Skip to content

Fix object literals silently corrupting data with interleaved field/method type annotations#265

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix/object-literal-interleaved-field-method-layout
Jul 20, 2026
Merged

Fix object literals silently corrupting data with interleaved field/method type annotations#265
ASDAlexander77 merged 1 commit into
mainfrom
fix/object-literal-interleaved-field-method-layout

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

  • An object literal's own internal storage always lays out as "all fields, then all methods" — mlirGenObjectLiteralFields/mlirGenObjectLiteralMethodPrototypes run as two separate passes over the literal's properties, by design (a method may reference sibling fields that must already be registered).
  • A type-literal annotation on the receiving var/let, however, preserves verbatim interleaved source declaration order.
  • When these orderings diverge — any annotation with 2+ fields and 2+ methods not grouped fields-first — method bodies get compiled against the WRONG byte offsets relative to the annotated variable's actual memory layout. A this.field write can silently land on a neighboring method's function-pointer slot instead of the intended field. No compile error, no crash — just silently corrupted data.
  • Minimal repro:
    var raw: { base: number; addBase(n: number): void; total: number; add(n: number): void; } = {
        base: 100.0,
        addBase(n: number) { this.base = this.base + n; },
        total: 0.0,
        add(n: number) { this.total = this.total + n; },
    };
    raw.add(3);
    print(raw.total); // printed 0 before this fix, should print 3

Fix

After all fields/methods/captures are processed but before method bodies are generated, if the receiver type's field order differs from the literal's own accumulated order, permute the literal's field list (and the parallel value list) to match it. Field access inside method bodies resolves by name against the storage type's live field list at codegen time, so this reordering alone is sufficient — no per-method index patching needed.

Test plan

  • Minimal repro and 7 bisection variants re-verified manually: all correct
  • New same-module regression test 00object_annotated_method_interleaved.ts (compile + JIT)
  • New cross-module regression test export/import_object_literal_structural_typed_interleaved.ts (compile + JIT), combining this fix with the recent decl-text printer fix (Fix cross-module multi-method vtable corruption via decl-text object-vs-tuple printing #263)
  • Full 738-test suite (734 existing + 4 new): 100% pass, no regressions

Known follow-up (not in scope here)

An extends-based variant of the cross-module scenario (interface Base {...} interface Accumulator extends Base {...}, cast in the importer) still fails to compile at all with a bare error: failed statement — confirmed unrelated to this bug (the flat, non-extends version with identical members now compiles and runs correctly). Looks related to a separately-documented discovery-pass issue; not investigated further here.

🤖 Generated with Claude Code

…ethod type annotations

An object literal's own storage always lays out as "all fields, then all
methods" (two separate codegen passes over the properties, by design - a
method may reference sibling fields that must already be registered). A
type-literal annotation on the receiving var/let, however, preserves
verbatim interleaved source declaration order. When these orderings
diverge - any annotation with 2+ fields and 2+ methods not grouped
field-first - method bodies get compiled against the WRONG byte offsets
for the annotated variable's actual memory layout: a `this.field` write
can silently land on a neighboring method's function-pointer slot instead,
corrupting data with no compile error and no crash.

Fix: after all fields/methods/captures are processed but before method
bodies are generated, if the receiver type's field order differs from the
literal's own accumulated order, permute the literal's field list (and
parallel value list) to match it. Field access inside method bodies
resolves by name against the storage type's live field list, so this
reordering alone is sufficient - no per-method index patching needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 8edfb2a into main Jul 20, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix/object-literal-interleaved-field-method-layout branch July 20, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant