Improve JS_DetectModule (#610)

It's still not infallible (I don't think it can ever be, the whole
premise is wrong) but hopefully it's a little less fallible now.

Fixes: https://github.com/quickjs-ng/quickjs/issues/606
This commit is contained in:
Ben Noordhuis 2024-10-20 12:42:21 +02:00 committed by GitHub
parent bed51fab0a
commit 8cd59bf7c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 54 additions and 33 deletions

Binary file not shown.

Binary file not shown.

View file

@ -20250,34 +20250,6 @@ static void skip_shebang(const uint8_t **pp, const uint8_t *buf_end)
} }
} }
/* return true if 'input' contains the source of a module
(heuristic). 'input' must be a zero terminated.
Heuristic:
- Skip comments
- Expect 'import' keyword not followed by '(' or '.'
- Expect 'export' keyword
- Expect 'await' keyword
*/
/* input is pure ASCII or UTF-8 encoded source code */
BOOL JS_DetectModule(const char *input, size_t input_len)
{
const uint8_t *p = (const uint8_t *)input;
int tok;
skip_shebang(&p, p + input_len);
switch(simple_next_token(&p, FALSE)) {
case TOK_IMPORT:
tok = simple_next_token(&p, FALSE);
return (tok != '.' && tok != '(');
case TOK_AWAIT:
case TOK_EXPORT:
return TRUE;
default:
return FALSE;
}
}
static inline int get_prev_opcode(JSFunctionDef *fd) { static inline int get_prev_opcode(JSFunctionDef *fd) {
if (fd->last_opcode_pos < 0) if (fd->last_opcode_pos < 0)
return OP_invalid; return OP_invalid;
@ -26380,6 +26352,7 @@ static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx,
/* load the module */ /* load the module */
if (!rt->module_loader_func) { if (!rt->module_loader_func) {
/* XXX: use a syntax error ? */ /* XXX: use a syntax error ? */
// XXX: update JS_DetectModule when you change this
JS_ThrowReferenceError(ctx, "could not load module '%s'", JS_ThrowReferenceError(ctx, "could not load module '%s'",
cname); cname);
js_free(ctx, cname); js_free(ctx, cname);
@ -54702,6 +54675,38 @@ static void _JS_AddIntrinsicCallSite(JSContext *ctx)
countof(js_callsite_proto_funcs)); countof(js_callsite_proto_funcs));
} }
BOOL JS_DetectModule(const char *input, size_t input_len)
{
JSRuntime *rt;
JSContext *ctx;
JSValue val;
BOOL is_module;
is_module = TRUE;
rt = JS_NewRuntime();
if (!rt)
return FALSE;
ctx = JS_NewContextRaw(rt);
if (!ctx) {
JS_FreeRuntime(rt);
return FALSE;
}
JS_AddIntrinsicRegExp(ctx); // otherwise regexp literals don't parse
val = __JS_EvalInternal(ctx, JS_UNDEFINED, input, input_len, "<unnamed>",
JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY, -1);
if (JS_IsException(val)) {
const char *msg = JS_ToCString(ctx, rt->current_exception);
// gruesome hack to recognize exceptions from import statements;
// necessary because we don't pass in a module loader
is_module = !!strstr(msg, "ReferenceError: could not load module");
JS_FreeCString(ctx, msg);
}
JS_FreeValue(ctx, val);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return is_module;
}
#undef malloc #undef malloc
#undef free #undef free
#undef realloc #undef realloc

View file

@ -693,6 +693,12 @@ JS_EXTERN JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj,
JS_EXTERN JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj, JS_EXTERN JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj,
JSValue new_target, JSValue new_target,
int argc, JSValue *argv); int argc, JSValue *argv);
/* Try to detect if the input is a module. Returns TRUE if parsing the input
* as a module produces no syntax errors. It's a naive approach that is not
* wholly infallible: non-strict classic scripts may _parse_ okay as a module
* but not _execute_ as one (different runtime semantics.) Use with caution.
* |input| can be either ASCII or UTF-8 encoded source code.
*/
JS_EXTERN JS_BOOL JS_DetectModule(const char *input, size_t input_len); JS_EXTERN JS_BOOL JS_DetectModule(const char *input, size_t input_len);
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ /* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
JS_EXTERN JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, JS_EXTERN JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,

1
tests/detect_module/0.js Normal file
View file

@ -0,0 +1 @@
await undefined

2
tests/detect_module/1.js Normal file
View file

@ -0,0 +1,2 @@
const p = Promise.resolve(42)
await p

1
tests/detect_module/2.js Normal file
View file

@ -0,0 +1 @@
await = 42 // parsed as classic script

8
tests/detect_module/3.js Normal file
View file

@ -0,0 +1,8 @@
/*---
negative:
phase: parse
type: SyntaxError
---*/
// the import statement makes it a module but `await = 42` is a SyntaxError
import * as _ from "dummy"
await = 42

3
tests/detect_module/4.js Normal file
View file

@ -0,0 +1,3 @@
// imports should classify it as a module, even when not at the top
os.now()
import * as os from "os"

View file

@ -1,5 +0,0 @@
// This needs to be parsed as a module or will throw SyntaxError.
//
await 0;