Skip to content

Commit ff5cbb8

Browse files
authored
Fix ES module deserialization (#942)
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: #913
1 parent ccb996b commit ff5cbb8

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

api-test.c

Lines changed: 55 additions & 0 deletions
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,66 @@ 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+
free(buf);
222+
assert(loader_calls == 1); // 'b' is returned from cache
223+
assert(!JS_IsException(mod));
224+
JSValue ret = JS_EvalFunction(ctx, mod);
225+
assert(!JS_IsException(ret));
226+
assert(JS_IsPromise(ret));
227+
JSValue result = JS_PromiseResult(ctx, ret);
228+
assert(!JS_IsException(result));
229+
assert(JS_IsUndefined(result));
230+
JS_FreeValue(ctx, result);
231+
JS_FreeValue(ctx, ret);
232+
JS_FreeValue(ctx, mod);
233+
JS_FreeContext(ctx);
234+
JS_FreeRuntime(rt);
235+
}
236+
183237
int main(void)
184238
{
185239
sync_call();
186240
async_call();
187241
async_call_stack_overflow();
188242
raw_context_global_var();
189243
is_array();
244+
module_serde();
190245
return 0;
191246
}

quickjs.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35150,8 +35150,21 @@ 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+
// Resolves a module either from the cache or by requesting
35157+
// it from the module loader. From cache is not ideal because
35158+
// the module may not be the one it was a time of serialization
35159+
// but directly petitioning the module loader is not correct
35160+
// either because then the same module can get loaded twice.
35161+
// JS_WriteModule() does not serialize modules transitively
35162+
// because that doesn't work for C modules and is also prone
35163+
// to loading the same JS module twice.
35164+
*pm = js_host_resolve_imported_module_atom(s->ctx, m->module_name,
35165+
rme->module_name);
35166+
if (!*pm)
35167+
goto fail;
3515535168
}
3515635169
}
3515735170

quickjs.h

Lines changed: 5 additions & 0 deletions
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)