]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++/modules: Use containing type as key for all member lambdas [PR122310]
authorNathaniel Shead <nathanieloshead@gmail.com>
Sat, 18 Oct 2025 12:43:14 +0000 (23:43 +1100)
committerNathaniel Shead <nathanieloshead@gmail.com>
Tue, 28 Oct 2025 01:22:52 +0000 (12:22 +1100)
The ICE in the linked PR occurs because we first stream the lambda type
before its keyed decl has been streamed, but the key decl's type depends
on the lambda.  And so when streaming the key decl to check for an
existing decl to merge with, merging the key decl itself crashes because
its type has only been partially streamed.

This patch fixes the issue by generalising the existing FIELD_DECL
handling to any class member using the outermost containing TYPE_DECL as
its key type.  This way we can guarantee that the key decl has been
streamed before the lambda type is otherwise needed.

PR c++/122310

gcc/cp/ChangeLog:

* module.cc (get_keyed_decl_scope): New function.
(trees_out::get_merge_kind): Use it.
(trees_out::key_mergeable): Use it.
(maybe_key_decl): Key to the containing type for all members.

gcc/testsuite/ChangeLog:

* g++.dg/modules/lambda-12.h: New test.
* g++.dg/modules/lambda-12_a.H: New test.
* g++.dg/modules/lambda-12_b.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
Reviewed-by: Jason Merrill <jason@redhat.com>
(cherry picked from commit 8212abbeffa69f143808e126f40c67f3eb7e7844)

gcc/cp/module.cc
gcc/testsuite/g++.dg/modules/lambda-12.h [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/lambda-12_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/lambda-12_b.C [new file with mode: 0644]

index 2f19b2b485dd52594128e67be18b528186e383b7..8c64a82f7ed8133f9f71056ebc3c8d5380d9a607 100644 (file)
@@ -2789,6 +2789,8 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+static tree get_keyed_decl_scope (tree);
+
 /* Instantiations of temploid friends imported from another module
    need to be attached to the same module as the temploid.  This maps
    these decls to the temploid they are instantiated from, as there is
@@ -11275,20 +11277,12 @@ trees_out::get_merge_kind (tree decl, depset *dep)
            if (DECL_IMPLICIT_TYPEDEF_P (STRIP_TEMPLATE (decl))
                && LAMBDA_TYPE_P (TREE_TYPE (decl)))
              {
-               if (tree scope = LAMBDA_TYPE_EXTRA_SCOPE (TREE_TYPE (decl)))
-                 {
-                   /* Lambdas attached to fields are keyed to its class.  */
-                   if (TREE_CODE (scope) == FIELD_DECL)
-                     scope = TYPE_NAME (DECL_CONTEXT (scope));
-                   if (DECL_LANG_SPECIFIC (scope)
-                       && DECL_MODULE_KEYED_DECLS_P (scope))
-                     {
-                       mk = MK_keyed;
-                       break;
-                     }
-                 }
-               /* Lambdas not attached to any mangling scope are TU-local.  */
-               mk = MK_unique;
+               if (get_keyed_decl_scope (decl))
+                 mk = MK_keyed;
+               else
+                 /* Lambdas not attached to any mangling scope are TU-local
+                    and so cannot be deduplicated.  */
+                 mk = MK_unique;
                break;
              }
 
@@ -11589,16 +11583,9 @@ trees_out::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
 
        case MK_keyed:
          {
-           gcc_checking_assert (LAMBDA_TYPE_P (TREE_TYPE (inner)));
-           tree scope = LAMBDA_TYPE_EXTRA_SCOPE (TREE_TYPE (inner));
-           gcc_checking_assert (TREE_CODE (scope) == VAR_DECL
-                                || TREE_CODE (scope) == FIELD_DECL
-                                || TREE_CODE (scope) == PARM_DECL
-                                || TREE_CODE (scope) == TYPE_DECL
-                                || TREE_CODE (scope) == CONCEPT_DECL);
-           /* Lambdas attached to fields are keyed to the class.  */
-           if (TREE_CODE (scope) == FIELD_DECL)
-             scope = TYPE_NAME (DECL_CONTEXT (scope));
+           tree scope = get_keyed_decl_scope (inner);
+           gcc_checking_assert (scope);
+
            auto *root = keyed_table->get (scope);
            unsigned ix = root->length ();
            /* If we don't find it, we'll write a really big number
@@ -20916,9 +20903,21 @@ maybe_key_decl (tree ctx, tree decl)
       && TREE_CODE (ctx) != CONCEPT_DECL)
     return;
 
-  /* For fields, key it to the containing type to handle deduplication
-     correctly.  */
-  if (TREE_CODE (ctx) == FIELD_DECL)
+  /* For members, key it to the containing type to handle deduplication
+     correctly.  For fields, this is necessary as FIELD_DECLs have no
+     dep and so would only be streamed after the lambda type, defeating
+     our ability to merge them.
+
+     Other class-scope key decls might depend on the type of the lambda
+     but be within the same cluster; we need to ensure that we never
+     first see the key decl while streaming the lambda type as merging
+     would then fail when comparing the partially-streamed lambda type
+     of the key decl with the existing (PR c++/122310).
+
+     Perhaps sort_cluster can be adjusted to handle this better, but
+     this is a simple workaround (and might down on the number of
+     entries in keyed_table as a bonus).  */
+  while (DECL_CLASS_SCOPE_P (ctx))
     ctx = TYPE_NAME (DECL_CONTEXT (ctx));
 
   if (!keyed_table)
@@ -20933,6 +20932,30 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* Find the scope that the lambda DECL is keyed to, if any.  */
+
+static tree
+get_keyed_decl_scope (tree decl)
+{
+  gcc_checking_assert (LAMBDA_TYPE_P (TREE_TYPE (decl)));
+  tree scope = LAMBDA_TYPE_EXTRA_SCOPE (TREE_TYPE (decl));
+  if (!scope)
+    return NULL_TREE;
+
+  gcc_checking_assert (TREE_CODE (scope) == VAR_DECL
+                      || TREE_CODE (scope) == FIELD_DECL
+                      || TREE_CODE (scope) == PARM_DECL
+                      || TREE_CODE (scope) == TYPE_DECL
+                      || TREE_CODE (scope) == CONCEPT_DECL);
+
+  while (DECL_CLASS_SCOPE_P (scope))
+    scope = TYPE_NAME (DECL_CONTEXT (scope));
+
+  gcc_checking_assert (DECL_LANG_SPECIFIC (scope)
+                      && DECL_MODULE_KEYED_DECLS_P (scope));
+  return scope;
+}
+
 /* DECL is an instantiated friend that should be attached to the same
    module that ORIG is.  */
 
diff --git a/gcc/testsuite/g++.dg/modules/lambda-12.h b/gcc/testsuite/g++.dg/modules/lambda-12.h
new file mode 100644 (file)
index 0000000..4dd329d
--- /dev/null
@@ -0,0 +1,27 @@
+// PR c++/122310
+struct Foo {
+  constexpr static inline auto do_nothing = [](auto && ...){};
+  using TNothing = decltype(do_nothing);
+};
+
+template <typename T>
+struct X {
+  struct Inner {
+    union MoreInner {
+      static constexpr auto x = []{};
+#if __cplusplus >= 202002L
+      static decltype([]{}) y;
+#endif
+    };
+  };
+
+  using A = decltype(Inner::MoreInner::x);
+#if __cplusplus >= 202002L
+  using B = decltype(Inner::MoreInner::y);
+#endif
+};
+
+inline X<int>::A* a{};
+#if __cplusplus >= 202002L
+inline X<int>::B* b{};
+#endif
diff --git a/gcc/testsuite/g++.dg/modules/lambda-12_a.H b/gcc/testsuite/g++.dg/modules/lambda-12_a.H
new file mode 100644 (file)
index 0000000..83f5d15
--- /dev/null
@@ -0,0 +1,5 @@
+// PR c++/122310
+// { dg-additional-options "-fmodule-header" }
+// { dg-module-cmi {} }
+
+#include "lambda-12.h"
diff --git a/gcc/testsuite/g++.dg/modules/lambda-12_b.C b/gcc/testsuite/g++.dg/modules/lambda-12_b.C
new file mode 100644 (file)
index 0000000..c24d939
--- /dev/null
@@ -0,0 +1,5 @@
+// PR c++/122310
+// { dg-additional-options "-fmodules -fno-module-lazy" }
+
+#include "lambda-12.h"
+import "lambda-12_a.H";