如果仍在范围内,将全局 js 对象存储在 JS::Heap<T> 中会在 JS_DestroyRuntime 中引发崩溃
Storing the global js object in JS::Heap<T> provokes crash in JS_DestroyRuntime if still in scope
学习this question后,
我正在尝试将 SpiderMonkey 的全局对象存储在堆上。只要在调用 JS_DestroyRuntime
之前超出范围,这似乎就可以工作。然而,在我的代码中,全局对象是一个 class 成员,因此在运行时在 class 的析构函数中被销毁之前不能超出范围。
不幸的是,这导致 Monkey 在 JS::~Heap
中崩溃,堆栈跟踪如下:
1 js::gc::Cell::storeBuffer() const Heap.h 1339 0x10004f905
2 JSObject::writeBarrierPost(void *, JSObject *, JSObject *) jsobj.h 655 0x1000a6fc8
3 js::InternalGCMethods<JSObject *>::postBarrier(JSObject * *, JSObject *, JSObject *) Barrier.h 254 0x1000a6df0
4 JS::HeapObjectPostBarrier(JSObject * *, JSObject *, JSObject *) Barrier.cpp 173 0x100bc1636
5 js::GCMethods<JSObject *>::postBarrier(JSObject * *, JSObject *, JSObject *) RootingAPI.h 551 0x100003065
6 JS::Heap<JSObject *>::post(JSObject * const&, JSObject * const&) RootingAPI.h 271 0x10000302b
7 JS::Heap<JSObject *>::~Heap() RootingAPI.h 237 0x10000369e
8 JS::Heap<JSObject *>::~Heap() RootingAPI.h 236 0x100002f75
9 MyMonkeyClass::~MyMonkeyClass() main.cpp 64 0x100003725
10 MyMonkeyClass::~MyMonkeyClass() main.cpp 58 0x100002aa5
11 main main.cpp 110 0x100002a12
12 start 0x1000029d4
这是一个触发问题的最小示例。我故意离开了 GC 跟踪调用,它们不会改变结果。
#include <js/Initialization.h>
#include <jsapi.h>
#include <QDebug>
// The class of the global object. Just a dummy.
static JSClass global_class = {
"global",
JSCLASS_GLOBAL_FLAGS,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
JS_GlobalObjectTraceHook
};
class MyMonkeyClass
{
public:
MyMonkeyClass() {
Q_ASSERT( JS_Init() );
auto runtime = JS_NewRuntime( 5 * 1024 * 1024 /*heap space*/ );
Q_ASSERT( runtime );
m_context = JS_NewContext( runtime, 8192 /*default. keep.*/ );
Q_ASSERT( m_context );
JSAutoRequest ar( m_context );
// Not sure which of these alternatives is the correct way with JS::Heap
m_global = JS::RootedObject( m_context,
JS_NewGlobalObject( m_context, &global_class, nullptr, JS::FireOnNewGlobalHook ) );
//global = JS_NewGlobalObject( context, &global_class, nullptr, JS::FireOnNewGlobalHook );
Q_ASSERT( m_global.get() );
}
~MyMonkeyClass() {
auto runtime = JS_GetRuntime( m_context );
JS_DestroyContext( m_context );
JS_DestroyRuntime( runtime );
JS_ShutDown();
}
private:
JSContext *m_context;
JS::Heap<JSObject*> m_global;
};
int main( int, char** )
{
MyMonkeyClass mmc;
return 0;
}
在撰写这个问题时,我发现在销毁东西之前在 dtor 中设置 m_global = nullptr;
实际上可以避免崩溃。现在我的最后一个问题是:
这是正确的解决方法吗?如果是,为什么? SM 是否可能假设非空 JS::Heap 指针指向仍在使用的内存,因此它会出现恐慌?
您最初的问题是由错误的对象释放顺序引起的。
C++ class 以相反的构造顺序析构,即调用 class 析构函数后跟 class 成员析构函数。成员以其声明的相反顺序被销毁。这意味着在你的情况下:
- ~MyMonkeyClass() 进入
- JS_Destroy* 和 JS_ShutDown 被称为
- JS::~堆被调用。但是,JS 引擎已经消失,这会导致未定义的行为 - 在您的情况下会崩溃。
通过将 m_global 设置为 nullptr 可以防止 JS::~Heap() 下降到 JS API 来清理内存。这样可以避免崩溃,但可能会导致内存泄漏。
正确的解决方法是以正确的顺序清理所有内容。
一种方法是定义另一个 class "JsUpDown" 来初始化 JS 引擎,在构造函数中创建 JS 运行时和 JS 上下文并调用 JS_Destroy* 和 JS_ShutDown在析构函数中。然后 MyMonkeyClass 看起来像:
class MyMonkeyClass
{
MyMonkeyClass() : m_engine(), m_global() // just to make it explicit
{
JSAutoRequest ar(m_engine.m_context);
m_global = JS::RootedObject(...);
}
~MyMonkeyClass() {} // no need for the destructor now
JsUpDown m_engine; // Should be first. The order is important:
// Members are destroyed in reverse order.
JS::Heap<JSObject*> m_global;
};
学习this question后,
我正在尝试将 SpiderMonkey 的全局对象存储在堆上。只要在调用 JS_DestroyRuntime
之前超出范围,这似乎就可以工作。然而,在我的代码中,全局对象是一个 class 成员,因此在运行时在 class 的析构函数中被销毁之前不能超出范围。
不幸的是,这导致 Monkey 在 JS::~Heap
中崩溃,堆栈跟踪如下:
1 js::gc::Cell::storeBuffer() const Heap.h 1339 0x10004f905
2 JSObject::writeBarrierPost(void *, JSObject *, JSObject *) jsobj.h 655 0x1000a6fc8
3 js::InternalGCMethods<JSObject *>::postBarrier(JSObject * *, JSObject *, JSObject *) Barrier.h 254 0x1000a6df0
4 JS::HeapObjectPostBarrier(JSObject * *, JSObject *, JSObject *) Barrier.cpp 173 0x100bc1636
5 js::GCMethods<JSObject *>::postBarrier(JSObject * *, JSObject *, JSObject *) RootingAPI.h 551 0x100003065
6 JS::Heap<JSObject *>::post(JSObject * const&, JSObject * const&) RootingAPI.h 271 0x10000302b
7 JS::Heap<JSObject *>::~Heap() RootingAPI.h 237 0x10000369e
8 JS::Heap<JSObject *>::~Heap() RootingAPI.h 236 0x100002f75
9 MyMonkeyClass::~MyMonkeyClass() main.cpp 64 0x100003725
10 MyMonkeyClass::~MyMonkeyClass() main.cpp 58 0x100002aa5
11 main main.cpp 110 0x100002a12
12 start 0x1000029d4
这是一个触发问题的最小示例。我故意离开了 GC 跟踪调用,它们不会改变结果。
#include <js/Initialization.h>
#include <jsapi.h>
#include <QDebug>
// The class of the global object. Just a dummy.
static JSClass global_class = {
"global",
JSCLASS_GLOBAL_FLAGS,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
JS_GlobalObjectTraceHook
};
class MyMonkeyClass
{
public:
MyMonkeyClass() {
Q_ASSERT( JS_Init() );
auto runtime = JS_NewRuntime( 5 * 1024 * 1024 /*heap space*/ );
Q_ASSERT( runtime );
m_context = JS_NewContext( runtime, 8192 /*default. keep.*/ );
Q_ASSERT( m_context );
JSAutoRequest ar( m_context );
// Not sure which of these alternatives is the correct way with JS::Heap
m_global = JS::RootedObject( m_context,
JS_NewGlobalObject( m_context, &global_class, nullptr, JS::FireOnNewGlobalHook ) );
//global = JS_NewGlobalObject( context, &global_class, nullptr, JS::FireOnNewGlobalHook );
Q_ASSERT( m_global.get() );
}
~MyMonkeyClass() {
auto runtime = JS_GetRuntime( m_context );
JS_DestroyContext( m_context );
JS_DestroyRuntime( runtime );
JS_ShutDown();
}
private:
JSContext *m_context;
JS::Heap<JSObject*> m_global;
};
int main( int, char** )
{
MyMonkeyClass mmc;
return 0;
}
在撰写这个问题时,我发现在销毁东西之前在 dtor 中设置 m_global = nullptr;
实际上可以避免崩溃。现在我的最后一个问题是:
这是正确的解决方法吗?如果是,为什么? SM 是否可能假设非空 JS::Heap 指针指向仍在使用的内存,因此它会出现恐慌?
您最初的问题是由错误的对象释放顺序引起的。
C++ class 以相反的构造顺序析构,即调用 class 析构函数后跟 class 成员析构函数。成员以其声明的相反顺序被销毁。这意味着在你的情况下:
- ~MyMonkeyClass() 进入
- JS_Destroy* 和 JS_ShutDown 被称为
- JS::~堆被调用。但是,JS 引擎已经消失,这会导致未定义的行为 - 在您的情况下会崩溃。
通过将 m_global 设置为 nullptr 可以防止 JS::~Heap() 下降到 JS API 来清理内存。这样可以避免崩溃,但可能会导致内存泄漏。
正确的解决方法是以正确的顺序清理所有内容。
一种方法是定义另一个 class "JsUpDown" 来初始化 JS 引擎,在构造函数中创建 JS 运行时和 JS 上下文并调用 JS_Destroy* 和 JS_ShutDown在析构函数中。然后 MyMonkeyClass 看起来像:
class MyMonkeyClass
{
MyMonkeyClass() : m_engine(), m_global() // just to make it explicit
{
JSAutoRequest ar(m_engine.m_context);
m_global = JS::RootedObject(...);
}
~MyMonkeyClass() {} // no need for the destructor now
JsUpDown m_engine; // Should be first. The order is important:
// Members are destroyed in reverse order.
JS::Heap<JSObject*> m_global;
};