Skip to content

Commit be2c962

Browse files
Fix cross-module interface field/method vtable index mismatch
Interface vtable slot numbers were assigned by two inconsistent algorithms: declaration-time assignment used raw interleaved declaration order, while cast-time assignment (getVirtualTable) always puts methods before fields. Whichever module performs an actual cast keeps the correct methods-first layout; a module that only reads an already-typed interface value it imported never runs the cast-time algorithm and keeps the stale declaration-order index, so a field read could land on a method's vtable slot (a function pointer misread as a byte offset) and crash the JIT with 0xC0000005. Add InterfaceInfo::assignCanonicalVirtualIndexes(), a pure function of the interface's own declaration, computed once when the declaration finishes resolving so all modules that re-parse it agree. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent b770463 commit be2c962

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,38 @@ struct InterfaceInfo
539539
return offset + methods.size() + fields.size();
540540
}
541541

542+
// vtable slot numbers must be a pure function of the interface's own declaration
543+
// (extends, then own methods in order, then own fields in order) - NOT of whichever
544+
// object happens to be cast to it first. getVirtualTable() re-derives the same
545+
// methods-then-fields order per-cast (needed to mark per-object optional members
546+
// missing), but a module that only reads an already-typed interface value - without
547+
// ever casting an object to it itself (e.g. importing an already-boxed global from
548+
// another compilation unit) - never runs getVirtualTable() at all. Without this
549+
// eagerly-computed, cast-independent pass, such a module falls back on the
550+
// interleaved declaration-order index assigned at member-registration time
551+
// (mlirGenInterfaceAddFieldMember / addInterfaceMethod), which disagrees with the
552+
// methods-first layout whenever a field is declared before a method in source order -
553+
// reading through the wrong vtable slot (e.g. a method's function pointer
554+
// reinterpreted as a field offset) and crashing.
555+
void assignCanonicalVirtualIndexes()
556+
{
557+
auto offset = 0;
558+
for (auto &extent : extends)
559+
{
560+
offset += std::get<1>(extent)->getVTableSize();
561+
}
562+
563+
for (auto &method : methods)
564+
{
565+
method.virtualIndex = offset++;
566+
}
567+
568+
for (auto &field : fields)
569+
{
570+
field.virtualIndex = offset++;
571+
}
572+
}
573+
542574
void recalcOffsets()
543575
{
544576
auto offset = 0;

tslang/lib/TypeScript/MLIRGenInterfaces.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,11 @@ namespace mlirgen
581581

582582
} while (notResolved > 0);
583583

584+
// fix up vtable slot numbers to the canonical methods-then-fields order now that
585+
// all members are known - see assignCanonicalVirtualIndexes() for why this can't
586+
// be left to getVirtualTable()'s per-cast assignment alone.
587+
newInterfacePtr->assignCanonicalVirtualIndexes();
588+
584589
// add to export if any
585590
if (auto hasExport = getExportModifier(interfaceDeclarationAST))
586591
{

tslang/test/tester/tests/export_object_literal_with_interface.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ namespace A {
1414

1515
// invalid Point3d is not exported
1616
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
17+
18+
export interface Counter {
19+
count: number;
20+
inc(): void;
21+
}
22+
23+
export var counter: Counter = { count: 0, inc() { this.count = this.count + 1; } };
1724
}

tslang/test/tester/tests/import_object_literal_with_interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ if (A.Origin3d.x == 0)
1010
print("ok");
1111
}
1212

13+
print(A.counter.count);
14+
1315
print("done.");

0 commit comments

Comments
 (0)