C++ PATCH for c++/82514, ICE with local class in generic lambda

Message ID CADzB+2==CM8i=c7e81D5WgNqPjrYejymKuGrkkU+WrEvuRRc=Q@mail.gmail.com
State New
Headers show
  • C++ PATCH for c++/82514, ICE with local class in generic lambda
Related show

Commit Message

Jason Merrill Jan. 26, 2018, 3:25 p.m.
The problem here was that when substituting the local class we first
substitute its context, and we weren't finding the rebuilt function
we're inside of, so we tried to create new instantiations of the
closure and call operator, leading to chaos.

The core of this patch is the change to tsubst_function_decl to
properly look up the enclosing function that we want for the context.
The subtle bit was handling the case where we're actually dealing with
a full instantiation of a generic lambda op(); we don't want to try to
look up an enclosing one when we're initially creating the decl as
part of normal template instantiation.  Since in the new model such a
full instantiation will always be from a template with exactly one
level of template parameters, we can check that to distinguish this

I also needed to adjust enclosing_instantiation_of to handle that
case, as regenerated_lambda_fn_p was giving the wrong answer.  The
result is actually simpler.

The change to do_pushtag isn't necessary, but it's a memory saving
opportunity I noticed; we never look in local_classes for closures, so
we shouldn't put them there either.

It's always interesting when after you get a patch working, you start
cutting it back to what's actually needed and discard bits that you
tried along the way but turned out not to be necessary.  This patch
was a lot longer at one point...

Tested x86_64-pc-linux-gnu, applying to trunk.
commit 220f44eaba310514a3410065dbf423886d5500d3
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Jan 24 13:33:00 2018 -0500

            PR c++/82514 - ICE with local class in generic lambda.
            * pt.c (regenerated_lambda_fn_p): Remove.
            (enclosing_instantiation_of): Don't use it.
            (tsubst_function_decl): Call enclosing_instantiation_of.
            * pt.c (lookup_template_class_1): Add sanity check.
            * name-lookup.c (do_pushtag): Don't add closures to local_classes.


diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index c37e52283e4..d0488c0a17e 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -6451,7 +6451,8 @@  do_pushtag (tree name, tree type, tag_scope scope)
 		 template instantiation rather than in some nested context.  */
 	      add_decl_expr (decl);
-	  else
+	  /* Lambdas use LAMBDA_EXPR_DISCRIMINATOR instead.  */
+	  else if (!LAMBDA_TYPE_P (type))
 	    vec_safe_push (local_classes, type);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index abfdbd96ae8..de8ad94200a 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -222,6 +222,7 @@  static tree tsubst_attributes (tree, tree, tsubst_flags_t, tree);
 static tree canonicalize_expr_argument (tree, tsubst_flags_t);
 static tree make_argument_pack (tree);
 static void register_parameter_specializations (tree, tree);
+static tree enclosing_instantiation_of (tree tctx);
 /* Make the current scope suitable for access checking when we are
    processing T.  T can be FUNCTION_DECL for instantiated function
@@ -8951,6 +8952,10 @@  lookup_template_class_1 (tree d1, tree arglist, tree in_decl, tree context,
       else if (CLASS_TYPE_P (template_type))
+	  /* Lambda closures are regenerated in tsubst_lambda_expr, not
+	     instantiated here.  */
+	  gcc_assert (!LAMBDA_TYPE_P (template_type));
 	  t = make_class_type (TREE_CODE (template_type));
 	    = CLASSTYPE_DECLARED_CLASS (template_type);
@@ -12183,9 +12188,20 @@  tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
 	return t;
       /* Calculate the most general template of which R is a
-	 specialization, and the complete set of arguments used to
-	 specialize R.  */
+	 specialization.  */
       gen_tmpl = most_general_template (DECL_TI_TEMPLATE (t));
+      /* We're substituting a lambda function under tsubst_lambda_expr but not
+	 directly from it; find the matching function we're already inside.
+	 But don't do this if T is a generic lambda with a single level of
+	 template parms, as in that case we're doing a normal instantiation. */
+      if (LAMBDA_FUNCTION_P (t) && !lambda_fntype
+	  && (!generic_lambda_fn_p (t)
+	      || TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (gen_tmpl)) > 1))
+	return enclosing_instantiation_of (t);
+      /* Calculate the complete set of arguments used to
+	 specialize R.  */
       argvec = tsubst_template_args (DECL_TI_ARGS
 				      (DECL_TI_TEMPLATE (t))),
@@ -12609,24 +12625,15 @@  lambda_fn_in_template_p (tree fn)
-/* True if FN is the op() for a lambda regenerated from a lambda in an
-   uninstantiated template.  */
-regenerated_lambda_fn_p (tree fn)
-  return (LAMBDA_FUNCTION_P (fn)
 /* We're instantiating a variable from template function TCTX.  Return the
    corresponding current enclosing scope.  This gets complicated because lambda
    functions in templates are regenerated rather than instantiated, but generic
    lambda functions are subsequently instantiated.  */
 static tree
-enclosing_instantiation_of (tree tctx)
+enclosing_instantiation_of (tree otctx)
+  tree tctx = otctx;
   tree fn = current_function_decl;
   int lambda_count = 0;
@@ -12635,22 +12642,18 @@  enclosing_instantiation_of (tree tctx)
   for (; fn; fn = decl_function_context (fn))
-      tree lambda = fn;
+      tree ofn = fn;
       int flambda_count = 0;
-      for (; fn && regenerated_lambda_fn_p (fn);
+      for (; flambda_count < lambda_count && fn && LAMBDA_FUNCTION_P (fn);
 	   fn = decl_function_context (fn))
       if (DECL_TEMPLATE_INFO (fn)
 	  ? most_general_template (fn) != most_general_template (tctx)
 	  : fn != tctx)
-      if (lambda_count)
-	{
-	  fn = lambda;
-	  while (flambda_count-- > lambda_count)
-	    fn = decl_function_context (fn);
-	}
-      return fn;
+      gcc_assert (DECL_NAME (ofn) == DECL_NAME (otctx)
+		  || DECL_CONV_FN_P (ofn));
+      return ofn;
   gcc_unreachable ();
diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-nested2.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-nested2.C
new file mode 100644
index 00000000000..3dbc5b3085a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-nested2.C
@@ -0,0 +1,13 @@ 
+// PR c++/82514
+// { dg-do compile { target c++14 } }
+void g();
+template <typename h> void i(h) { g(); }
+template <int> void n() {
+  [](auto) {
+    struct p { };
+    i(p{});
+  } ('\n');
+auto f = n<1>;