Skip to content

rc: a method bound through an interface, or by a global's initializer, counts its object - #659

Merged
ASDAlexander77 merged 7 commits into
mainfrom
fix-rc-bound-method-counts-object
Oct 23, 2026
Merged

ASDAlexander77 merged 7 commits into
mainfrom
fix-rc-bound-method-counts-object

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Fixes fuzzer repro 40181 (#646), found in a fresh range past the first 1000 programs. It fails on main as well.

Why

Under rc a bound method counts the object it is bound to through that object's type tag at CLOSURE_TYPE_INDEX. Only the bound value's receivers take a count (getBoundObjectTypeTag). There were two holes:

  • Bound through an interface (c = ig.get): the object's type isn't known where the bound value is built, so the value got no tag and counted nothing. c() then ran on a freed object once the interface went away.
  • A global initialized with a bound method (let c = mk().get, let c = G.v.get): the global took no count. A temporary object was given back at the end of the initializer, and the first c = d released it a second time.

Fix

  • InterfaceSymbolRefOpLowering, rc only: the bound value takes the tag the interface already carries for what it points at (INTERFACE_TYPE_INDEX, set by NewInterfaceOpLowering). A missing optional method has a null this, and its tag is null too. The struct is built from LLVM values directly, the way CreateBoundFunctionOpLowering builds it. A first version wrapped a not-yet-converted ts.CreateBoundFunction in an UnrealizedConversionCast. With that version, tslang aborted in malloc under --opt on Linux (36interfaces, 00owned_construct_interface). Building the struct directly, in the second commit, makes both pass. I didn't pin down exactly what corrupted the heap.
  • claimGlobalInitializers: a global initialized with a bound method takes a count, as it already does for a field read.

Tests

tests/00bound_method_counts_object.ts covers:

  • a local bound through an interface
  • a global bound through an interface
  • a global bound by its initializer to a temporary
  • a global bound by its initializer to a global's field
  • the global bound again

It runs compiled, under JIT, and in the rc/none corpus.

Verification

🤖 Generated with Claude Code

ASDAlexander77 and others added 7 commits October 23, 2026 06:48
…, counts its object

Under rc a bound method counts the object it is bound to through that
object's type tag, and only the bound value's receivers take a count
(getBoundObjectTypeTag). Two holes:

- bound through an interface (`c = ig.get`), the object's type is not
  known where the bound value is built, so it got no tag and counted
  nothing: `c()` ran on a freed object once the interface went. It now
  takes the tag the interface carries for what it points at (null for a
  missing optional method).
- a global initialized with a bound method (`let c = mk().get`,
  `let c = G.v.get`) took no count: a temporary object was given back
  at the end of the initializer, and the first `c = d` released it a
  second time. The global now takes one, as it does for a field read.

Fuzzer (#646), found past the first 1000 programs (40181).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tagged bound value was an UnrealizedConversionCast over a
ts.CreateBoundFunction still waiting for its own conversion. Under
--opt that corrupted the compiler's heap on Linux (36interfaces,
00owned_construct_interface). Build the struct directly, as
CreateBoundFunctionOpLowering does: method, this, then the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ounts-object

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
…ounts-object

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
…ounts-object

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
@ASDAlexander77
ASDAlexander77 merged commit eb80478 into main Oct 23, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-bound-method-counts-object branch October 23, 2026 10:07
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