napi backend: JSMethodCallback runs napi calls on the invoking thread — JS IMPs crash when ObjC calls them on foreign threads

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

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
18/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
c, cpp, macos, nodejs, objective-c
领域
backend

调研方向

Start in NativeScript/ffi/napi/Closure.mm and compare JSMethodCallback with JSBlockCallback, which already checks closure->jsThreadId and marshals foreign-thread calls onto the JS run loop or threadsafe function. The repro needs a macOS arm64 build with -DNS_FFI_BACKEND=napi, so the first check is whether the spawned-thread ping call crashes in ObjCBridgeState::getObject. Done means that call either blocks until the JS thread returns 42 or fails with a clear error, without a SIGSEGV.

由索引模型根据 Issue 内容生成。

描述

Summary

JSMethodCallback (NativeScript/ffi/napi/Closure.mm) performs napi calls — ObjCBridgeState::getObject, napi_get_named_property, napi_call_function — directly on whatever thread invoked the ffi closure, with no thread check. When a JS-backed ObjC method IMP is invoked on a non-JS thread, the bridge calls into the JS engine off-thread and crashes.

JSBlockCallback in the same file does check the calling thread (currentThreadId == closure->jsThreadId) and marshals foreign-thread invocations onto the JS side (napi_call_threadsafe_function for generic-napi builds; CFRunLoopPerformBlock(jsRunLoop, kCFRunLoopCommonModes) for embedded-runtime builds). Method IMPs get no equivalent handling. JSFunctionCallback (C-function-pointer callbacks) has the same unguarded shape.

Concrete hit in the wild: NSPrintOperation with canSpawnSeparateThread=YES invokes the operation's didRunSelector on the spawned print thread — a JS didRun IMP there crashes inside ObjCBridgeState::getObject → tryResolveBridgedClassConstructor (SIGSEGV).

Reproduction

macOS arm64, NativeScript.framework built at 5b697b39 with -DTARGET_ENGINE=none -DNS_FFI_BACKEND=napi (code identical on main), env owned by the main thread:

var Delegate = NSObject.extend(
  { ping: function () { return 42; } },
  { exposedMethods: { ping: { returns: interop.types.int32 } } }
);
var obj = Delegate.new();
obj.respondsToSelector(NSSelectorFromString("ping")); // true
NSThread.detachNewThreadSelectorToTargetWithObject(NSSelectorFromString("ping"), obj, null);

→ EXC_BAD_ACCESS (SIGSEGV) on the spawned thread:

nativescript::ObjCBridgeState::tryResolveBridgedClassConstructor(napi_env*, napi_value*, objc_class**)
nativescript::ObjCBridgeState::getObject(napi_env*, objc_object*, napi_value*, ObjectOwnership)
nativescript::JSMethodCallback(ffi_cif*, void*, void**, void*)
ffi_closure_SYSV_inner
__NSThread__start__
_pthread_start

Expected

Foreign-thread IMP/closure invocations should be marshalled to the JS thread the way JSBlockCallback already does — the caller blocks until the return value is produced, which suits synchronous IMPs — or fail deterministically with a clear error rather than corrupting engine state. As it stands, delegate methods that Apple legitimately invokes off the main thread (spawned NSPrintOperation threads, some AVFoundation/UN delegate calls on background queues) cannot be implemented in JS at all, and a missed-thread dispatch takes down the whole process.

Environment

  • NativeScript/runtimes main (verified identical at 5b697b39)
  • Generic napi backend, TARGET_PLATFORM=macos, arm64; the same ffi/napi code path ships in the iOS napi runtime.
主要语言
C++
星标
27
派生
4
平均合并
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 摘要。