Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0128a59

Browse files
committedMar 2, 2025
Fix ES module deserialization
A module's imports are not serialized along with the module itself and that left the deserialized module with dangling references. Fix that by checking the module cache first, the module loader second. A glaring problem with cache checking is that the cached module doesn't have to be the module it was at the time of serialization. Why not call out to the module loader right away? Because then a module can get loaded twice and that's arguably even worse. The alternative of serializing modules transitively doesn't work for C modules and is also prone to loading the same module twice. Fixes: quickjs-ng#913
1 parent 53c3a2d commit 0128a59

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed
 

‎api-test.c

+54
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#endif
44
#include <assert.h>
55
#include <stdlib.h>
6+
#include <string.h>
67
#include "quickjs.h"
78

89
#define MAX_TIME 10
@@ -180,12 +181,65 @@ static void is_array(void)
180181
JS_FreeRuntime(rt);
181182
}
182183

184+
static int loader_calls;
185+
186+
static JSModuleDef *loader(JSContext *ctx, const char *name, void *opaque)
187+
{
188+
loader_calls++;
189+
assert(!strcmp(name, "b"));
190+
static const char code[] = "export function f(x){}";
191+
JSValue ret = JS_Eval(ctx, code, strlen(code), "b",
192+
JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY);
193+
assert(!JS_IsException(ret));
194+
JSModuleDef *m = JS_VALUE_GET_PTR(ret);
195+
assert(m);
196+
JS_FreeValue(ctx, ret);
197+
return m;
198+
}
199+
200+
static void module_serde(void)
201+
{
202+
JSRuntime *rt = JS_NewRuntime();
203+
JS_SetDumpFlags(rt, JS_DUMP_MODULE_RESOLVE);
204+
JS_SetModuleLoaderFunc(rt, NULL, loader, NULL);
205+
JSContext *ctx = JS_NewContext(rt);
206+
static const char code[] = "import {f} from 'b'; f()";
207+
assert(loader_calls == 0);
208+
JSValue mod = JS_Eval(ctx, code, strlen(code), "a",
209+
JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY);
210+
assert(loader_calls == 1);
211+
assert(!JS_IsException(mod));
212+
assert(JS_IsModule(mod));
213+
size_t len = 0;
214+
uint8_t *buf = JS_WriteObject(ctx, &len, mod,
215+
JS_WRITE_OBJ_BYTECODE|JS_WRITE_OBJ_REFERENCE);
216+
assert(buf);
217+
assert(len > 0);
218+
JS_FreeValue(ctx, mod);
219+
assert(loader_calls == 1);
220+
mod = JS_ReadObject(ctx, buf, len, JS_READ_OBJ_BYTECODE);
221+
assert(loader_calls == 1); // 'b' is returned from cache
222+
assert(!JS_IsException(mod));
223+
JSValue ret = JS_EvalFunction(ctx, mod);
224+
assert(!JS_IsException(ret));
225+
assert(JS_IsPromise(ret));
226+
JSValue result = JS_PromiseResult(ctx, ret);
227+
assert(!JS_IsException(result));
228+
assert(JS_IsUndefined(result));
229+
JS_FreeValue(ctx, result);
230+
JS_FreeValue(ctx, ret);
231+
JS_FreeValue(ctx, mod);
232+
JS_FreeContext(ctx);
233+
JS_FreeRuntime(rt);
234+
}
235+
183236
int main(void)
184237
{
185238
sync_call();
186239
async_call();
187240
async_call_stack_overflow();
188241
raw_context_global_var();
189242
is_array();
243+
module_serde();
190244
return 0;
191245
}

‎quickjs.c

+5
Original file line numberDiff line numberDiff line change
@@ -35150,8 +35150,13 @@ static JSValue JS_ReadModule(BCReaderState *s)
3515035150
goto fail;
3515135151
for(i = 0; i < m->req_module_entries_count; i++) {
3515235152
JSReqModuleEntry *rme = &m->req_module_entries[i];
35153+
JSModuleDef **pm = &rme->module;
3515335154
if (bc_get_atom(s, &rme->module_name))
3515435155
goto fail;
35156+
*pm = js_host_resolve_imported_module_atom(s->ctx, m->module_name,
35157+
rme->module_name);
35158+
if (!*pm)
35159+
goto fail;
3515535160
}
3515635161
}
3515735162

‎quickjs.h

+5
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,11 @@ static inline bool JS_IsObject(JSValue v)
662662
return JS_VALUE_GET_TAG(v) == JS_TAG_OBJECT;
663663
}
664664

665+
static inline bool JS_IsModule(JSValue v)
666+
{
667+
return JS_VALUE_GET_TAG(v) == JS_TAG_MODULE;
668+
}
669+
665670
JS_EXTERN JSValue JS_Throw(JSContext *ctx, JSValue obj);
666671
JS_EXTERN JSValue JS_GetException(JSContext *ctx);
667672
JS_EXTERN bool JS_HasException(JSContext *ctx);

0 commit comments

Comments
 (0)
Please sign in to comment.