On 12/20/24 2:37 PM, Simon Martin wrote:
We currently fail due to "infinite recursion" on the following invalid
code with -std=c++20 and above

=== cut here ===
template <class T> struct S { struct U { const S s; } u; };
S t{2};
=== cut here ===

The problem is that reshape_init_class for S calls reshape_init_r for
its field S::u, that calls reshape_init_class for S::U, that calls
reshape_init_r for field S::U::s that calls reshape_init_class for its
type S, etc.

This patch fixes the issue by erroring out in reshape_init_class if we
detect that we're about to call reshape_init_r for a type that's part of
our "TYPE_CONTEXT chain".

An alternative was to change the check in grokdeclarator that rejects
fields with an incomplete type to check for the field type's
TYPE_BEING_DECLARED (which sounds like the right thing to do), however
erroring for the definition of U::s breaks a bunch of existing test
cases, and it would also make us diverge from clang and MSVC (but behave
like EDG) - see https://godbolt.org/z/hT8d1GWa3. It feels to me like we
don't want that (I'm happy to revisit if people think it's OK).

We do error for U::s at instantiation time, because we need to instantiate U in order to instantiate S.

Erroring at template parse time is a bit tricky; we can't error immediately on the declaration of U::s, what makes it a problem is the later declaration of S::u. Without that we could instantiate U later, after S is already complete.

Successfully tested on x86_64-pc-linux-gnu.

        PR c++/118078

gcc/cp/ChangeLog:

        * decl.cc (reshape_init_class): Don't trigger infinite recursion
        for invalid "recursive types".

gcc/testsuite/ChangeLog:

        * g++.dg/cpp1z/class-deduction118.C: New test.
        * g++.dg/cpp2a/class-deduction-aggr16.C: New test.

---
  gcc/cp/decl.cc                                | 21 ++++++++++++++++---
  .../g++.dg/cpp1z/class-deduction118.C         |  8 +++++++
  .../g++.dg/cpp2a/class-deduction-aggr16.C     |  7 +++++++
  3 files changed, 33 insertions(+), 3 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction118.C
  create mode 100644 gcc/testsuite/g++.dg/cpp2a/class-deduction-aggr16.C

diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 42e83f880f9..ea55ea4c0e5 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -7315,9 +7315,24 @@ reshape_init_class (tree type, reshape_iter *d, bool 
first_initializer_p,
          d->cur++;
        }
        else
-       field_init = reshape_init_r (TREE_TYPE (field), d,
-                                    /*first_initializer_p=*/NULL_TREE,
-                                    complain);
+       {
+         /* Make sure that we won't be calling ourselves recursively, which
+            could happen with "recursive template types" (PR c++/118078).  */
+         tree ctx = TYPE_CONTEXT (type);
+         while (ctx && CLASS_TYPE_P (ctx))
+           {
+             if (ctx == TYPE_MAIN_VARIANT (TREE_TYPE (field))) {
+                 if (complain & tf_error)
+                   error ("field %qD has incomplete type %qT", field,
+                          TREE_TYPE (field));
+                 return error_mark_node;
+             }
+             ctx = TYPE_CONTEXT (ctx);
+           }

I'm concerned that this will break the situation I mentioned, of initializing an S::U in the case where S doesn't actually have a member of type U. That the member has the enclosing class type isn't the problem; the problem is that combined with the enclosing class having a member of the nested class type. It's the recursion, not the nesting.

+         field_init = reshape_init_r (TREE_TYPE (field), d,
+                                      /*first_initializer_p=*/NULL_TREE,
+                                      complain);
+       }
if (field_init == error_mark_node)
        return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction118.C 
b/gcc/testsuite/g++.dg/cpp1z/class-deduction118.C
new file mode 100644
index 00000000000..b14d62df793
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction118.C
@@ -0,0 +1,8 @@
+// PR c++/118078
+// { dg-do "compile" { target c++11 } }
+
+template <class T>
+struct S { struct U { const S s; } u; };
+S t{2};   // { dg-error "invalid use of template-name 'S' without an argument list" 
"" { target c++14_down } }
+         // { dg-error "class template argument deduction failed" "" { target 
c++17 } .-1 }
+         // { dg-error "no matching function for call to 'S\\\(int\\\)'" "" { 
target c++17 } .-2 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/class-deduction-aggr16.C 
b/gcc/testsuite/g++.dg/cpp2a/class-deduction-aggr16.C
new file mode 100644
index 00000000000..feab11927c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/class-deduction-aggr16.C
@@ -0,0 +1,7 @@
+// PR c++/118078
+// { dg-do "compile" { target c++20 } }
+
+template <class T>
+struct S { const struct U { struct V { volatile S s; } v; } u; };
+S t{2};   // { dg-error "class template argument deduction failed" }
+         // { dg-error "no matching function for call to 'S\\\(int\\\)'" "" { 
target *-*-* } .-1 }

Reply via email to