Skip to content

Tags: NativeScript/ios

Tags

v9.1.0-next.2026-09-02-33668116573

Toggle v9.1.0-next.2026-09-02-33668116573's commit message
chore: bump to 9.1.0

v9.0.4-next.2026-09-02-33663440710

Toggle v9.0.4-next.2026-09-02-33663440710's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(runtime): null-initialize Runtime::isolate_ (#460)

Init assigns isolate_ only after the context and its bindings are set up,
so from construction until then the member holds an indeterminate value.
The destructor and GetIsolate() read it unconditionally, and
currentRuntime_ already points at the runtime from the constructor on.
Initialize it to nullptr, like napiEnv_, so a runtime that has not
completed Init reports no isolate instead of garbage.

v9.1.0

Toggle v9.1.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(runtime): interop memory-safety fixes from the worker memory-corr…

…uption hunt (#458)

* fix(runtime): single-owner adapter wrappers and reentrancy-safe disposal

The collection adapters attached an ObjCDataWrapper to their JS object
unconditionally, and DisposeValue's ObjCObject branch releases the
adapter with the wrapper pointer cached in a local -- the adapter's
-dealloc, running inside that release, freed the same wrapper the tail
then deleted again. The double-free's recycled chunk corrupted live
allocations (captured in the field as a registered persistent's slot
word zeroed while its node stayed armed), surfacing as three distinct
GC crash signatures on worker isolates within seconds of heavy
collection marshalling.

Ownership is now single and explicit: a JS object's internal field
holds at most one wrapper and owns it; the first adapter to attach
wins, a later one stays detached and never writes or clears the field;
dataWrapper_ is a claim token for recognising our own wrapper, not an
ownership handle. Every path that runs arbitrary code between reading
the field and freeing it -- DisposeValue's tail and
__releaseNativeCounterpart -- re-reads the field and frees only a
wrapper still attached.

DictionaryAdapter also gains the object_->Reset() the other adapters
already had (its absence leaked the armed global-handle node), and its
key enumerators retain the adapter -- NSEnumerator semantics -- so the
reset cannot empty the persistent under a live enumeration.

New GCFinalizerTests specs pin the ownership contract under the
production workload mix (adapter marshalling interleaved with native
TextDecoder/atob churn, finalizer-driven releases, a worker-isolate
variant); they are tripwires -- the old double-free needs a
guard-malloc/ASan lane to abort deterministically. Suite 1512/0.

* fix(interop): zero-initialize the NSError out-parameter buffer

A callee writes *error only on failure, so on success the read-back
found whatever the malloc chunk last held. A non-null stale value was
then sent localizedDescription and -- read through a __strong pointer
-- retained and released by ARC: an over-release of whatever object
now lives at that address, prematurely freeing live allocations whose
owners keep writing through dangling references. Those writes landing
in recycled GC bookkeeping produced the worker-isolate crash family
this branch chases; under MallocScribble the stale read reproduces
deterministically at boot as an unrecognized-selector throw on the
scribble pattern.

The other transient interop buffers are fully written before any read;
this was the only uninitialized read-back.

* feat(worker): name looper threads after their entry script

Crash reports previously showed every worker as an anonymous
NSOperationQueue thread; the thread name now carries the worker id and
script basename (worker3:pixelmap-socket.js).

* fix(runtime): pin the backing store for the NSDataAdapter's lifetime

The adapter's persistent pins the JS object, not its bytes: a postMessage
transfer detaches the ArrayBuffer and hands the store to another isolate,
whose GC can free the memory while native code still holds this NSData —
an async reader/writer then touches a freed, recycled chunk. Holding the
BackingStore shared_ptr keeps the bytes alive for the adapter's lifetime,
which is the contract NSData callers assume, and lets -bytes answer
without unlocked cross-thread V8 access. The never-materialized-view
branch now serves one stable copy freed in dealloc instead of leaking a
fresh malloc per call.

* fix(runtime): snapshot the NSDataAdapter length with its pinned store

NSData is immutable — length must not change for the object's lifetime —
but the live ByteLength() read reported zero after a transfer detach
while the pinned bytes stayed valid, and it was also the adapter's last
unlocked cross-thread V8 access.

* fix(runtime): materialize the on-heap view copy at adapter init

The lazy first--bytes copy ran isolate APIs from whatever thread the
caller was on, could race concurrent callers on the publication, and a
view detached before the first call would expose an uninitialized
allocation. Copying during init — isolate owned, view alive — removes
the lazy path entirely, so every -bytes branch answers from native
storage.

* fix(runtime): free the adapter's wrapper claim after isolate teardown

An adapter released after its isolate died skipped the whole cleanup
block and orphaned its attached wrapper — one 48-byte leak per adapter
on every worker teardown. With the isolate gone the JS object and every
other reader or deleter of the claim are gone too (all IsValid-gated),
so the owner can free it unconditionally.

* fix(runtime): let the JSBlock own the block cache attached to its function

Passing a JS function as a block argument caches a BlockWrapper on the
function so repeat calls reuse the same block. That wrapper was freed only
from the JSBlock dispose helper, and only by looking it up through the
cached function -- which needs a live isolate. A block built on a worker
isolate that outlives it, or released after it, skipped that branch
entirely and orphaned the wrapper: two 48-byte tns::BlockWrapper leaks per
TestRunner run, both allocated under WorkerWrapper::BackgroundLooper
(NativeCallbackWorker and TeardownCrashWorker install an
NSNotificationCenter observer block at module load).

The block now carries the wrapper pointer, so disposal frees it without
the isolate, and native code holding the block keeps the wrapper reachable
in the meantime. While the isolate is alive the wrapper is freed only when
the function's slot still points at it, the same ownership rule
ObjectManager::DisposeValue applies -- __releaseNativeCounterpart can
retire the same wrapper first. Disposal stays inline and
callback_->Reset() stays unconditional.

* docs(runtime): document the known losing-emplace leak in isImplementedInClass

Freeing the loser is unsafe (never-initialized instance of an arbitrary
class, arbitrary thread) and parking it merely converts the leak into
perpetual retention; the leak stays, visible and explained, tracked by
issue #459.

* fix(runtime): make the adapter the sole owner of its wrapper claim

__releaseNativeCounterpart could delete an adapter's attached claim while
a native reference kept the adapter alive; if the isolate then died
before the adapter's -dealloc, the teardown branch freed the stale
pointer again. Claims are now marked and retirement paths leave them
attached — the only deleter is the adapter's own -dealloc, in either
isolate state.

v9.0.4-next.2026-08-27-33108447041

Toggle v9.0.4-next.2026-08-27-33108447041's commit message
chore: complete the 9.1.0 changelog for v9.0.3...v9.1.0

v9.0.4-next.2026-08-27-33099278340

Toggle v9.0.4-next.2026-08-27-33099278340's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(runtime): interop memory-safety fixes from the worker memory-corr…

…uption hunt (#458)

* fix(runtime): single-owner adapter wrappers and reentrancy-safe disposal

The collection adapters attached an ObjCDataWrapper to their JS object
unconditionally, and DisposeValue's ObjCObject branch releases the
adapter with the wrapper pointer cached in a local -- the adapter's
-dealloc, running inside that release, freed the same wrapper the tail
then deleted again. The double-free's recycled chunk corrupted live
allocations (captured in the field as a registered persistent's slot
word zeroed while its node stayed armed), surfacing as three distinct
GC crash signatures on worker isolates within seconds of heavy
collection marshalling.

Ownership is now single and explicit: a JS object's internal field
holds at most one wrapper and owns it; the first adapter to attach
wins, a later one stays detached and never writes or clears the field;
dataWrapper_ is a claim token for recognising our own wrapper, not an
ownership handle. Every path that runs arbitrary code between reading
the field and freeing it -- DisposeValue's tail and
__releaseNativeCounterpart -- re-reads the field and frees only a
wrapper still attached.

DictionaryAdapter also gains the object_->Reset() the other adapters
already had (its absence leaked the armed global-handle node), and its
key enumerators retain the adapter -- NSEnumerator semantics -- so the
reset cannot empty the persistent under a live enumeration.

New GCFinalizerTests specs pin the ownership contract under the
production workload mix (adapter marshalling interleaved with native
TextDecoder/atob churn, finalizer-driven releases, a worker-isolate
variant); they are tripwires -- the old double-free needs a
guard-malloc/ASan lane to abort deterministically. Suite 1512/0.

* fix(interop): zero-initialize the NSError out-parameter buffer

A callee writes *error only on failure, so on success the read-back
found whatever the malloc chunk last held. A non-null stale value was
then sent localizedDescription and -- read through a __strong pointer
-- retained and released by ARC: an over-release of whatever object
now lives at that address, prematurely freeing live allocations whose
owners keep writing through dangling references. Those writes landing
in recycled GC bookkeeping produced the worker-isolate crash family
this branch chases; under MallocScribble the stale read reproduces
deterministically at boot as an unrecognized-selector throw on the
scribble pattern.

The other transient interop buffers are fully written before any read;
this was the only uninitialized read-back.

* feat(worker): name looper threads after their entry script

Crash reports previously showed every worker as an anonymous
NSOperationQueue thread; the thread name now carries the worker id and
script basename (worker3:pixelmap-socket.js).

* fix(runtime): pin the backing store for the NSDataAdapter's lifetime

The adapter's persistent pins the JS object, not its bytes: a postMessage
transfer detaches the ArrayBuffer and hands the store to another isolate,
whose GC can free the memory while native code still holds this NSData —
an async reader/writer then touches a freed, recycled chunk. Holding the
BackingStore shared_ptr keeps the bytes alive for the adapter's lifetime,
which is the contract NSData callers assume, and lets -bytes answer
without unlocked cross-thread V8 access. The never-materialized-view
branch now serves one stable copy freed in dealloc instead of leaking a
fresh malloc per call.

* fix(runtime): snapshot the NSDataAdapter length with its pinned store

NSData is immutable — length must not change for the object's lifetime —
but the live ByteLength() read reported zero after a transfer detach
while the pinned bytes stayed valid, and it was also the adapter's last
unlocked cross-thread V8 access.

* fix(runtime): materialize the on-heap view copy at adapter init

The lazy first--bytes copy ran isolate APIs from whatever thread the
caller was on, could race concurrent callers on the publication, and a
view detached before the first call would expose an uninitialized
allocation. Copying during init — isolate owned, view alive — removes
the lazy path entirely, so every -bytes branch answers from native
storage.

* fix(runtime): free the adapter's wrapper claim after isolate teardown

An adapter released after its isolate died skipped the whole cleanup
block and orphaned its attached wrapper — one 48-byte leak per adapter
on every worker teardown. With the isolate gone the JS object and every
other reader or deleter of the claim are gone too (all IsValid-gated),
so the owner can free it unconditionally.

* fix(runtime): let the JSBlock own the block cache attached to its function

Passing a JS function as a block argument caches a BlockWrapper on the
function so repeat calls reuse the same block. That wrapper was freed only
from the JSBlock dispose helper, and only by looking it up through the
cached function -- which needs a live isolate. A block built on a worker
isolate that outlives it, or released after it, skipped that branch
entirely and orphaned the wrapper: two 48-byte tns::BlockWrapper leaks per
TestRunner run, both allocated under WorkerWrapper::BackgroundLooper
(NativeCallbackWorker and TeardownCrashWorker install an
NSNotificationCenter observer block at module load).

The block now carries the wrapper pointer, so disposal frees it without
the isolate, and native code holding the block keeps the wrapper reachable
in the meantime. While the isolate is alive the wrapper is freed only when
the function's slot still points at it, the same ownership rule
ObjectManager::DisposeValue applies -- __releaseNativeCounterpart can
retire the same wrapper first. Disposal stays inline and
callback_->Reset() stays unconditional.

* docs(runtime): document the known losing-emplace leak in isImplementedInClass

Freeing the loser is unsafe (never-initialized instance of an arbitrary
class, arbitrary thread) and parking it merely converts the leak into
perpetual retention; the leak stays, visible and explained, tracked by
issue #459.

* fix(runtime): make the adapter the sole owner of its wrapper claim

__releaseNativeCounterpart could delete an adapter's attached claim while
a native reference kept the adapter alive; if the isolate then died
before the adapter's -dealloc, the teardown branch freed the stale
pointer again. Claims are now marked and retirement paths leave them
attached — the only deleter is the adapter's own -dealloc, in either
isolate state.

v9.0.4-next.2026-08-27-33033911119

Toggle v9.0.4-next.2026-08-27-33033911119's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(runtime): finalizer-safe handle ownership and deferred JSBlock te…

…ardown (#457)

* fix(runtime): finalizer-safe ownership for registered handles and deferred JSBlock teardown

An in-flight kFinalizer callback could have its state freed or its node
reset underneath it, breaking the contract that a finalizer either
resets its handle or re-arms it and corrupting the drain's bookkeeping
(the production V8_Fatal CHECKs on worker isolates).

ObjectWeakCallbackState now has exactly two deleting sites --
FinalizerCallback's disposed branch and DisposeAllRegistered -- and
every other retirement resets the persistent first, which frees the
node, clears the pending bit and guarantees no further callback. A
disposing flag makes a reentrant retirement (a -dealloc reached from
DisposeValue calling __releaseNativeCounterpart) defer to the frame
that owns the state. FinalizerCallback re-checks handle emptiness
after DisposeValue: an adapter dealloc can reset the very persistent
being finalized, and ClearWeak on an empty handle writes through a
dead slot, so a handle emptied underneath its callback is retired,
never re-armed.

__releaseNativeCounterpart gains its missing Reset -- it retired
registrations by deleting the state while leaving the node rooted
forever with parameter() dangling at freed memory.

The JSBlock dispose helper no longer does V8 work inline: the last
native release can land on any thread, including inside the finalizer
drain via dealloc cascades, so handle teardown posts to the owning
isolate's event loop (a refused post means the isolate is gone and
only native memory remains). The block pointer is cleared
synchronously, and marshalling builds a fresh block for a wrapper
whose JSBlock already died.

Removes DisposerPHV (dead since VisitHandlesWithClassIds went away)
and an unlocked, guardless Reset in NSDataAdapter's dealloc.

Suite green; new GCFinalizerTests specs cover the retired-handle
collectability contract, dealloc-cascade reentrancy, and the
natively-held-block production shape. Reverting only the added Reset
crashes the runtime outright.

* fix(runtime): keep JSBlock dispose inline and guard the teardown walk's claim

The dispose helper goes back to inline teardown under the isolate
Locker: callback_ is a strong, unregistered persistent, so resetting it
never touches the finalizer drain's bookkeeping, and a foreign-thread
Locker into the block's own isolate is legitimate now that extended
class names are worker-scoped. The deferred posting -- and the
cleared-block re-marshal machinery it required -- is removed; the
unconditional callback_ Reset stays, since an already-detached callback
still owns its node.

FinalizerCallback now honors the disposing claim on entry: a nested
collection during DisposeAllRegistered's walk can condemn a pre-claimed
state, and disposing it there would free memory the walk still holds.
The callback re-arms its node -- satisfying the finalizer contract --
and leaves clear, reset and delete to the owner.

Block-collectability specs poll instead of assuming a single tick
suffices; drain interleaving makes one tick a coin flip either way.

v9.1.0-rc.2

Toggle v9.1.0-rc.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(runtime): DOMException and CustomEvent as lazy globals (#452)

v9.1.0-rc.1

Toggle v9.1.0-rc.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(runtime): lazy-global tier with native TextEncoder/TextDecoder a…

…nd atob/btoa (#448)

v9.0.4-next.2026-08-25-32905314171

Toggle v9.0.4-next.2026-08-25-32905314171's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(runtime): DOMException and CustomEvent as lazy globals (#452)

v9.0.4-next.2026-08-25-32896454963

Toggle v9.0.4-next.2026-08-25-32896454963's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(runtime): lazy-global tier with native TextEncoder/TextDecoder a…

…nd atob/btoa (#448)