# UNDF: UNDF-2026-000000265 # ruby-0001: class.c do_include_modules_at super chain linear scan O(M*S) # # In class.c, do_include_modules_at() includes modules into a class by # iterating over each module to include (outer while loop, M modules) # and for each, scanning the entire super chain (inner for loop, S entries) # to check if the module's method table pointer is already present. # # The check is RCLASS_M_TBL(p) == RCLASS_M_TBL(module) — pointer equality # along a linked list. Total cost: O(M * S) where M = modules being # included (from the module's ancestor chain) and S = length of the # target class's super chain. # # In Rails applications with many concerns (ActiveRecord::Base typically # includes 50+ modules), S can be 50-100 and M can be 10-20 per include # call. Repeated include calls compound the problem as S grows. # # Fix: Build an st_table (hash set) of method table pointers already in # the super chain before the outer loop. Check membership via st_lookup # instead of walking the chain. Update the set as new iclasses are added. # # Severity: MEDIUM — affects Ruby applications with deep module hierarchies # (Rails, Hanami, dry-rb). At M=20, S=100: 2000 pointer comparisons vs # 20 hash lookups. # --- a/class.c +++ b/class.c @@ -1792,6 +1792,16 @@ static int do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super, bool check_cyclic) { VALUE p, iclass, origin_stack = 0; + st_table *included_mtbls = NULL; + + /* Build hash set of method table pointers already in super chain */ + included_mtbls = st_init_numtable(); + for (p = RCLASS_SUPER(klass); p; p = RCLASS_SUPER(p)) { + if (BUILTIN_TYPE(p) == T_ICLASS) { + struct rb_id_table *tbl = RCLASS_M_TBL(p); + if (tbl) st_insert(included_mtbls, (st_data_t)tbl, 1); + } + } int method_changed = 0; long origin_len; VALUE klass_origin = RCLASS_ORIGIN(klass); @@ -1816,7 +1826,9 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super for (p = RCLASS_SUPER(klass); p; p = RCLASS_SUPER(p)) { int type = BUILTIN_TYPE(p); if (type == T_ICLASS) { - if (RCLASS_M_TBL(p) == RCLASS_M_TBL(module)) { + struct rb_id_table *mod_tbl = RCLASS_M_TBL(module); + st_data_t tmp; + if (mod_tbl && st_lookup(included_mtbls, (st_data_t)mod_tbl, &tmp)) { if (!superclass_seen && c_seen) { c = p; /* move insertion point */ } @@ -1856,6 +1868,10 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super // setup T_ICLASS for the include/prepend module iclass = rb_include_class_new(module, super_class); c = rb_class_set_super(c, iclass); + /* Update hash set with newly added iclass */ + struct rb_id_table *new_tbl = RCLASS_M_TBL(iclass); + if (new_tbl) st_insert(included_mtbls, (st_data_t)new_tbl, 1); + RCLASS_SET_INCLUDER(iclass, klass); @@ -1889,6 +1905,7 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super module = RCLASS_SUPER(module); } + if (included_mtbls) st_free_table(included_mtbls); return method_changed; }