[RFC PATCH] testsuite: Add plugin to verify bits/std.cc exports
Jakub Jelinek
jakub@redhat.com
Thu Mar 19 14:14:40 GMT 2026
Hi!
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 on top of the uglification plugin patch,
but only dependent on it because of the plugin_test_list hunk,
it could be applied independently with a little effort.
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.
The tests currently FAIL:
FAIL: g++.dg/plugin/std-module-exports-c++20.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
FAIL: g++.dg/plugin/std-module-exports-c++23.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
FAIL: g++.dg/plugin/std-module-exports-c++26.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
and the details are:
sed -n '/^FAIL/,/^Executing on host/p' testsuite/g++/g++.log | grep 'FAIL\|error:.missing.using' | sed 's/^.*libstdc/libstdc/'
FAIL: g++.dg/plugin/std-module-exports-c++20.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/memory:159:3: error: missing using std::undeclare_reachable;
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/memory:167:1: error: missing using std::undeclare_no_pointers;
libstdc++-v3/include/memory:171:1: error: missing using std::get_pointer_safety;
libstdc++-v3/libsupc++/exception:101:40: error: missing using std::unexpected_handler;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
libstdc++-v3/include/memory:154:1: error: missing using std::declare_reachable;
libstdc++-v3/include/memory:150:12: error: missing using std::pointer_safety;
libstdc++-v3/include/memory:163:1: error: missing using std::declare_no_pointers;
FAIL: g++.dg/plugin/std-module-exports-c++23.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/bits/memoryfwd.h:86:12: error: missing using std::allocation_result;
libstdc++-v3/include/bits/formatfwd.h:190:20: error: missing using std::enable_nonlocking_formatter_optimization;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
FAIL: g++.dg/plugin/std-module-exports-c++26.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/type_traits:3566:25: error: missing using std::is_reflection_v;
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/type_traits:761:12: error: missing using std::is_reflection;
libstdc++-v3/include/ranges:934:29: error: missing using std::ranges::views::indices;
libstdc++-v3/include/bits/random.h:2346:29: error: missing using std::philox4x32;
libstdc++-v3/include/bits/random.h:2353:45: error: missing using std::philox4x64;
libstdc++-v3/include/bits/memoryfwd.h:86:12: error: missing using std::allocation_result;
libstdc++-v3/include/bits/formatfwd.h:190:20: error: missing using std::enable_nonlocking_formatter_optimization;
libstdc++-v3/include/type_traits:3923:27: error: missing using std::is_consteval_only_v;
libstdc++-v3/include/bits/random.h:2059:11: error: missing using std::philox_engine;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
libstdc++-v3/include/type_traits:3914:12: error: missing using std::is_consteval_only;
Now, for the C++20 (I know, bits/std.cc for C++20 is just an extension), all
the reported names are C++23 zombie.names, but only
{result_of{,_t},unexpected_handler} are C++20 zombie.names.
So dunno what way you want to handle it, either the plugin can whitelist
those for C++20, or we could export for C++20 only all but the 3 and
deprecate the 3 for C++20+, or export for C++20 except for the 3 and
whitelist in the plugin the 3.
allocation_result has been added in PR118030 P0401 r16-7986 and I'll post a
patch monetarily.
enable_nonlocking_formatter_optimization has been added in PR121790 P3235R3
r16-4351 and I have to wonder, though bits/formatfwd.h hunk already in
PR121790 P3107R5 r16-4350. I wonder why it shows up for C++23 though.
is_{reflection,consteval_only}{,_v} is my fault in r16-6808 and will post
a fix momentarily.
philox{_engine,4x32,4x64} is PR119794 P2075 r16-4062 and I'll post a patch
monetarily.
views::indices is P3060R3 r16-4511 and I'll post a patch monetarily.
With the 4 patches I'll post after this one the results are already
sed -n '/^FAIL/,/^Executing on host/p' testsuite/g++/g++.log | grep 'FAIL\|error:.missing.using' | sed 's/^.*libstdc/libstdc/'
FAIL: g++.dg/plugin/std-module-exports-c++20.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/memory:159:3: error: missing using std::undeclare_reachable;
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/memory:167:1: error: missing using std::undeclare_no_pointers;
libstdc++-v3/include/memory:171:1: error: missing using std::get_pointer_safety;
libstdc++-v3/libsupc++/exception:101:40: error: missing using std::unexpected_handler;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
libstdc++-v3/include/memory:154:1: error: missing using std::declare_reachable;
libstdc++-v3/include/memory:150:12: error: missing using std::pointer_safety;
libstdc++-v3/include/memory:163:1: error: missing using std::declare_no_pointers;
FAIL: g++.dg/plugin/std-module-exports-c++23.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/bits/formatfwd.h:190:20: error: missing using std::enable_nonlocking_formatter_optimization;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
FAIL: g++.dg/plugin/std-module-exports-c++26.C -fplugin=./std_module_exports_plugin.so (test for excess errors)
libstdc++-v3/include/type_traits:2958:11: error: missing using std::result_of_t;
libstdc++-v3/include/bits/formatfwd.h:190:20: error: missing using std::enable_nonlocking_formatter_optimization;
libstdc++-v3/include/type_traits:2725:12: error: missing using std::result_of;
2026-03-19 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.
--- gcc/testsuite/g++.dg/plugin/plugin.exp.jj 2026-03-18 16:48:34.395284889 +0100
+++ gcc/testsuite/g++.dg/plugin/plugin.exp 2026-03-19 13:18:33.788079106 +0100
@@ -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
--- gcc/testsuite/g++.dg/plugin/std_module_exports_plugin.cc.jj 2026-03-19 13:19:04.369565898 +0100
+++ gcc/testsuite/g++.dg/plugin/std_module_exports_plugin.cc 2026-03-19 13:09:21.876379950 +0100
@@ -0,0 +1,173 @@
+#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> **);
+
+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;
+
+ error_at (DECL_SOURCE_LOCATION (decl), "missing %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, "using 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;
+}
--- gcc/testsuite/g++.dg/plugin/std-module-exports-c++20.C.jj 2026-03-19 13:23:22.813228787 +0100
+++ gcc/testsuite/g++.dg/plugin/std-module-exports-c++20.C 2026-03-19 13:23:43.724877854 +0100
@@ -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 ()
+{
+}
--- gcc/testsuite/g++.dg/plugin/std-module-exports-c++23.C.jj 2026-03-19 13:23:25.737179717 +0100
+++ gcc/testsuite/g++.dg/plugin/std-module-exports-c++23.C 2026-03-19 13:23:53.534713228 +0100
@@ -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 ()
+{
+}
--- gcc/testsuite/g++.dg/plugin/std-module-exports-c++26.C.jj 2026-03-19 13:23:28.884126906 +0100
+++ gcc/testsuite/g++.dg/plugin/std-module-exports-c++26.C 2026-03-19 13:24:08.449462936 +0100
@@ -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 ()
+{
+}
Jakub
More information about the Libstdc++
mailing list