Found while building fixtures for #239 Phase B (PR #324). Pre-existing, not introduced by that PR, and it fails loudly — no silent miscompile.
What happens
reconstruct_component replaces each nested core module by locating its section header and writing a fresh [id=1][LEB128 size][module bytes] in its place. That is correct for a top-level ModuleSection, but Parser::parse_all also yields ModuleSection payloads for modules inside a nested ComponentSection. For those, the inner module's new (smaller) size is written while the enclosing component section's own LEB128 size field is copied through verbatim from the original bytes. The outer size is then wrong and everything after it misparses.
optimize_component catches this in its own post-pass validation and returns an error, so the output is never shipped:
Optimized component validation failed: core instance 0 has no export named `add` (at offset 0x8d)
Repro
Any component that defines a nested component containing a core module — e.g. the NESTED_COMPONENT fixture in loom-core/src/component_optimizer.rs (phase_b_reachability_gc_tests). Optimizing it fails as above, because Phase A shrinks both modules and the inner one is nested.
Why it matters
Nested components are how the Component Model expresses composition, so this is not an exotic shape. Today it means loom optimize simply refuses such components (CLI falls back with "Component optimization not available"), which is safe but is a capability gap rather than a deliberate decision.
Suggested fix
Track ComponentSection byte ranges the way module_ranges is already tracked for custom sections, and for a ModuleSection that falls inside one, leave the bytes verbatim (do not optimize or replace that module) — conservative, matches the existing "skip rather than risk" idiom. A full fix would re-emit the enclosing component section with a corrected size.
Note
#239 Phase B (PR #324) bails on nested components, so it cannot make this worse.
Refs #239
Found while building fixtures for #239 Phase B (PR #324). Pre-existing, not introduced by that PR, and it fails loudly — no silent miscompile.
What happens
reconstruct_componentreplaces each nested core module by locating its section header and writing a fresh[id=1][LEB128 size][module bytes]in its place. That is correct for a top-levelModuleSection, butParser::parse_allalso yieldsModuleSectionpayloads for modules inside a nestedComponentSection. For those, the inner module's new (smaller) size is written while the enclosing component section's own LEB128 size field is copied through verbatim from the original bytes. The outer size is then wrong and everything after it misparses.optimize_componentcatches this in its own post-pass validation and returns an error, so the output is never shipped:Repro
Any component that defines a nested component containing a core module — e.g. the
NESTED_COMPONENTfixture inloom-core/src/component_optimizer.rs(phase_b_reachability_gc_tests). Optimizing it fails as above, because Phase A shrinks both modules and the inner one is nested.Why it matters
Nested components are how the Component Model expresses composition, so this is not an exotic shape. Today it means
loom optimizesimply refuses such components (CLI falls back with "Component optimization not available"), which is safe but is a capability gap rather than a deliberate decision.Suggested fix
Track
ComponentSectionbyte ranges the waymodule_rangesis already tracked for custom sections, and for aModuleSectionthat falls inside one, leave the bytes verbatim (do not optimize or replace that module) — conservative, matches the existing "skip rather than risk" idiom. A full fix would re-emit the enclosing component section with a corrected size.Note
#239 Phase B (PR #324) bails on nested components, so it cannot make this worse.
Refs #239