[PATCH v2] c++: Endless loop with targ deduction in member tmpl [PR95888]
Marek Polacek
polacek@redhat.com
Thu Feb 11 19:24:22 GMT 2021
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
More information about the Gcc-patches
mailing list