object_class_foreach() initializes every registered class before
applying its implements_type filter. A query for one ordinary class
hierarchy can therefore initialize classes that cannot match it.
Resolve an already registered filter type without loading modules.
For a non-interface filter, use TypeImpl ancestry to reject unrelated
types before invoking type_initialize(). Keep the existing abstract
class check and dynamic cast for the remaining candidates.
Interface filters retain the original initialization and cast path,
including inherited interfaces and ambiguous casts. Unknown filters
and unfiltered enumeration also retain their existing behavior. No
registered type is removed, and later registrations are considered
by subsequent walks of the type table.
This defers unrelated class initialization rather than changing the
set of classes returned by a filtered enumeration. Add a unit test
covering initialization counts, abstract classes, interface matching,
late registration and subsequent direct instantiation.
---
qom/object.c | 17 +++-
tests/unit/check-qom-enumeration.c | 138 +++++++++++++++++++++++++++++
tests/unit/meson.build | 1 +
3 files changed, 155 insertions(+), 1 deletion(-)
create mode 100644 tests/unit/check-qom-enumeration.c
diff --git a/qom/object.c b/qom/object.c
index 96498f3d00..abb3c6d23f 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1203,6 +1203,7 @@ typedef struct OCFData
const char *implements_type;
bool include_abstract;
void *opaque;
+ TypeImpl *class_filter;
} OCFData;
static void object_class_foreach_tramp(gpointer key, gpointer value,
@@ -1212,6 +1213,11 @@ static void object_class_foreach_tramp(gpointer key,
gpointer value,
TypeImpl *type = value;
ObjectClass *k;
+ /* Class ancestry is available before class initialization. */
+ if (data->class_filter && !type_is_ancestor(type, data->class_filter)) {
+ return;
+ }
+
type_initialize(type);
k = type->class;
@@ -1231,7 +1237,16 @@ void object_class_foreach(void (*fn)(ObjectClass *klass,
void *opaque),
const char *implements_type, bool include_abstract,
void *opaque)
{
- OCFData data = { fn, implements_type, include_abstract, opaque };
+ TypeImpl *filter = type_get_by_name_noload(implements_type);
+ OCFData data = {
+ .fn = fn,
+ .implements_type = implements_type,
+ .include_abstract = include_abstract,
+ .opaque = opaque,
+ /* Interface casts still require initialization and ambiguity checks.
*/
+ .class_filter = filter && !type_is_ancestor(filter, type_interface)
+ ? filter : NULL,
+ };
enumerating_types = true;
g_hash_table_foreach(type_table, object_class_foreach_tramp, &data);
diff --git a/tests/unit/check-qom-enumeration.c
b/tests/unit/check-qom-enumeration.c
new file mode 100644
index 0000000000..0482a1c4a7
--- /dev/null
+++ b/tests/unit/check-qom-enumeration.c
@@ -0,0 +1,138 @@
+/*
+ * Check filtered QOM enumeration and deferred class initialization.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qom/object.h"
+#include "qemu/module.h"
+
+static unsigned initialized[4];
+
+static void count_init(ObjectClass *klass, const void *opaque)
+{
+ unsigned index = GPOINTER_TO_UINT(opaque);
+
+ initialized[index]++;
+}
+
+static const TypeInfo types[] = {
+ {
+ .name = "enum-base", .parent = TYPE_OBJECT, .abstract = true,
+ .class_init = count_init, .class_data = GUINT_TO_POINTER(0),
+ }, {
+ .name = "enum-child", .parent = "enum-base",
+ .class_init = count_init, .class_data = GUINT_TO_POINTER(1),
+ }, {
+ .name = "enum-grandchild", .parent = "enum-child",
+ }, {
+ .name = "enum-unrelated", .parent = TYPE_OBJECT,
+ .class_init = count_init, .class_data = GUINT_TO_POINTER(2),
+ }, {
+ /* Zero instance size makes this interface abstract. */
+ .name = "enum-if", .parent = TYPE_INTERFACE,
+ .class_size = sizeof(InterfaceClass),
+ .class_init = count_init, .class_data = GUINT_TO_POINTER(3),
+ }, {
+ .name = "enum-if-a", .parent = "enum-if",
+ }, {
+ .name = "enum-if-b", .parent = "enum-if",
+ }, {
+ .name = "enum-impl", .parent = "enum-child",
+ .interfaces = (InterfaceInfo[]) { { "enum-if-a" }, { } },
+ }, {
+ .name = "enum-impl-child", .parent = "enum-impl",
+ }, {
+ .name = "enum-ambiguous", .parent = TYPE_OBJECT,
+ .interfaces = (InterfaceInfo[]) {
+ { "enum-if-a" }, { "enum-if-b" }, { }
+ },
+ },
+};
+
+static bool has_class(GSList *list, const char *name)
+{
+ for (; list; list = list->next) {
+ if (!strcmp(object_class_get_name(list->data), name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void enumeration_test(void)
+{
+ GSList *list;
+ static const TypeInfo late = {
+ .name = "enum-late", .parent = "enum-child",
+ };
+
+ /* Run before looking up or instantiating any of the test classes. */
+ list = object_class_get_list("enum-base", false);
+ g_assert_cmpuint(g_slist_length(list), ==, 4);
+ g_assert_false(has_class(list, "enum-base"));
+ g_assert_true(has_class(list, "enum-child"));
+ g_assert_true(has_class(list, "enum-grandchild"));
+ g_assert_true(has_class(list, "enum-impl"));
+ g_assert_true(has_class(list, "enum-impl-child"));
+ g_assert_cmpuint(initialized[0], ==, 1);
+ g_assert_cmpuint(initialized[1], ==, 1);
+ g_assert_cmpuint(initialized[2], ==, 0);
+ g_assert_cmpuint(initialized[3], ==, 1);
+ g_slist_free(list);
+
+ list = object_class_get_list("enum-base", true);
+ g_assert_cmpuint(g_slist_length(list), ==, 5);
+ g_assert_true(has_class(list, "enum-base"));
+ g_assert_cmpuint(initialized[2], ==, 0);
+ g_slist_free(list);
+
+ list = object_class_get_list("enum-if", false);
+ g_assert_cmpuint(g_slist_length(list), ==, 2);
+ g_assert_true(has_class(list, "enum-impl"));
+ g_assert_true(has_class(list, "enum-impl-child"));
+ g_assert_false(has_class(list, "enum-ambiguous"));
+ g_assert_false(has_class(list, "enum-if"));
+ g_assert_cmpuint(initialized[2], ==, 1);
+ g_slist_free(list);
+
+ list = object_class_get_list("enum-if", true);
+ g_assert_cmpuint(g_slist_length(list), ==, 5);
+ g_assert_true(has_class(list, "enum-if"));
+ g_assert_true(has_class(list, "enum-if-a"));
+ g_assert_true(has_class(list, "enum-if-b"));
+ g_slist_free(list);
+
+ list = object_class_get_list("enum-no-such-type", true);
+ g_assert_null(list);
+ list = object_class_get_list(NULL, true);
+ for (unsigned i = 0; i < G_N_ELEMENTS(types); i++) {
+ g_assert_true(has_class(list, types[i].name));
+ }
+ for (unsigned i = 0; i < G_N_ELEMENTS(initialized); i++) {
+ g_assert_cmpuint(initialized[i], ==, 1);
+ }
+ g_slist_free(list);
+
+ type_register_static(&late);
+ list = object_class_get_list("enum-base", false);
+ g_assert_cmpuint(g_slist_length(list), ==, 5);
+ g_assert_true(has_class(list, "enum-late"));
+ g_slist_free(list);
+ Object *obj = object_new("enum-unrelated");
+
+ g_assert_nonnull(obj);
+ object_unref(obj);
+ obj = object_new("enum-late");
+ g_assert_nonnull(object_dynamic_cast(obj, "enum-base"));
+ object_unref(obj);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ module_call_init(MODULE_INIT_QOM);
+ type_register_static_array(types, G_N_ELEMENTS(types));
+ g_test_add_func("/qom/enumeration/lazy-and-compatible", enumeration_test);
+ return g_test_run();
+}
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 37b561d4a7..b8c4dfd753 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -42,6 +42,7 @@ tests = {
'test-bitcnt': [],
'test-qgraph': ['../qtest/libqos/qgraph.c'],
'check-qom-interface': [qom],
+ 'check-qom-enumeration': [qom],
'check-qom-proplist': [qom],
'test-qemu-opts': [],
'test-keyval': [testqapi],
--
2.47.0.windows.1