]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++/modules: Fix exported using-directive of imported namespace [PR121702]
authorNathaniel Shead <nathanieloshead@gmail.com>
Fri, 5 Sep 2025 03:29:12 +0000 (13:29 +1000)
committerNathaniel Shead <nathanieloshead@gmail.com>
Sat, 6 Sep 2025 12:00:39 +0000 (22:00 +1000)
Currently we represent exported using-directives as a list of indices
into the namespace array that we stream.  However this list of
namespaces doesn't include any namespaces that we don't expose in this
module's purview, and so we ICE.

This patch reworks the handling to instead use the existing depset
tracking for namespaces directly.  This means that we don't need to
build up a second lookup map when streaming, and we can reuse the logic
in {read,write}_namespace.  We do need to make sure that we create a
depset for namespaces only referenced by a using-directive, though.

I don't expect to be exporting large numbers of using-directives from a
namespace, so for simplicity we stream the names as {parent, target}
pairs.

This also adjusts read handling so that we load the using-directives for
any import (including indirect) if it's in the import list for the
current TU.  Otherwise we run into issues if the using-directive is in
a namespace that is otherwise never referenced in the 'export import'ing
module, because we never walk this namespace and so never know that we
need to emit it.  To do this the patch ensures that we calculate the
import list before read_language is called.

As a drive-by fix, I noticed that with modules 'add_using_namespace'
will add duplicate using-directives because we compare usings against
the target namespace, but we then push a wrapping USING_DECL instead.
This reworks so that the contents of the structure is equivalent between
modules and non-modules code.

PR c++/121702

gcc/cp/ChangeLog:

* module.cc (enum module_state_counts): New counter.
(depset::hash::add_namespace_entities): Seed using-directive
targets for later streaming.
(module_state::write_namespaces): Don't handle using-directives
here.
(module_state::read_namespaces): Likewise.
(module_state::write_using_directives): New function.
(module_state::read_using_directives): New function.
(module_state::write_counts): Log using-directives.
(module_state::read_counts): Likewise.
(module_state::write_begin): Stream using-directives.
(module_state::read_language): Read using-directives if
directly importing.
(module_state::direct_import): Update current TU import list
before calling read_language.
* name-lookup.cc (add_using_namespace): Fix lookup of previous
using-directives.
* parser.cc (cp_parser_import_declaration): Don't set
MK_EXPORTING when performing import_module.

gcc/testsuite/ChangeLog:

* g++.dg/modules/namespace-10_c.C: Add check for log dump.
* g++.dg/modules/namespace-13_a.C: New test.
* g++.dg/modules/namespace-13_b.C: New test.
* g++.dg/modules/namespace-13_c.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
gcc/cp/module.cc
gcc/cp/name-lookup.cc
gcc/cp/parser.cc
gcc/testsuite/g++.dg/modules/namespace-10_c.C
gcc/testsuite/g++.dg/modules/namespace-13_a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/namespace-13_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/namespace-13_c.C [new file with mode: 0644]

index 170a0bdaa9e3e27028b30a7b54ab54098a12338e..5ab340afe365da35fa1f6923ea12354a61d7eac9 100644 (file)
@@ -3633,6 +3633,7 @@ enum module_state_counts
   MSC_pendings,
   MSC_entities,
   MSC_namespaces,
+  MSC_using_directives,
   MSC_bindings,
   MSC_macros,
   MSC_inits,
@@ -3854,6 +3855,10 @@ class GTY((chain_next ("%h.parent"), for_user)) module_state {
                         unsigned, unsigned *crc_ptr);
   bool read_namespaces (unsigned);
 
+  unsigned write_using_directives (elf_out *to, depset::hash &,
+                                  vec<depset *> spaces, unsigned *crc_ptr);
+  bool read_using_directives (unsigned);
+
   void intercluster_seed (trees_out &sec, unsigned index, depset *dep);
   unsigned write_cluster (elf_out *to, depset *depsets[], unsigned size,
                          depset::hash &, unsigned *counts, unsigned *crc_ptr);
@@ -14691,17 +14696,22 @@ depset::hash::add_namespace_entities (tree ns, bitmap partitions)
   data.partitions = partitions;
   data.hash = this;
 
-  hash_table<named_decl_hash>::iterator end
-    (DECL_NAMESPACE_BINDINGS (ns)->end ());
-  for (hash_table<named_decl_hash>::iterator iter
-        (DECL_NAMESPACE_BINDINGS (ns)->begin ()); iter != end; ++iter)
+  for (tree binding : *DECL_NAMESPACE_BINDINGS (ns))
     {
       data.binding = nullptr;
       data.met_namespace = false;
-      if (walk_module_binding (*iter, partitions, add_binding_entity, &data))
+      if (walk_module_binding (binding, partitions, add_binding_entity, &data))
        count++;
     }
 
+  /* Seed any using-directives so that we emit the relevant namespaces.  */
+  for (tree udir : NAMESPACE_LEVEL (ns)->using_directives)
+    if (TREE_CODE (udir) == USING_DECL && DECL_MODULE_EXPORT_P (udir))
+      {
+       make_dependency (USING_DECL_DECLS (udir), depset::EK_NAMESPACE);
+       count++;
+      }
+
   if (count)
     dump () && dump ("Found %u entries", count);
   dump.outdent ();
@@ -17128,15 +17138,11 @@ module_state::write_namespaces (elf_out *to, vec<depset *> spaces,
   bytes_out sec (to);
   sec.begin ();
 
-  hash_map<tree, unsigned> ns_map;
-
   for (unsigned ix = 0; ix != num; ix++)
     {
       depset *b = spaces[ix];
       tree ns = b->get_entity ();
 
-      ns_map.put (ns, ix);
-
       /* This could be an anonymous namespace even for a named module,
         since we can still emit no-linkage decls.  */
       gcc_checking_assert (TREE_CODE (ns) == NAMESPACE_DECL);
@@ -17178,31 +17184,6 @@ module_state::write_namespaces (elf_out *to, vec<depset *> spaces,
        }
     }
 
-  /* Now write exported using-directives, as a sequence of 1-origin indices in
-     the spaces array (not entity indices): First the using namespace, then the
-     used namespaces.  And then a zero terminating the list.  :: is
-     represented as index -1.  */
-  auto emit_one_ns = [&](unsigned ix, tree ns) {
-    for (auto udir: NAMESPACE_LEVEL (ns)->using_directives)
-      {
-       if (TREE_CODE (udir) != USING_DECL || !DECL_MODULE_EXPORT_P (udir))
-         continue;
-       tree ns2 = USING_DECL_DECLS (udir);
-       dump() && dump ("Writing using-directive in %N for %N",
-                       ns, ns2);
-       sec.u (ix);
-       sec.u (*ns_map.get (ns2) + 1);
-      }
-  };
-  emit_one_ns (-1, global_namespace);
-  for (unsigned ix = 0; ix != num; ix++)
-    {
-      depset *b = spaces[ix];
-      tree ns = b->get_entity ();
-      emit_one_ns (ix + 1, ns);
-    }
-  sec.u (0);
-
   sec.end (to, to->name (MOD_SNAME_PFX ".nms"), crc_p);
   dump.outdent ();
 }
@@ -17221,8 +17202,6 @@ module_state::read_namespaces (unsigned num)
   dump () && dump ("Reading namespaces");
   dump.indent ();
 
-  tree *ns_map = XALLOCAVEC (tree, num);
-
   for (unsigned ix = 0; ix != num; ix++)
     {
       unsigned entity_index = sec.u ();
@@ -17284,8 +17263,6 @@ module_state::read_namespaces (unsigned num)
        DECL_ATTRIBUTES (inner)
          = tree_cons (get_identifier ("abi_tag"), tags, DECL_ATTRIBUTES (inner));
 
-      ns_map[ix] = inner;
-
       /* Install the namespace.  */
       (*entity_ary)[entity_lwm + entity_index] = inner;
       if (DECL_MODULE_IMPORT_P (inner))
@@ -17301,41 +17278,79 @@ module_state::read_namespaces (unsigned num)
        }
     }
 
-  /* Read the exported using-directives.  */
-  while (unsigned ix = sec.u ())
+  dump.outdent ();
+  if (!sec.end (from ()))
+    return false;
+  return true;
+}
+
+unsigned
+module_state::write_using_directives (elf_out *to, depset::hash &table,
+                                     vec<depset *> spaces, unsigned *crc_p)
+{
+  dump () && dump ("Writing using-directives");
+  dump.indent ();
+
+  bytes_out sec (to);
+  sec.begin ();
+
+  unsigned num = 0;
+  auto emit_one_ns = [&](depset *parent_dep)
     {
-      tree ns;
-      if (ix == (unsigned)-1)
-       ns = global_namespace;
-      else
-       {
-         if (--ix >= num)
-           {
-             sec.set_overrun ();
-             break;
-           }
-         ns = ns_map [ix];
-       }
-      unsigned ix2 = sec.u ();
-      if (--ix2 >= num)
+      tree parent = parent_dep->get_entity ();
+      for (auto udir : NAMESPACE_LEVEL (parent)->using_directives)
        {
-         sec.set_overrun ();
-         break;
-       }
-      tree ns2 = ns_map [ix2];
-      if (directness)
-       {
-         dump() && dump ("Reading using-directive in %N for %N",
-                         ns, ns2);
-         /* In an export import this will mark the using-directive as
-            exported, so it will be emitted again.  */
-         add_using_namespace (ns, ns2);
+         if (TREE_CODE (udir) != USING_DECL || !DECL_MODULE_EXPORT_P (udir))
+           continue;
+         tree target = USING_DECL_DECLS (udir);
+         depset *target_dep = table.find_dependency (target);
+         gcc_checking_assert (target_dep);
+
+         dump () && dump ("Writing using-directive in %N for %N",
+                          parent, target);
+         write_namespace (sec, parent_dep);
+         write_namespace (sec, target_dep);
+         ++num;
        }
-      else
-       /* Ignore using-directives from indirect imports, we only want them
-          from our own imports.  */
-       dump() && dump ("Ignoring using-directive in %N for %N",
-                       ns, ns2);
+    };
+
+  emit_one_ns (table.find_dependency (global_namespace));
+  for (depset *parent_dep : spaces)
+    emit_one_ns (parent_dep);
+
+  sec.end (to, to->name (MOD_SNAME_PFX ".udi"), crc_p);
+  dump.outdent ();
+
+  return num;
+}
+
+bool
+module_state::read_using_directives (unsigned num)
+{
+  if (!bitmap_bit_p ((*modules)[0]->imports, mod))
+    {
+      dump () && dump ("Ignoring using-directives because module %M "
+                      "is not visible in this TU", this);
+      return true;
+    }
+
+  bytes_in sec;
+
+  if (!sec.begin (loc, from (), MOD_SNAME_PFX ".udi"))
+    return false;
+
+  dump () && dump ("Reading using-directives");
+  dump.indent ();
+
+  for (unsigned ix = 0; ix != num; ++ix)
+    {
+      tree parent = read_namespace (sec);
+      tree target = read_namespace (sec);
+      if (sec.get_overrun ())
+       break;
+
+      dump () && dump ("Read using-directive in %N for %N", parent, target);
+      add_using_namespace (parent, target);
     }
 
   dump.outdent ();
@@ -19857,6 +19872,7 @@ module_state::write_counts (elf_out *to, unsigned counts[MSC_HWM],
       dump ("Pendings %u", counts[MSC_pendings]);
       dump ("Entities %u", counts[MSC_entities]);
       dump ("Namespaces %u", counts[MSC_namespaces]);
+      dump ("Using-directives %u", counts[MSC_using_directives]);
       dump ("Macros %u", counts[MSC_macros]);
       dump ("Initializers %u", counts[MSC_inits]);
     }
@@ -19883,6 +19899,7 @@ module_state::read_counts (unsigned counts[MSC_HWM])
       dump ("Pendings %u", counts[MSC_pendings]);
       dump ("Entities %u", counts[MSC_entities]);
       dump ("Namespaces %u", counts[MSC_namespaces]);
+      dump ("Using-directives %u", counts[MSC_using_directives]);
       dump ("Macros %u", counts[MSC_macros]);
       dump ("Initializers %u", counts[MSC_inits]);
     }
@@ -20165,7 +20182,8 @@ ool_cmp (const void *a_, const void *b_)
      qualified-names       : binding value(s)
      MOD_SNAME_PFX.README   : human readable, strings
      MOD_SNAME_PFX.ENV      : environment strings, strings
-     MOD_SNAME_PFX.nms             : namespace hierarchy
+     MOD_SNAME_PFX.nms      : namespace hierarchy
+     MOD_SNAME_PFX.udi      : namespace using-directives
      MOD_SNAME_PFX.bnd      : binding table
      MOD_SNAME_PFX.spc      : specialization table
      MOD_SNAME_PFX.imp      : import table
@@ -20411,6 +20429,11 @@ module_state::write_begin (elf_out *to, cpp_reader *reader,
   if (counts[MSC_namespaces])
     write_namespaces (to, spaces, counts[MSC_namespaces], &crc);
 
+  /* Write any using-directives.  */
+  if (counts[MSC_namespaces])
+    counts[MSC_using_directives]
+      = write_using_directives (to, table, spaces, &crc);
+
   /* Write the bindings themselves.  */
   counts[MSC_bindings] = write_bindings (to, sccs, &crc);
 
@@ -20662,11 +20685,16 @@ module_state::read_language (bool outermost)
                         counts[MSC_sec_lwm], counts[MSC_sec_hwm]))
     ok = false;
 
-  /* Read the namespace hierarchy. */
+  /* Read the namespace hierarchy.  */
   if (ok && counts[MSC_namespaces]
       && !read_namespaces (counts[MSC_namespaces]))
     ok = false;
 
+  /* Read any using-directives.  */
+  if (ok && counts[MSC_using_directives]
+      && !read_using_directives (counts[MSC_using_directives]))
+    ok = false;
+
   if (ok && !read_bindings (counts[MSC_bindings],
                            counts[MSC_sec_lwm], counts[MSC_sec_hwm]))
     ok = false;
@@ -21753,6 +21781,8 @@ direct_import (module_state *import, cpp_reader *reader)
     if (!import->do_import (reader, true))
       gcc_unreachable ();
 
+  (*modules)[0]->set_import (import, import->exported_p);
+
   if (import->loadedness < ML_LANGUAGE)
     {
       if (!keyed_table)
@@ -21760,8 +21790,6 @@ direct_import (module_state *import, cpp_reader *reader)
       import->read_language (true);
     }
 
-  (*modules)[0]->set_import (import, import->exported_p);
-
   dump.pop (n);
   timevar_stop (TV_MODULE_IMPORT);
 }
index 9a2d0747ba48b6064f2181d26d3168b7c6898616..43b95bffecfac10a98f57da09df52e0bec7caf8d 100644 (file)
@@ -8957,20 +8957,34 @@ pop_nested_namespace (tree ns)
 static void
 add_using_namespace (vec<tree, va_gc> *&usings, tree target)
 {
+  /* Find if this using already exists.  */
+  tree old = NULL_TREE;
   if (usings)
-    for (unsigned ix = usings->length (); ix--;)
-      if ((*usings)[ix] == target)
-       return;
+    for (tree t : *usings)
+      if (USING_DECL_DECLS (t) == target)
+       {
+         old = t;
+         break;
+       }
 
+  tree decl = old;
+  if (!decl)
+    {
+      decl = build_lang_decl (USING_DECL, NULL_TREE, NULL_TREE);
+      USING_DECL_DECLS (decl) = target;
+    }
+
+  /* Update purviewness and exportedness in case that has changed.  */
   if (modules_p ())
     {
-      tree u = build_lang_decl (USING_DECL, NULL_TREE, NULL_TREE);
-      USING_DECL_DECLS (u) = target;
-      DECL_MODULE_EXPORT_P (u) = module_exporting_p ();
-      DECL_MODULE_PURVIEW_P (u) = module_purview_p ();
-      target = u;
+      if (module_purview_p ())
+       DECL_MODULE_PURVIEW_P (decl) = true;
+      if (module_exporting_p ())
+       DECL_MODULE_EXPORT_P (decl) = true;
     }
-  vec_safe_push (usings, target);
+
+  if (!old)
+    vec_safe_push (usings, decl);
 }
 
 /* Convenience overload for the above, taking the user as its first
index 9f9bd565004764c06fb366f74c53ded682c34fb5..8d1187f8cdae7015add294665e481195bca7c1b3 100644 (file)
@@ -16267,13 +16267,7 @@ cp_parser_import_declaration (cp_parser *parser, module_parse mp_state,
                      " must not be from header inclusion");
        }
 
-      auto mk = module_kind;
-      if (exporting)
-       module_kind |= MK_EXPORTING;
-
       import_module (mod, token->location, exporting, attrs, parse_in);
-
-      module_kind = mk;
     }
 }
 
index aee00bdc438824bd42f7ee0ec77037eb0f7659d3..7e03c19d2461e1de96b500faf3951cb503e026dd 100644 (file)
@@ -1,6 +1,8 @@
-// { dg-additional-options -fmodules }
+// { dg-additional-options "-fmodules -fdump-lang-module" }
 
 import M2;
 
 A::AT var1;
 AT var2;                       // { dg-error "AT" }
+
+// { dg-final { scan-lang-dump {Ignoring using-directives because module M1 is not visible} module } }
diff --git a/gcc/testsuite/g++.dg/modules/namespace-13_a.C b/gcc/testsuite/g++.dg/modules/namespace-13_a.C
new file mode 100644 (file)
index 0000000..09255fd
--- /dev/null
@@ -0,0 +1,16 @@
+// PR c++/121702
+// { dg-additional-options "-fmodules -fdump-lang-module" }
+// { dg-module-cmi a }
+
+export module a;
+export namespace a {
+  constexpr int f() { return 42; }
+}
+
+namespace x {}
+namespace y {
+  export using namespace x;
+}
+
+// { dg-final { scan-lang-dump {Using-directives 1} module } }
+// { dg-final { scan-lang-dump {Writing using-directive in '::y' for '::x'} module } }
diff --git a/gcc/testsuite/g++.dg/modules/namespace-13_b.C b/gcc/testsuite/g++.dg/modules/namespace-13_b.C
new file mode 100644 (file)
index 0000000..1b30961
--- /dev/null
@@ -0,0 +1,32 @@
+// PR c++/121702
+// { dg-additional-options "-fmodules -Wno-global-module -fdump-lang-module" }
+// { dg-module-cmi b }
+
+module;
+
+namespace gmf::blah {}
+namespace gmf::other {}
+
+export module b;
+export import a;
+// { dg-final { scan-lang-dump {Read using-directive in '::y' for '::x'} module } }
+
+export namespace b {
+  using namespace a;
+  using namespace gmf::blah;
+}
+namespace c {
+  using namespace gmf;
+  export using namespace gmf;
+  using namespace a;
+}
+
+// { dg-final { scan-lang-dump {Using-directives 3} module } }
+
+// { dg-final { scan-lang-dump {Writing using-directive in '::b' for '::a'} module } }
+// { dg-final { scan-lang-dump {Writing using-directive in '::b' for '::gmf::blah'} module } }
+// { dg-final { scan-lang-dump {Writing using-directive in '::c' for '::gmf'} module } }
+// { dg-final { scan-lang-dump-not {Writing using-directive in '::y' for '::x'} module } }
+
+// { dg-final { scan-lang-dump {Writing namespace:[0-9]* '::gmf::blah', public} module } }
+// { dg-final { scan-lang-dump-not {Writing namespace:[0-9]* '::gmf::other'} module } }
diff --git a/gcc/testsuite/g++.dg/modules/namespace-13_c.C b/gcc/testsuite/g++.dg/modules/namespace-13_c.C
new file mode 100644 (file)
index 0000000..d04ef37
--- /dev/null
@@ -0,0 +1,17 @@
+// PR c++/121702
+// { dg-additional-options "-fmodules" }
+
+import b;
+namespace gmf::blah {
+  constexpr int g() { return 123; }
+}
+namespace gmf::other {
+  constexpr int h() { return 99; }
+}
+namespace x {
+  constexpr int i() { return 5; }
+}
+static_assert(b::f() == 42);
+static_assert(b::g() == 123);
+static_assert(c::other::h() == 99);
+static_assert(y::i() == 5);