Skip to content

iOS Unit Tests - #8

Merged
bghgary merged 13 commits into
BabylonJS:mainfrom
bghgary:ios
Feb 16, 2023
Merged

iOS Unit Tests#8
bghgary merged 13 commits into
BabylonJS:mainfrom
bghgary:ios

Conversation

@bghgary

@bghgary bghgary commented Feb 16, 2023

Copy link
Copy Markdown
Contributor

@bghgary bghgary closed this Feb 16, 2023
@bghgary bghgary reopened this Feb 16, 2023
@bghgary
bghgary marked this pull request as ready for review February 16, 2023 21:33
Comment thread Tests/UnitTests/Scripts/tests.js
@bghgary
bghgary merged commit ddb8a2e into BabylonJS:main Feb 16, 2023
@bghgary
bghgary deleted the ios branch February 16, 2023 22:53
CedricGuillemet added a commit to CedricGuillemet/JsRuntimeHost that referenced this pull request Jun 3, 2026
Captured a full backtrace via lldb -k (see prior CI commit). The smoking gun:

  frame BabylonJS#5: ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED

  frame BabylonJS#6: napi_delete_reference at hermes_napi_reference.cpp:113

  frame BabylonJS#7: Napi::Reference<...>::~Reference at napi-inl.h:3262

  frame BabylonJS#8: Napi::ObjectReference::~ObjectReference

  frame BabylonJS#10: Babylon::Polyfills::Internal::URL::~URL at URL.h:10

  frame BabylonJS#12: Napi::ObjectWrap<...URL>::FinalizeCallback at napi-inl.h:4963

  frame BabylonJS#13: napi_env__::shutdown at hermes_napi.cpp:214

  frame BabylonJS#16: hermes::vm::Runtime::~Runtime

Root cause: Hermes's napi_env__::shutdown() iterates refListHead_ and delete ref; one at a time. It only sets ref->deletionPending_ on the *current* ref before its finalize_cb fires. If the finalizer transitively destroys a node-addon-api wrapper (Napi::Reference / Napi::ObjectReference) whose underlying napi_ref was already deleted earlier in the same loop, napi_delete_reference reads ref->deletionPending_ from freed memory and proceeds to delete ref again -> double-free.

The exact path: URL (an ObjectWrap subclass) has a Napi::ObjectReference member m_searchParamsReference. addReference prepends to the linked list, so m_searchParamsReference's ref is processed BEFORE URL's wrap ref. When URL's wrap finalizer runs delete this, ~URL destroys m_searchParamsReference, whose destructor calls napi_delete_reference on the already-freed sibling ref.

macOS libmalloc detects this (malloc: *** error for object 0x...: pointer being freed was not allocated -> SIGABRT). Linux glibc and Windows CRT happen to miss it.

Fix: PATCH Hermes shutdown() to mark ALL refs deletionPending in a pre-pass BEFORE iterating. Apply as a FetchContent PATCH_COMMAND via the new ApplyPatchIfNeeded.cmake helper (idempotent — uses git apply --check --reverse to detect already-applied state across reconfigure).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bkaradzic-microsoft added a commit that referenced this pull request Aug 20, 2026
### Problem

`napi_throw`, `napi_throw_error`, `napi_throw_type_error` and
`napi_throw_range_error` returned `napi_pending_exception` after
successfully scheduling the throw.

In Node-API that status means *"this call failed because an exception
was already pending"*, not *"a throw is now pending"*. The upstream
implementation returns `napi_clear_last_error(env)` (i.e. `napi_ok`).

Because the QuickJS port reported failure,
`Error::ThrowAsJavaScriptException` in `napi-inl.h` took its failure
branch on **every** native throw:

```cpp
napi_status status = napi_throw(_env, Value());
#ifdef NAPI_CPP_EXCEPTIONS
    if (status != napi_ok) {
      throw Error::New(_env);   // consumes the exception that was just set
    }
#endif
```

`Error::New(env)` calls `napi_get_and_clear_last_exception`, so the
pending JS exception is discarded and a fresh C++ exception is thrown
out of `details::WrapCallback`. `ExternalCallback::Callback` then
catches it, observes `!JS_HasException(ctx)`, and rebuilds the error
from `e.what()`.

By that point the `HandleScope` opened by `ThrowAsJavaScriptException`
has been destroyed during unwinding, so stringifying the message reads
freed memory.

### Impact

Two symptoms, both of which reproduce today:

1. **Wrong error surfaced to JS.** The real error is replaced by
`InternalError: Uncaught C++ exception: <message>`. Every native throw
on QuickJS is affected, so `err.name` and `err instanceof TypeError` are
wrong throughout.
2. **Use-after-free.** On Linux this segfaults. Backtrace from a
BabylonNative CI core dump:

```
#0  js_dup                       quickjs.c:1628          <-- SIGSEGV
#1  js_force_tostring            quickjs.c:4813
#3  JS_ToCStringLen
#4  napi_get_value_string_utf8   js_native_api_quickjs.cc:696
#5  Napi::String::Utf8Value      napi-inl.h:1118
#7  Napi::Error::Message         napi-inl.h:3087
#8  Napi::Error::what            napi-inl.h:3157
#9  ExternalCallback::Callback   js_native_api_quickjs.cc:164
```

The `JSValue` being stringified carries `JS_TAG_STRING` with an
unaligned, freed pointer.

I instrumented the `catch` in `ExternalCallback::Callback` in a
BabylonNative QuickJS build and confirmed that **all ~50 native throws**
in that test run escaped `WrapCallback` with `hasExc=0`. After this
change the count is 0.

### Fix

Return `napi_ok` from the four throw entry points, matching upstream.
The exception stays pending, `WrapCallback` returns normally, and the
fragile `e.what()` fallback is never entered.

### Test

Added a strict assertion to the existing `URLSearchParams.set()` arity
throw, checking the error type and exact message rather than a
substring. The pre-existing `.to.throw()` test could not catch this,
because `"Uncaught C++ exception: <msg>"` still *contains* the expected
substring.

Verified on Linux QuickJS (RelWithDebInfo):

| | result |
|---|---|
| without the C++ change | `expected 'InternalError' to equal 'Error'` —
212 passing, **1 failing** |
| with the C++ change | **213 passing**, 10/10 gtest |

Also verified in a BabylonNative QuickJS build on Windows: 21/21 gtest,
49 JS assertions, exit 0, and zero escapes from `WrapCallback`.

Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 88569c10-a7ff-4373-9a58-afa9c68b8c09
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