java-topology/defects/crystal/patch/crystal-0003-non-nilable-outside.md

1.6 KiB
Raw Permalink Blame History

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_outside at 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.