71 lines
3.3 KiB
Diff
71 lines
3.3 KiB
Diff
# 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;
|
|
}
|