30 lines
1.5 KiB
Diff
30 lines
1.5 KiB
Diff
# UNDF: UNDF-2026-000000242
|
||
Fixes rails-0002: ActiveSupport Callbacks — chain.index(callback) inside skip_callback filters.each loop.
|
||
|
||
--- a/activesupport/lib/active_support/callbacks.rb
|
||
+++ b/activesupport/lib/active_support/callbacks.rb
|
||
|
||
@@ DEFECT rails-0002: lines 794-803 in skip_callback
|
||
|
||
__update_callbacks(name) do |target, chain|
|
||
+ # FIX rails-0002: build position map once before filters loop
|
||
+ position_map = chain.each_with_index.each_with_object({}) { |(c, i), h| h[c] = i }
|
||
+
|
||
filters.each do |filter|
|
||
callback = chain.find { |c| c.matches?(type, filter) }
|
||
if callback && (options.key?(:if) || options.key?(:unless))
|
||
new_callback = callback.merge_conditional_options(chain, type: type, **options)
|
||
- chain.insert(chain.index(callback), new_callback) # O(C) scan — CWE-407
|
||
+ chain.insert(position_map[callback] || chain.size, new_callback) # O(1) — fixed
|
||
+ # update position_map for subsequent inserts in same filter pass
|
||
+ position_map.transform_values! { |i| i >= (position_map[callback] || 0) ? i + 1 : i }
|
||
+ position_map[new_callback] = position_map[callback] || 0
|
||
end
|
||
chain.delete(callback)
|
||
+ position_map.delete(callback)
|
||
end
|
||
end
|
||
|
||
# BEFORE: chain.index(callback) is O(C) CallbackChain scan inside filters.each across descendants
|
||
# AFTER: position_map[callback] is O(1) hash lookup; position_map updated incrementally
|
||
# Complexity: O(D×F×C²) → O(D×F×C) where D=descendants, F=filters, C=chain_length
|