java-topology/defects/ruby/patch/ruby-0001-class-include-super-chain-scan.patch

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;
}