QuickJS调用JS_Eval运行模块脚本时触发段错误如何解决
QuickJS 加载ES模块触发段错误修复方案
这个是QuickJS宿主开发的经典新手坑,核心是漏了官方qjs启动流程里几个必须的初始化和执行步骤,和你写的逻辑差异点如下,改完就能跑:
- 第一,
JS_Eval用*JS_EVAL_TYPE_MODULE*模式时,必须接收返回值并做异常判断。这个模式下返回值是编译后的模块对象,虽然漏接返回值不会直接崩,但完全跳过异常检查的话,内部一旦触发错误就会走到非法内存访问,直接段错误,连错误提示都拿不到。 - 第二,光调用
js_init_module_std、js_init_module_os不够,必须先给runtime注册模块加载器。QuickJS解析到import语句时会调用注册的加载器函数找对应模块,没注册的话这个函数指针是空,调用直接跳0地址,这就是你现在段错误的直接原因。QuickJS源码自带内置模块加载器初始化函数,创建完runtime直接调js_module_loader_init(jsr.get(), NULL);就行,不用自己写加载逻辑,官方qjs启动第一时间就会调这个接口。 - 第三,模块模式下
JS_Eval只做编译和依赖解析,不会直接执行代码。eval完成后必须循环调用JS_ExecutePendingJob清空待执行任务队列,模块顶层代码才会真正跑起来,不然连第一行print都出不来。 - 额外细节:
js_load_file返回的文件buffer是堆上分配的,用完要记得用js_free释放,所有JSValue类型的返回值用完也要调JS_FreeValue释放,不然会有内存泄漏。
修复后的可运行代码片段:
auto jsr = shared_ptr<JSRuntime>(JS_NewRuntime(), JS_FreeRuntime); // 初始化内置模块加载器,必须在创建context之前调用 js_module_loader_init(jsr.get(), NULL); for (auto &f : files){ auto ctx = shared_ptr<JSContext>(JS_NewContext(jsr.get()), JS_FreeContext); // 注册内置std、os模块 js_init_module_os(ctx.get(), "os"); js_init_module_std(ctx.get(), "std"); size_t bufLen = 0; auto buf = js_load_file(ctx.get(), &bufLen, f.c_str()); if (!buf) { cout << "Load script failed: " << f << endl; continue; } cout << "Starting Evaluation\n"; JSValue eval_ret = JS_Eval(ctx.get(), (char*)buf, bufLen, f.c_str(), JS_EVAL_TYPE_MODULE); js_free(ctx.get(), buf); // 释放文件内容内存 if (JS_IsException(eval_ret)) { JSValue err = JS_GetException(ctx.get()); const char* err_msg = JS_ToCString(ctx.get(), err); cout << "Compile error: " << err_msg << endl; JS_FreeCString(ctx.get(), err_msg); JS_FreeValue(ctx.get(), err); JS_FreeValue(ctx.get(), eval_ret); continue; } JS_FreeValue(ctx.get(), eval_ret); // 跑任务队列,执行模块代码,等待异步任务完成 int job_ret; JSContext* job_ctx; while (JS_IsPendingJob(jsr.get())) { job_ret = JS_ExecutePendingJob(jsr.get(), &job_ctx); if (job_ret <= 0) { JSValue runtime_err = JS_GetException(job_ctx); const char* err_msg = JS_ToCString(job_ctx, runtime_err); cout << "Runtime error: " << err_msg << endl; JS_FreeCString(job_ctx, err_msg); JS_FreeValue(job_ctx, runtime_err); break; } } cout << "Ending Evaluation\n"; }
改完之后你那个测试脚本就能和官方qjs跑的效果一样,每秒打印一次计数,直到循环跑完。注意你用的os模块的sleep是异步函数,任务队列循环会等所有异步任务执行完才退出,不用额外加事件循环逻辑。
内容的提问来源于stack exchange,提问作者JvJ
相关产品推荐
相关产品推荐

