Bug 95888 - [9 Regression] Regression in 9.3. GCC freezes when compiling code using boost::poly_collection::segment
Summary: [9 Regression] Regression in 9.3. GCC freezes when compiling code using boost...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.3.0
: P2 normal
Target Milestone: 9.4
Assignee: Marek Polacek
URL:
Keywords: patch
Depends on:
Blocks:
 
Reported: 2020-06-25 07:03 UTC by soropi8907
Modified: 2021-02-11 23:25 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work: 9.2.0
Known to fail: 10.1.0, 10.2.0, 9.3.0
Last reconfirmed: 2021-02-05 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description soropi8907 2020-06-25 07:03:50 UTC
Version 9.3 and later. 9.2 compiles OK. 


#include <boost/poly_collection/base_collection.hpp>

struct base{};

int main()
{
  const boost::base_collection<base> n;

#define EXPOSE_BUG 1
#if EXPOSE_BUG
  n.segment<base>().begin();
#else
  n.segment<base,nullptr>().begin();
#endif
}

https://godbolt.org/z/_yau99
Comment 1 Jonathan Wakely 2020-06-25 07:15:13 UTC
You were asked to read https://gcc.gnu.org/bugs/ when creating a new bug report. Please read it and provide what's missing.
Comment 2 Jonathan Wakely 2020-06-25 07:20:27 UTC
The problem started with r10-7007-6b3302da9ef26aa11940f8c0dc92bec19e15c09b:

            PR c++/90505 - mismatch in template argument deduction.
            * pt.c (tsubst): Don't reduce the template level of template
            parameters when tf_partial.
Comment 3 Martin Liška 2020-06-25 07:44:43 UTC
I'm reducing that right now..
Comment 4 Martin Liška 2020-06-25 08:08:36 UTC
Reduced test-case:

cat pr95888.ii
template <typename PolyCollection> class local_iterator_impl {
  using const_segment_map_iterator =
      typename PolyCollection::const_segment_map_iterator;
  template <typename Iterator>
  local_iterator_impl(const_segment_map_iterator, Iterator);
  template <typename> friend class local_iterator_impl;
  friend PolyCollection;
};
template <typename> class poly_collection {
  template <typename> using enable_if_acceptable = int *;
  using segment_map = int;
  using const_segment_map_iterator = segment_map;
  template <typename> friend class local_iterator_impl;
  template <typename>
  using local_iterator_impl = local_iterator_impl<poly_collection>;
public:
  template <typename T> using const_local_iterator = local_iterator_impl<T>;
  template <typename> class const_segment_info {
  public:
    const_local_iterator<int> begin() { return {it, 0}; }
    const_segment_map_iterator it;
  };
  template <typename T, enable_if_acceptable<T> = nullptr>
  const_segment_info<T> segment();
};

int
main() {
  poly_collection<int> n;
  n.segment<int>().begin();
}
Comment 5 Marek Polacek 2021-02-05 22:28:55 UTC
Simplified test:

template <typename T> class A {
  A(int, int);
  template <typename> friend class A;
  friend T;
};

template<typename U> struct B {
  template<typename T> struct C {
    A<B> begin() { return {1, 0}; }
  };
  template<typename T, int *P = nullptr>
  C<T> fn();
};

int
main ()
{
  B<int> b;
  b.fn<int>().begin();
}
Comment 6 Marek Polacek 2021-02-09 20:05:26 UTC
I've found out that the same issue happens with auto template parameter too, and started way before my change:

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();
}

this (valid) used to crash, and since r243867 we loop infinitely.

I have a patch that fixes both.
Comment 7 Marek Polacek 2021-02-09 22:46:06 UTC
Patch posted:
https://gcc.gnu.org/pipermail/gcc-patches/2021-February/565086.html
Comment 8 GCC Commits 2021-02-11 20:57:10 UTC
The master branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:88cfd531c69b3c1fe7a3c183d83cfeacc8f69402

commit r11-7197-g88cfd531c69b3c1fe7a3c183d83cfeacc8f69402
Author: Marek Polacek <polacek@redhat.com>
Date:   Tue Feb 9 15:17:48 2021 -0500

    c++: Endless loop with targ deduction in member tmpl [PR95888]
    
    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): Pass tf_none to tsubst 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.
Comment 9 Marek Polacek 2021-02-11 20:59:49 UTC
Fixed on trunk so far.  Testing gcc10 now.
Comment 10 GCC Commits 2021-02-11 23:24:48 UTC
The releases/gcc-10 branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:60dbb46b5b3c52d0483d676eabd4dfe32110e61b

commit r10-9360-g60dbb46b5b3c52d0483d676eabd4dfe32110e61b
Author: Marek Polacek <polacek@redhat.com>
Date:   Tue Feb 9 15:17:48 2021 -0500

    c++: Endless loop with targ deduction in member tmpl [PR95888]
    
    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): Pass tf_none to tsubst 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.
    
    (cherry picked from commit 88cfd531c69b3c1fe7a3c183d83cfeacc8f69402)
Comment 11 Marek Polacek 2021-02-11 23:25:13 UTC
Fixed.