1.6 KiB
1.6 KiB
UNDF: UNDF-2026-000000371
crystal-0003: compute_non_nilable_outside_single — O(N) includes? in O(A) ancestor loop
Severity: MEDIUM
Location
src/compiler/crystal/semantic/type_declaration_processor.cr:602- Called from:
compute_non_nilable_outsideat line 590-594, inside ancestor loop
Description
compute_non_nilable_outside_single builds a deduplicated list of non-nilable instance
variable names using Array#includes? — O(N) — for each variable from each ancestor type.
This makes compute_non_nilable_outside O(V × A) where V = instance var count, A = ancestor
chain depth. Called once per type during instance variable type declaration processing.
Root Cause
# type_declaration_processor.cr:598-606
private def compute_non_nilable_outside_single(owner, non_nilable_outside)
if vars = @instance_vars_outside[owner]?
non_nilable_outside ||= [] of String
vars.each do |name|
non_nilable_outside << name unless non_nilable_outside.includes?(name) # O(N)
end
end
non_nilable_outside
end
Fix
Return a Set(String) instead of Array(String) (or track seen in a Set alongside):
private def compute_non_nilable_outside_single(owner, non_nilable_outside)
if vars = @instance_vars_outside[owner]?
non_nilable_outside ||= Set(String).new
vars.each do |name|
non_nilable_outside.add(name) # O(1) set add, handles dedup
end
end
non_nilable_outside
end
Impact
MEDIUM — bounded by instance var count per class. Noticeable in deep class hierarchies with many instance variables declared outside initializers.