Repository navigation
Cast from any to an interface asks the object what class it is - #712
Merged
Merged
Conversation
`<Holder><any>runner` compiled to a plain unbox: the box's payload was read as Holder's (this, vtable) pair, so a Runner value (Runner's vtable) or a class object in the box was called through garbage - an access violation in every memory model. An `any` records only the kind of what it holds, not which interface an interface value was made for, so there is no run-time lookup by interface name. castAnyToInterface generates `___unbox_interface<I>`, which asks the object behind the box (`instanceof`): - a class with a vtable for I - it declares `implements I`, or some cast in the module makes one (classInterfaceVTablesCast, recorded from the discovery pass on, so a cast later in the module counts) - gives I through that vtable, from a class object or an interface value; - any other class of this module throws "class 'X' does not implement interface 'I'"; no vtable is made for it here, so no new way to reach a class's fields appears; - anything else (an interface value over an object literal or another module's class, a number used as a token) is read as before, so a value boxed as the interface it is cast to round-trips. Test: 00any_to_interface (gc, rc, none, own; compile and JIT). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
<Holder><any>r, whereris aRunnerover aWalker(which never becomes aHolderin the module), compiled to a plain unbox. The box's payload was read asHolder's(this, vtable)pair, so callingh.f(...)went throughRunner's vtable and crashed the JIT with 0xC0000005 in every memory model. A class object boxed intoanyand cast to an interface was read the same wrong way.An
anyrecords only the kind of what it holds ("interface","class", ...), not which interface an interface value was made for. So there is no run-time lookup by interface name.Fix
castToInterfaceSpecialCasesnow sendsany→ interface tocastAnyToInterface. That function generates___unbox_interface<I>, which asks the object behind the box what class it is (instanceof):IgivesIthrough that vtable, from a class object or an interface value. That covers a class that declaresimplements I, and one that some cast in the module turns intoI. Those casts are recorded inclassInterfaceVTablesCastfrom the discovery pass on, so a cast later in the file still counts.Can't cast from any: class 'X' does not implement interface 'I'. A class object no class answers for also throws. No vtable is made here, so the cast adds no new way to reach a class's fields (the-mm=own§58 concern).29lazyreferences). So a value boxed as the interface it is cast to still round-trips (00any_types).The repro now throws a catchable error in every model instead of crashing.
Notes
-mm=own, the result is a borrow of a call result, the same as the existing<C>aunbox. Sofunction f(a: any) { const h = <Holder>a; h.f(x); }is rejected the wayconst w = <Walker>a; const h: Holder = w; h.f(x)already was. Before this change the plain cast escaped that check.Walker.Holdervtable so that<Holder><any>runnerworks as TypeScript allows. That vtable would be a new path to the class's fields, so it is left out here.Tests
00any_to_interface.ts: another interface's value, a class object, a declared implementer, a vtable made by a cast later in the module, and a round-trip. It runs under gc (compile and JIT), the rc/none corpus, and-mm=own(compile and JIT). The unpatched compiler crashes on it.ctest -C Release: 6055/6055 pass.🤖 Generated with Claude Code