]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
testsuite: Add plugin to verify bits/std.cc exports
authorJakub Jelinek <jakub@redhat.com>
Wed, 29 Apr 2026 05:55:02 +0000 (07:55 +0200)
committerJakub Jelinek <jakub@gcc.gnu.org>
Wed, 29 Apr 2026 05:55:02 +0000 (07:55 +0200)
The following patch adds another g++.dg/plugin/ testsuite plugin,
this time to verify whether some std.cc exports aren't mistakenly
omitted.

The patch is a reworked version of the
https://gcc.gnu.org/pipermail/libstdc++/2025-August/thread.html#62859
proof of concept.  That version just dumped out everything it saw
in the std namespace and its child namespaces (excluding non-inline
subnamespaces with identifiers starting with underscore) and then I've
used sed&grep to form a list of omissions.

This patch keeps the previous walk of std namespace and namespaces children
of it, but it only reports (in this version using error_at instead of inform
previously) what it finds if it isn't exported from the module and is not
deprecated (deprecated attribute is used usually for zombie.names in the
standard).

I've been strugling with the detection of what is and what isn't exported,
had to try several different methods.
What is DECL_MODULE_EXPORT_P is ignored, but that is not set on everything
actually exported.  In other cases there is OVL_EXPORT_P flag on OVERLOAD
(but OVL_HIDDEN_P at the start doesn't have it).  Another case are inline
namespaces, e.g. for std::filesystem::__cxx11::begin or
std::filesystem::__cxx11::directory_iterator.  In the latter case, there
is no sign of the above flags in __cxx11 binding entry, but there is a
USING_DECL with the same name directly in std::filesystem.  And for begin
there is OVERLOAD with OVL_EXPORT_P in std::filesystem but not in
std::filesystem::__cxx11.

2026-04-29  Jakub Jelinek  <jakub@redhat.com>

* g++.dg/plugin/plugin.exp: Set PLUGIN_DEFAULT_REPO.  Add
set*module*exports* to plugin_test_list.  Remove *.gcm files
at the start and end.
* g++.dg/plugin/std_module_exports_plugin.cc: New file.
* g++.dg/plugin/std-module-exports-c++20.C: New test.
* g++.dg/plugin/std-module-exports-c++23.C: New test.
* g++.dg/plugin/std-module-exports-c++26.C: New test.

Reviewed-by: Andrew Pinski <andrew.pinski@oss.qualcomm.com>
gcc/testsuite/g++.dg/plugin/plugin.exp
gcc/testsuite/g++.dg/plugin/std-module-exports-c++20.C [new file with mode: 0644]
gcc/testsuite/g++.dg/plugin/std-module-exports-c++23.C [new file with mode: 0644]
gcc/testsuite/g++.dg/plugin/std-module-exports-c++26.C [new file with mode: 0644]
gcc/testsuite/g++.dg/plugin/std_module_exports_plugin.cc [new file with mode: 0644]

index 26df19b1b5a8b2053b00ab6b3cf0f125dd8cbbc1..ab04ce108346048b694b45316e2d5a443a612986 100644 (file)
@@ -51,6 +51,8 @@ if ![gcc_parallel_test_run_p plugin] {
 }
 gcc_parallel_test_enable 0
 
+set PLUGIN_DEFAULT_REPO "gcm.cache"
+
 # Specify the plugin source file and the associated test files in a list.
 # plugin_test_list={ {plugin1 test1 test2 ...} {plugin2 test1 ...} ... }
 set plugin_test_list [list \
@@ -87,9 +89,17 @@ set plugin_test_list [list \
          uglification-c++20.C \
          uglification-c++23.C \
          uglification-c++26.C } \
+    { std_module_exports_plugin.cc \
+         std-module-exports-c++20.C \
+         std-module-exports-c++23.C \
+         std-module-exports-c++26.C } \
     { comment_plugin.cc comments-1.C } \
 ]
 
+foreach file [find $PLUGIN_DEFAULT_REPO *.gcm] {
+    file_on_host delete $file
+}
+
 foreach plugin_test $plugin_test_list {
     # Replace each source file with its full-path name
     for {set i 0} {$i < [llength $plugin_test]} {incr i} {
@@ -105,4 +115,8 @@ foreach plugin_test $plugin_test_list {
     plugin-test-execute $plugin_src $plugin_input_tests
 }
 
+foreach file [find $PLUGIN_DEFAULT_REPO *.gcm] {
+    file_on_host delete $file
+}
+
 gcc_parallel_test_enable 1
diff --git a/gcc/testsuite/g++.dg/plugin/std-module-exports-c++20.C b/gcc/testsuite/g++.dg/plugin/std-module-exports-c++20.C
new file mode 100644 (file)
index 0000000..0640eee
--- /dev/null
@@ -0,0 +1,11 @@
+// Test bits/std.cc exports everything it should.
+// This is done using a plugin to walk std namespace and
+// its child namespaces, looking for decls with non-uglified
+// names which aren't deprecated and are not exported.
+// { dg-do link }
+// { dg-options "-O0 -std=c++20 -fmodules -x c++-system-module bits/std.cc -x none" }
+
+int
+main ()
+{
+}
diff --git a/gcc/testsuite/g++.dg/plugin/std-module-exports-c++23.C b/gcc/testsuite/g++.dg/plugin/std-module-exports-c++23.C
new file mode 100644 (file)
index 0000000..d5745f1
--- /dev/null
@@ -0,0 +1,11 @@
+// Test bits/std.cc exports everything it should.
+// This is done using a plugin to walk std namespace and
+// its child namespaces, looking for decls with non-uglified
+// names which aren't deprecated and are not exported.
+// { dg-do link }
+// { dg-options "-O0 -std=c++23 -fmodules -x c++-system-module bits/std.cc -x none" }
+
+int
+main ()
+{
+}
diff --git a/gcc/testsuite/g++.dg/plugin/std-module-exports-c++26.C b/gcc/testsuite/g++.dg/plugin/std-module-exports-c++26.C
new file mode 100644 (file)
index 0000000..7e8f5e2
--- /dev/null
@@ -0,0 +1,11 @@
+// Test bits/std.cc exports everything it should.
+// This is done using a plugin to walk std namespace and
+// its child namespaces, looking for decls with non-uglified
+// names which aren't deprecated and are not exported.
+// { dg-do link }
+// { dg-options "-O0 -std=c++26 -fmodules -freflection -fcontracts -x c++-system-module bits/std.cc -x none" }
+
+int
+main ()
+{
+}
diff --git a/gcc/testsuite/g++.dg/plugin/std_module_exports_plugin.cc b/gcc/testsuite/g++.dg/plugin/std_module_exports_plugin.cc
new file mode 100644 (file)
index 0000000..e81eb6d
--- /dev/null
@@ -0,0 +1,219 @@
+#include "gcc-plugin.h"
+#include <stdlib.h>
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "intl.h"
+#include "cp/cp-tree.h"
+#include "cp/name-lookup.h"
+#include "diagnostic.h"
+#include "stringpool.h"
+#include "attribs.h"
+
+int plugin_is_GPL_compatible;
+
+void plugin_dump_ns (tree, char *, hash_set<tree> **);
+
+enum class whitelist_std {
+  cxx20 = 1 << 0,
+  cxx23 = 1 << 1,
+  cxx26 = 1 << 2,
+  all = (1 << 3) - 1
+};
+struct {
+  const char *name;
+  whitelist_std enabled_in;
+} whitelist[] = {
+  // [zombie.names] in C++20-C++26, supported as an extension.
+  { "std::result_of", whitelist_std::all },
+  { "std::result_of_t", whitelist_std::all },
+  // [zombie.names] in C++20-C++26, supported in C++20 as an extension.
+  { "std::unexpected_handler", whitelist_std::cxx20 },
+  // [zombie.names] in C++23-C++26, not exported in C++20.
+  { "std::declare_no_pointers", whitelist_std::cxx20 },
+  { "std::declare_reachable", whitelist_std::cxx20 },
+  { "std::get_pointer_safety", whitelist_std::cxx20 },
+  { "std::pointer_safety", whitelist_std::cxx20 },
+  { "std::undeclare_no_pointers", whitelist_std::cxx20 },
+  { "std::undeclare_reachable", whitelist_std::cxx20 }
+};
+
+void
+plugin_dump_decl (tree decl, char *scope, hash_set<tree> **exported_usings)
+{
+  if (VAR_P (decl) && DECL_ARTIFICIAL (decl))
+    return;
+
+  tree name = DECL_NAME (decl);
+  if (!name)
+    return;
+
+  if (IDENTIFIER_ANON_P (name))
+    return;
+
+  if (TREE_CODE (decl) == CONST_DECL)
+    return;
+
+  if (TREE_CODE (decl) == NAMESPACE_DECL && DECL_NAMESPACE_INLINE_P (decl))
+    {
+      plugin_dump_ns (decl, scope, exported_usings);
+      return;
+    }
+
+  if (IDENTIFIER_POINTER (name)[0] == '_'
+      || strchr (IDENTIFIER_POINTER (name), ' '))
+    return;
+
+  if (TREE_CODE (decl) == NAMESPACE_DECL)
+    {
+      char *p = strchr (scope, '\0');
+      strcpy (p, IDENTIFIER_POINTER (name));
+      strcat (p, "::");
+      plugin_dump_ns (decl, scope, exported_usings);
+      *p = '\0';
+      return;
+    }
+
+  if (DECL_MODULE_EXPORT_P (decl))
+    return;
+  if (TREE_DEPRECATED (decl)
+      || lookup_attribute ("deprecated", DECL_ATTRIBUTES (decl)))
+    return;
+  if (TREE_CODE (decl) == TEMPLATE_DECL
+      && DECL_TEMPLATE_RESULT (decl)
+      && (TREE_DEPRECATED (DECL_TEMPLATE_RESULT (decl))
+         || lookup_attribute ("deprecated",
+                              DECL_ATTRIBUTES (DECL_TEMPLATE_RESULT (decl)))))
+    return;
+  if (*exported_usings
+      && (*exported_usings)->contains (DECL_NAME (decl)))
+    return;
+
+  size_t scope_len = strlen (scope);
+  whitelist_std this_std;
+  if (cxx_dialect == cxx20)
+    this_std = whitelist_std::cxx20;
+  else if (cxx_dialect == cxx23)
+    this_std = whitelist_std::cxx23;
+  else if (cxx_dialect == cxx26)
+    this_std = whitelist_std::cxx26;
+  for (int i = 0; i < ARRAY_SIZE (whitelist); ++i)
+    if (strncmp (whitelist[i].name, scope, scope_len) == 0
+       && strcmp (whitelist[i].name + scope_len,
+                  IDENTIFIER_POINTER (name)) == 0)
+      {
+       if (((int) whitelist[i].enabled_in & (int) this_std) != 0)
+         {
+           inform (DECL_SOURCE_LOCATION (decl),
+                   "missing using %s%D; whitelisted", scope, name);
+           return;
+         }
+       break;
+      }
+
+  error_at (DECL_SOURCE_LOCATION (decl), "missing using %s%D;", scope, name);
+}
+
+void
+plugin_dump_binding (tree binding, char *scope,
+                    hash_set<tree> **exported_usings)
+{
+  tree value = NULL_TREE;
+
+  if (TREE_CODE (binding) == OVERLOAD)
+    {
+      tree ovl = ovl_skip_hidden (binding);
+      if (ovl && TREE_CODE (ovl) == OVERLOAD && OVL_EXPORT_P (ovl))
+       return;
+    }
+
+  if (STAT_HACK_P (binding))
+    {
+      if (!STAT_TYPE_HIDDEN_P (binding)
+          && STAT_TYPE (binding))
+        return plugin_dump_decl (STAT_TYPE (binding), scope, exported_usings);
+      else if (!STAT_DECL_HIDDEN_P (binding))
+        value = STAT_DECL (binding);
+    }
+  else
+    value = binding;
+
+  value = ovl_skip_hidden (value);
+  if (value)
+    {
+      value = OVL_FIRST (value);
+      return plugin_dump_decl (value, scope, exported_usings);
+    }
+}
+
+void
+plugin_dump_ns (tree ns, char *scope, hash_set<tree> **exported_usings)
+{
+  using itert = hash_table<named_decl_hash>::iterator;
+  itert end (DECL_NAMESPACE_BINDINGS (ns)->end ());
+  hash_set<tree> *my_exported_usings = NULL;
+  if (DECL_NAMESPACE_INLINE_P (ns))
+    {
+      if (*exported_usings == NULL)
+       {
+         *exported_usings = new hash_set<tree>;
+         tree parent = DECL_CONTEXT (ns);
+         itert pend (DECL_NAMESPACE_BINDINGS (parent)->end ());
+         for (itert iter (DECL_NAMESPACE_BINDINGS (parent)->begin ());
+              iter != pend; ++iter)
+           {
+             tree b = *iter;
+             if (TREE_CODE (b) == USING_DECL
+                 && DECL_MODULE_EXPORT_P (b)
+                 && DECL_NAME (b))
+               (*exported_usings)->add (DECL_NAME (b));
+             else if (TREE_CODE (b) == OVERLOAD)
+               {
+                 tree ovl = ovl_skip_hidden (b);
+                 if (ovl && TREE_CODE (ovl) == OVERLOAD && OVL_EXPORT_P (ovl))
+                   {
+                     b = OVL_FIRST (ovl);
+                     if (DECL_NAME (b))
+                       (*exported_usings)->add (DECL_NAME (b));
+                   }
+               }
+           }
+       }
+    }
+  else
+    exported_usings = &my_exported_usings;
+  for (itert iter (DECL_NAMESPACE_BINDINGS (ns)->begin ());
+       iter != end; ++iter)
+    {
+      tree b = *iter;
+      gcc_assert (TREE_CODE (b) != BINDING_VECTOR);
+      plugin_dump_binding (b, scope, exported_usings);
+    }
+  delete my_exported_usings;
+}
+
+void
+plugin_finish_unit (void *, void *)
+{
+  if (!main_input_filename
+      || strstr (main_input_filename, "/std.cc") == NULL)
+    return;
+
+  char buf[4096];
+  strcpy (buf, "std::");
+  hash_set<tree> *exported_usings = NULL;
+  plugin_dump_ns (std_node, buf, &exported_usings);
+  delete exported_usings;
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+             struct plugin_gcc_version *version)
+{
+  const char *plugin_name = plugin_info->base_name;
+
+  register_callback (plugin_name, PLUGIN_FINISH_UNIT,
+                     plugin_finish_unit, NULL);
+  return 0;
+}