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

未关闭
#109 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
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
派生
5
平均合并
5 天 11 小时
30 天内合并 PR
12

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

NativeScript/runtimes 的其他 Issue

查看 NativeScript/runtimes 的全部 Issue

相似的 Issue

更多 C++ Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。