On Thu, Feb 11, 2021 at 11:30:07AM -0500, Jason Merrill via Gcc-patches wrote: > On 2/9/21 5:41 PM, Marek Polacek wrote: > > My r10-7007 patch tweaked tsubst not to reduce the template level of > > template parameters when tf_partial. That caused infinite looping in > > is_specialization_of: we ended up with a class template specialization > > whose TREE_TYPE (CLASSTYPE_TI_TEMPLATE (t)) == t, so the second for > > loop in is_specialization_of never finished. > > > > There's a lot going on in this test, but essentially: the template fn > > here has two template parameters, we call it with one explicitly > > provided, the other one has to be deduced. So we'll find ourselves > > in fn_type_unification which uses tf_partial when tsubsting the > > *explicit* template arguments into the function type. That leads to > > tsubstituting the return type, C<T>. C is a member template; its > > most general template is > > > > template<class U> template<class V> struct B<U>::C > > > > we figure out (tsubst_template_args) that the template argument list > > is <int, int>. They come from different levels, one comes from B<int>, > > the other one from fn<int>. > > > > So now we lookup_template_class to see if we have C<int, int>. We > > do the > > /* This is a full instantiation of a member template. Find > > the partial instantiation of which this is an instance. */ > > TREE_VEC_LENGTH (arglist)--; > > // arglist is now <int>, not <int, int> > > found = tsubst (gen_tmpl, arglist, complain, NULL_TREE); > > TREE_VEC_LENGTH (arglist)++; > > > > magic which is looking for the partial instantiation, in this case, > > that would be template<class V> struct B<int>::C. Note we're still > > in a tf_partial context! So the tsubst_template_args in the tsubst > > (which tries to substitute <int> into <U, V>) returns <int, V>, but > > V's template level hasn't been reduced! After tsubst_template_args, > > tsubst_template_decl looks to see if we already have this specialization: > > > > // t = template_decl C > > // full_args = <int, V> > > spec = retrieve_specialization (t, full_args, hash); > > > > but doesn't find the one we created a while ago, when processing > > B<int> b; in the test, because V's levels don't match. Whereupon > > tsubst_template_decl creates a new TEMPLATE_DECL, one that leads to > > the infinite looping problem. > > > > I think let's clear tf_partial when looking for an existing partial > > instantiation. > > > > It also occurred to me that I should be able to trigger a similar > > problem with 'auto', since r10-7007 removed an is_auto check. And lo, > > I constructed deduce10.C which exhibits the same issue with pre-r10-7007 > > compilers. This patch fixes that problem as well. I'm ecstatic. > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/10? Also > > built cmcstl2. > > Perhaps the mistake here is using the complain parm at all; this > substitution is not in the "immediate context" of deduction. Either tf_none > or tf_warning_or_error should be fine, as we know this substitution has > previously succeeded.
Yeah, that makes sense to me too: Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/10? -- >8 -- My r10-7007 patch tweaked tsubst not to reduce the template level of template parameters when tf_partial. That caused infinite looping in is_specialization_of: we ended up with a class template specialization whose TREE_TYPE (CLASSTYPE_TI_TEMPLATE (t)) == t, so the second for loop in is_specialization_of never finished. There's a lot going on in this test, but essentially: the template fn here has two template parameters, we call it with one explicitly provided, the other one has to be deduced. So we'll find ourselves in fn_type_unification which uses tf_partial when tsubsting the *explicit* template arguments into the function type. That leads to tsubstituting the return type, C<T>. C is a member template; its most general template is template<class U> template<class V> struct B<U>::C we figure out (tsubst_template_args) that the template argument list is <int, int>. They come from different levels, one comes from B<int>, the other one from fn<int>. So now we lookup_template_class to see if we have C<int, int>. We do the /* This is a full instantiation of a member template. Find the partial instantiation of which this is an instance. */ TREE_VEC_LENGTH (arglist)--; // arglist is now <int>, not <int, int> found = tsubst (gen_tmpl, arglist, complain, NULL_TREE); TREE_VEC_LENGTH (arglist)++; magic which is looking for the partial instantiation, in this case, that would be template<class V> struct B<int>::C. Note we're still in a tf_partial context! So the tsubst_template_args in the tsubst (which tries to substitute <int> into <U, V>) returns <int, V>, but V's template level hasn't been reduced! After tsubst_template_args, tsubst_template_decl looks to see if we already have this specialization: // t = template_decl C // full_args = <int, V> spec = retrieve_specialization (t, full_args, hash); but doesn't find the one we created a while ago, when processing B<int> b; in the test, because V's levels don't match. Whereupon tsubst_template_decl creates a new TEMPLATE_DECL, one that leads to the infinite looping problem. Fixed by using tf_none when looking for an existing partial instantiation. It also occurred to me that I should be able to trigger a similar problem with 'auto', since r10-7007 removed an is_auto check. And lo, I constructed deduce10.C which exhibits the same issue with pre-r10-7007 compilers. This patch fixes that problem as well. I'm ecstatic. gcc/cp/ChangeLog: PR c++/95888 * pt.c (lookup_template_class_1): Clear tf_partial when looking for the partial instantiation. gcc/testsuite/ChangeLog: PR c++/95888 * g++.dg/template/deduce10.C: New test. * g++.dg/template/deduce9.C: New test. --- gcc/cp/pt.c | 12 +++++++++++- gcc/testsuite/g++.dg/template/deduce10.C | 23 +++++++++++++++++++++++ gcc/testsuite/g++.dg/template/deduce9.C | 23 +++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/template/deduce10.C create mode 100644 gcc/testsuite/g++.dg/template/deduce9.C diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index d8574f649b2..37c45a297c2 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -10137,7 +10137,17 @@ lookup_template_class_1 (tree d1, tree arglist, tree in_decl, tree context, /* Temporarily reduce by one the number of levels in the ARGLIST so as to avoid comparing the last set of arguments. */ TREE_VEC_LENGTH (arglist)--; - found = tsubst (gen_tmpl, arglist, complain, NULL_TREE); + /* We don't use COMPLAIN in the following call because this isn't + the immediate context of deduction. For instance, tf_partial + could be set here as we might be at the beginning of template + argument deduction when any explicitly specified template + arguments are substituted into the function type. tf_partial + could lead into trouble because we wouldn't find the partial + instantiation that might have been created outside tf_partial + context, because the levels of template parameters wouldn't + match, because in a tf_partial context, tsubst doesn't reduce + TEMPLATE_PARM_LEVEL. */ + found = tsubst (gen_tmpl, arglist, tf_none, NULL_TREE); TREE_VEC_LENGTH (arglist)++; /* FOUND is either a proper class type, or an alias template specialization. In the later case, it's a diff --git a/gcc/testsuite/g++.dg/template/deduce10.C b/gcc/testsuite/g++.dg/template/deduce10.C new file mode 100644 index 00000000000..165ff195728 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/deduce10.C @@ -0,0 +1,23 @@ +// PR c++/95888 +// { dg-do compile { target c++17 } } + +template <typename T> class A { + A(int, int); + template <typename> friend class A; + friend T; +}; + +template<typename U> struct B { + template<auto V> struct C { + A<B> begin() { return {1, 0}; } + }; + template<auto Z, int *P = nullptr> + C<Z> fn(); +}; + +int +main () +{ + B<int> b; + b.fn<1>().begin(); +} diff --git a/gcc/testsuite/g++.dg/template/deduce9.C b/gcc/testsuite/g++.dg/template/deduce9.C new file mode 100644 index 00000000000..5f55a84ed0a --- /dev/null +++ b/gcc/testsuite/g++.dg/template/deduce9.C @@ -0,0 +1,23 @@ +// PR c++/95888 +// { dg-do compile { target c++11 } } + +template <typename T> class A { + A(int, int); + template <typename> friend class A; + friend T; +}; + +template<typename U> struct B { + template<typename V> struct C { + A<B> begin() { return {1, 0}; } + }; + template<typename Z, int *P = nullptr> + C<Z> fn(); +}; + +int +main () +{ + B<int> b; + b.fn<int>().begin(); +} base-commit: 2dcdd15d0bafb9b45a8d7ff580217bd6ac1f0975 -- 2.29.2