Skip to content

Fix potential unbounded file/type lookup #600

Description

@AntoineGautier

Latent on main, currently masked by eager type loading: dead-ended references warm typeStore
before cold lookup paths are ever exercised. The parse-skipping work for __ctrlFlow(enable=false)
(#601) removes that warming and reliably triggers RangeError: Maximum call stack size exceeded
while building Buildings.Templates.AirHandlersFans.Data.VAVMultiZone's parameter table
(schedule.test.ts, real-Buildings-library data). Root-caused with a cycle detector; this must be
fixed before or together with #601.

Root cause — four interacting defects

  1. Store._generatePaths() (parser.ts:138-150) blindly prefixes already-qualified names.
    Resolving Buildings.Templates.AirHandlersFans.Data.PartialAirHandler from
    basePath = ...Data.VAVMultiZone generates candidates like
    Buildings.Templates.AirHandlersFans.Data.Buildings.Templates.AirHandlersFans.Data.PartialAirHandler.
  2. getPathFromClassName()'s directory-trimming fallback (loader.ts:186-207) fuzzy-"succeeds" on
    those bogus candidates
    (the example above trims back to .../Data/package.json), so they load
    real files. File's within-path validation (parser.ts:1212-1218) doesn't catch it because
    within is a segment-prefix of the bogus name.
  3. getFile() (parser.ts:1234-1239) has no construction dedupe: loader() memoizes lookups
    (keyed by the requested className string — each bogus candidate is a fresh key), but
    new File(...) and all element constructors re-run on every call.
  4. ShortClass and Component resolve their type before registerPath (parser.ts:503 vs 512;
    829 vs 835), so duplicate punch-out never protects them (unlike LongClass, which registers at
    607 and punches out at 609). Every redundant File reconstruction re-runs a full candidate sweep
    per component/short class → more bogus candidates → more getFile calls. This closes the cycle:
    getFile ↔ pre-registration typeStore.get is mutual recursion with no terminator — genuinely
    unbounded, not just deep (a 16x larger V8 stack doesn't survive it).

Proposed fix (in order of leverage)

  1. Root-aware _generatePaths: if the first segment of path names a top-level loaded package
    (derivable from MODELICA_JSON_PATH roots / PACKAGE_LIST[0]), return [path] only.
  2. Dedupe File construction by resolved file identity (resolved json path or
    getClassNameFromJson): each real file constructed at most once per run. Makes the system robust
    to any future candidate-generation slop.
  3. Move registerPath (and annotation parsing) ahead of type resolution in ShortClass and
    Component
    — also required by Fix __ctrlFlow.enable annotation handling #601.
  4. Negative-cache the _load step in Store.get (a Set of candidate strings that failed to
    load; keep checking the store positively since it grows). Perf polish once 1–3 are in — it cannot
    bound the blow-up alone because the bogus keys are distinct.

Also add a permanent cheap getFile re-entrancy guard that throws with the active stack — turns any
regression here into a one-line diagnostic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions