napi backend: Pointer::finalize erases g_pointerCache re-entrantly on the JS thread — map corruption / SIGSEGV under GC finalization

オープン
#109 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
32/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
cpp, objective-c
領域
backend

調査の方向性

Start in NativeScript/ffi/napi/Interop.mm at Pointer::finalize, then follow it to PostFinalizer in ffi/napi/ObjCBridge.mm, where finalizePointerNow runs inline on the JS thread. Check the iterator use in getCachedPointer, cachePointer and interop_free around napi calls, and the unconditional it->second dereference. Done means finalization is deferred to the JS run loop and null-ref entries are purged before use. No test is named in the payload, so you will need to find or write a reproduction.

索引モデルが issue の本文から書いたものです。

説明

Summary

Pointer::finalize (NativeScript/ffi/napi/Interop.mm) can mutate g_pointerCache re-entrantly during GC finalization on the JS thread, corrupting the std::unordered_map under live iterators and crashing intermittently.

Pointer::finalize defers through PostFinalizer (ffi/napi/ObjCBridge.mm), which runs finalizePointerNow inline whenever finalization happens on the JS thread (bridgeState->jsThreadId == this_thread). A JS engine may finalize inside any napi call that allocates — e.g. get_ref_value, napi_create_reference, napi_delete_reference. getCachedPointer, cachePointer, and interop_free all hold g_pointerCache iterators across napi calls, so an inline finalizePointerNow can erase nodes underneath them: the outer frame then dereferences a recycled iterator (SIGSEGV in __hash_table::remove) or passes a stale napi_ref to napi_delete_reference (fault in js_delete_reference on JSC).

We observed intermittent SIGSEGVs during run_file eval in a libjsc-based host once bridge work increased pointer-cache churn at boot — timing-dependent, not a script bug.

A second, related hazard: a stale entry can hold a null napi_ref. getCachedPointer dereferences it->second unconditionally (get_ref_value, napi_delete_reference); a null-ref entry should be purged and treated as a miss before use.

Expected

finalizePointerNow should never run re-entrantly inside an outer napi call that holds map iterators — e.g., always defer pointer finalization to the JS run loop (CFRunLoopPerformBlock(jsRunLoop, kCFRunLoopCommonModes, …) + CFRunLoopWakeUp) rather than inline — and getCachedPointer should purge null-ref entries before dereferencing them.

Environment

  • NativeScript/runtimes main (verified identical at 5b697b39)
  • Generic napi backend, TARGET_PLATFORM=macos, arm64, holepunchto libjsc env
  • Reproduced intermittently at script-eval time; Interop.mm is backend-shared, so other napi embedders carry the same risk profile.
主要言語
C++
スター
27
フォーク
4
平均マージ
5日 11時間
マージ済み PR(30日)
12

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

NativeScript/runtimes のほかの issue

NativeScript/runtimes の issue をすべて見る

似ている issue

C++ の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。