85 lines
3.5 KiB
Diff
85 lines
3.5 KiB
Diff
# UNDF: UNDF-2026-000001076
|
|
# UNDF: (leave blank)
|
|
# CWE-407: modulegroups.c _lib_modulegroups_test_visible O(M*G*P) per module visibility check
|
|
#
|
|
# In src/libs/modulegroups.c, _lib_modulegroups_update_iop_visibility() iterates
|
|
# all IOP modules (length M) to determine whether each should be shown or hidden.
|
|
# For the DT_MODULEGROUP_NONE case, it calls _lib_modulegroups_test_visible() for
|
|
# each module. That function iterates all module groups (G) and for each group
|
|
# calls g_list_find_custom(gr->modules, module_op, _iop_compare) — a linear scan
|
|
# of the group's module list (length P).
|
|
#
|
|
# Total cost per UI refresh: O(M * G * P)
|
|
#
|
|
# Typical values: M=80 iop modules, G=8 groups, P=10 modules/group = 6,400
|
|
# g_strcmp0 calls per update. With the search text entry callback wired directly
|
|
# to _lib_modulegroups_update_iop_visibility, every keystroke triggers this.
|
|
# A user typing a 10-character search string incurs 64,000 string comparisons
|
|
# instead of 80.
|
|
#
|
|
# Fix: build a GHashTable (keyed on module op-name string) that covers all
|
|
# module names across all groups. Populate it once, then test_visible becomes
|
|
# a single g_hash_table_contains call — O(1).
|
|
# Invalidate/rebuild the hash table whenever d->groups is updated.
|
|
#
|
|
# The hash table can be stored in dt_lib_modulegroups_t and rebuilt in
|
|
# _lib_modulegroups_update (called whenever groups are loaded/saved).
|
|
#
|
|
# Severity: MEDIUM — 80x per-module savings; triggered on every text search
|
|
# keystroke, module group switch, and image load in the darkroom panel.
|
|
# Overhead ratio: ~80x at M=80 modules, G=8 groups, P=10 per group.
|
|
#
|
|
--- a/src/libs/modulegroups.c
|
|
+++ b/src/libs/modulegroups.c
|
|
@@ -107,6 +107,8 @@ typedef struct dt_lib_modulegroups_t
|
|
GList *groups;
|
|
GList *edit_groups;
|
|
+ /* Flat hash set of all module op-names visible in any group (for test_visible).
|
|
+ * Rebuilt whenever d->groups changes. */
|
|
+ GHashTable *visible_modules_set;
|
|
int current;
|
|
gboolean show_search;
|
|
|
|
@@ -277,12 +279,20 @@ static gboolean _lib_modulegroups_test_visible(dt_lib_module_t *self, gchar *mo
|
|
{
|
|
dt_lib_modulegroups_t *d = self->data;
|
|
- for(const GList *l = d->groups; l; l = g_list_next(l))
|
|
- {
|
|
- dt_lib_modulegroups_group_t *gr = l->data;
|
|
- if(g_list_find_custom(gr->modules, module, _iop_compare) != NULL)
|
|
- {
|
|
- return TRUE;
|
|
- }
|
|
- }
|
|
- return FALSE;
|
|
+ if(d->visible_modules_set)
|
|
+ return g_hash_table_contains(d->visible_modules_set, module);
|
|
+ /* Fallback to linear scan if hash not yet built (should not happen). */
|
|
+ for(const GList *l = d->groups; l; l = g_list_next(l))
|
|
+ {
|
|
+ dt_lib_modulegroups_group_t *gr = l->data;
|
|
+ if(g_list_find_custom(gr->modules, module, _iop_compare) != NULL)
|
|
+ return TRUE;
|
|
+ }
|
|
+ return FALSE;
|
|
}
|
|
|
|
+/* Rebuild the visible-modules hash set from d->groups.
|
|
+ * Call whenever groups are loaded, saved, or modified. */
|
|
+static void _lib_modulegroups_rebuild_visible_set(dt_lib_modulegroups_t *d)
|
|
+{
|
|
+ if(d->visible_modules_set)
|
|
+ g_hash_table_destroy(d->visible_modules_set);
|
|
+ d->visible_modules_set = g_hash_table_new(g_str_hash, g_str_equal);
|
|
+ for(const GList *l = d->groups; l; l = g_list_next(l))
|
|
+ {
|
|
+ dt_lib_modulegroups_group_t *gr = l->data;
|
|
+ for(const GList *m = gr->modules; m; m = g_list_next(m))
|
|
+ g_hash_table_add(d->visible_modules_set, m->data);
|
|
+ }
|
|
+}
|
|
+
|
|
/* Call _lib_modulegroups_rebuild_visible_set after every d->groups assignment. */
|
|
@@ -1458,2 +1472,3 @@ static void _lib_modulegroups_update(dt_lib_module_t *self, ...)
|
|
d->groups = res;
|
|
+ _lib_modulegroups_rebuild_visible_set(d);
|