Skip to content

Commit 90b2437

Browse files
authored
Add parserless build mode (#1021)
Add a QJS_DISABLE_PARSER build option that disables the JS source code parser. The JSON parser is unaffected. Useful if you want to execute bytecode but not parse source code, e.g., for hardening reasons, or due to running in a constrained environment. Shrinks a release build by about 15%. Fixes: #976
1 parent 28fa43d commit 90b2437

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,19 @@ jobs:
576576
run: |
577577
make jscheck
578578
579+
parserless:
580+
runs-on: ubuntu-latest
581+
steps:
582+
- uses: actions/checkout@v4
583+
- name: build
584+
run: |
585+
make QJS_DISABLE_PARSER=ON QJS_BUILD_EXAMPLES=ON
586+
- name: test
587+
run: |
588+
./build/hello
589+
./build/hello_module
590+
591+
579592
meson:
580593
runs-on: ${{ matrix.platform }}
581594
name: meson on ${{ matrix.platform }} (${{ matrix.mode.name }} ${{ matrix.flavor }}, ${{ matrix.features.name }})

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ xoption(QJS_BUILD_EXAMPLES "Build examples" OFF)
144144
xoption(QJS_BUILD_CLI_STATIC "Build a static qjs executable" OFF)
145145
xoption(QJS_BUILD_CLI_WITH_MIMALLOC "Build the qjs executable with mimalloc" OFF)
146146
xoption(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC "Build the qjs executable with mimalloc (statically linked)" OFF)
147+
xoption(QJS_DISABLE_PARSER "Disable JS source code parser" OFF)
147148
xoption(QJS_ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF)
148149
xoption(QJS_ENABLE_MSAN "Enable MemorySanitizer (MSan)" OFF)
149150
xoption(QJS_ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF)

quickjs.c

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3455,6 +3455,8 @@ const char *JS_AtomToCString(JSContext *ctx, JSAtom atom)
34553455
return cstr;
34563456
}
34573457

3458+
#ifndef QJS_DISABLE_PARSER
3459+
34583460
/* return a string atom containing name concatenated with str1 */
34593461
/* `str1` may be pure ASCII or UTF-8 encoded */
34603462
// TODO(chqrlie): use string concatenation instead of UTF-8 conversion
@@ -3497,6 +3499,8 @@ static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n)
34973499
return js_atom_concat_str(ctx, name, buf);
34983500
}
34993501

3502+
#endif // QJS_DISABLE_PARSER
3503+
35003504
static inline bool JS_IsEmptyString(JSValueConst v)
35013505
{
35023506
return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0;
@@ -20372,8 +20376,6 @@ static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = {
2037220376
opcode_info[(op) >= OP_TEMP_START ? \
2037320377
(op) + (OP_TEMP_END - OP_TEMP_START) : (op)]
2037420378

20375-
static __exception int next_token(JSParseState *s);
20376-
2037720379
static void free_token(JSParseState *s, JSToken *token)
2037820380
{
2037920381
switch(token->val) {
@@ -20480,6 +20482,10 @@ int JS_PRINTF_FORMAT_ATTR(2, 3) js_parse_error(JSParseState *s, JS_PRINTF_FORMAT
2048020482
return -1;
2048120483
}
2048220484

20485+
#ifndef QJS_DISABLE_PARSER
20486+
20487+
static __exception int next_token(JSParseState *s);
20488+
2048320489
static int js_parse_expect(JSParseState *s, int tok)
2048420490
{
2048520491
char buf[ATOM_GET_STR_BUF_SIZE];
@@ -20824,6 +20830,8 @@ static __exception int js_parse_regexp(JSParseState *s)
2082420830
return -1;
2082520831
}
2082620832

20833+
#endif // QJS_DISABLE_PARSER
20834+
2082720835
static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize,
2082820836
char *static_buf)
2082920837
{
@@ -20851,6 +20859,8 @@ static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize,
2085120859
return 0;
2085220860
}
2085320861

20862+
#ifndef QJS_DISABLE_PARSER
20863+
2085420864
/* convert a TOK_IDENT to a keyword when needed */
2085520865
static void update_token_ident(JSParseState *s)
2085620866
{
@@ -21425,6 +21435,8 @@ static __exception int next_token(JSParseState *s)
2142521435
return -1;
2142621436
}
2142721437

21438+
#endif // QJS_DISABLE_PARSER
21439+
2142821440
static int json_parse_error(JSParseState *s, const uint8_t *curp, const char *msg)
2142921441
{
2143021442
const uint8_t *p, *line_start;
@@ -21725,6 +21737,8 @@ static __exception int json_next_token(JSParseState *s)
2172521737
return -1;
2172621738
}
2172721739

21740+
#ifndef QJS_DISABLE_PARSER
21741+
2172821742
/* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is
2172921743
only set if TOK_IMPORT is returned */
2173021744
/* XXX: handle all unicode cases */
@@ -27641,6 +27655,8 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
2764127655
return -1;
2764227656
}
2764327657

27658+
#endif // QJS_DISABLE_PARSER
27659+
2764427660
/* 'name' is freed */
2764527661
static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
2764627662
{
@@ -27726,6 +27742,8 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
2772627742
js_free(ctx, m);
2772727743
}
2772827744

27745+
#ifndef QJS_DISABLE_PARSER
27746+
2772927747
static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
2773027748
JSAtom module_name)
2773127749
{
@@ -27750,6 +27768,8 @@ static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
2775027768
return i;
2775127769
}
2775227770

27771+
#endif // QJS_DISABLE_PARSER
27772+
2775327773
static JSExportEntry *find_export_entry(JSContext *ctx, const JSModuleDef *m,
2775427774
JSAtom export_name)
2775527775
{
@@ -27794,6 +27814,8 @@ static JSExportEntry *add_export_entry2(JSContext *ctx,
2779427814
return me;
2779527815
}
2779627816

27817+
#ifndef QJS_DISABLE_PARSER
27818+
2779727819
static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m,
2779827820
JSAtom local_name, JSAtom export_name,
2779927821
JSExportTypeEnum export_type)
@@ -27817,6 +27839,8 @@ static int add_star_export_entry(JSContext *ctx, JSModuleDef *m,
2781727839
return 0;
2781827840
}
2781927841

27842+
#endif // QJS_DISABLE_PARSER
27843+
2782027844
/* create a C module */
2782127845
/* `name_str` may be pure ASCII or UTF-8 encoded */
2782227846
JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str,
@@ -29465,6 +29489,8 @@ static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
2946529489
return js_dup(m->promise);
2946629490
}
2946729491

29492+
#ifndef QJS_DISABLE_PARSER
29493+
2946829494
static __exception JSAtom js_parse_from_clause(JSParseState *s)
2946929495
{
2947029496
JSAtom module_name;
@@ -29904,6 +29930,8 @@ static JSFunctionDef *js_new_function_def(JSContext *ctx,
2990429930
return fd;
2990529931
}
2990629932

29933+
#endif // QJS_DISABLE_PARSER
29934+
2990729935
static void free_bytecode_atoms(JSRuntime *rt,
2990829936
const uint8_t *bc_buf, int bc_len,
2990929937
bool use_short_opcodes)
@@ -29937,6 +29965,8 @@ static void free_bytecode_atoms(JSRuntime *rt,
2993729965
}
2993829966
}
2993929967

29968+
#ifndef QJS_DISABLE_PARSER
29969+
2994029970
static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
2994129971
{
2994229972
int i;
@@ -29999,6 +30029,8 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
2999930029
js_free(ctx, fd);
3000030030
}
3000130031

30032+
#endif // QJS_DISABLE_PARSER
30033+
3000230034
#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_*
3000330035
static const char *skip_lines(const char *p, int n) {
3000430036
while (p && n-- > 0 && *p) {
@@ -30455,6 +30487,8 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB
3045530487
}
3045630488
#endif
3045730489

30490+
#ifndef QJS_DISABLE_PARSER
30491+
3045830492
static int add_closure_var(JSContext *ctx, JSFunctionDef *s,
3045930493
bool is_local, bool is_arg,
3046030494
int var_idx, JSAtom var_name,
@@ -33762,8 +33796,10 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
3376233796
are used to compile the eval and they must be ordered by scope,
3376333797
so it is necessary to create the closure variables before any
3376433798
other variable lookup is done. */
33799+
#ifndef QJS_DISABLE_PARSER
3376533800
if (fd->has_eval_call)
3376633801
add_eval_variables(ctx, fd);
33802+
#endif // QJS_DISABLE_PARSER
3376733803

3376833804
/* add the module global variables in the closure */
3376933805
if (fd->module) {
@@ -33922,6 +33958,8 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
3392233958
return JS_EXCEPTION;
3392333959
}
3392433960

33961+
#endif // QJS_DISABLE_PARSER
33962+
3392533963
static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
3392633964
{
3392733965
int i;
@@ -33956,6 +33994,8 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
3395633994
}
3395733995
}
3395833996

33997+
#ifndef QJS_DISABLE_PARSER
33998+
3395933999
static __exception int js_parse_directives(JSParseState *s)
3396034000
{
3396134001
char str[20];
@@ -34767,6 +34807,8 @@ static __exception int js_parse_program(JSParseState *s)
3476734807
return 0;
3476834808
}
3476934809

34810+
#endif // QJS_DISABLE_PARSER
34811+
3477034812
static void js_parse_init(JSContext *ctx, JSParseState *s,
3477134813
const char *input, size_t input_len,
3477234814
const char *filename, int line)
@@ -34822,6 +34864,8 @@ JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj)
3482234864
return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL);
3482334865
}
3482434866

34867+
#ifndef QJS_DISABLE_PARSER
34868+
3482534869
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
3482634870
/* `export_name` and `input` may be pure ASCII or UTF-8 encoded */
3482734871
static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
@@ -34938,6 +34982,8 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
3493834982
return JS_EXCEPTION;
3493934983
}
3494034984

34985+
#endif // QJS_DISABLE_PARSER
34986+
3494134987
/* the indirection is needed to make 'eval' optional */
3494234988
static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
3494334989
const char *input, size_t input_len,
@@ -52859,7 +52905,9 @@ void JS_AddIntrinsicDate(JSContext *ctx)
5285952905

5286052906
void JS_AddIntrinsicEval(JSContext *ctx)
5286152907
{
52908+
#ifndef QJS_DISABLE_PARSER
5286252909
ctx->eval_internal = __JS_EvalInternal;
52910+
#endif // QJS_DISABLE_PARSER
5286352911
}
5286452912

5286552913
/* BigInt */
@@ -57526,6 +57574,7 @@ static void _JS_AddIntrinsicCallSite(JSContext *ctx)
5752657574

5752757575
bool JS_DetectModule(const char *input, size_t input_len)
5752857576
{
57577+
#ifndef QJS_DISABLE_PARSER
5752957578
JSRuntime *rt;
5753057579
JSContext *ctx;
5753157580
JSValue val;
@@ -57554,6 +57603,9 @@ bool JS_DetectModule(const char *input, size_t input_len)
5755457603
JS_FreeContext(ctx);
5755557604
JS_FreeRuntime(rt);
5755657605
return is_module;
57606+
#else
57607+
return false;
57608+
#endif // QJS_DISABLE_PARSER
5755757609
}
5755857610

5755957611
uintptr_t js_std_cmd(int cmd, ...) {

quickjs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ JS_EXTERN JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj,
903903
* wholly infallible: non-strict classic scripts may _parse_ okay as a module
904904
* but not _execute_ as one (different runtime semantics.) Use with caution.
905905
* |input| can be either ASCII or UTF-8 encoded source code.
906+
* Returns false if QuickJS was built with -DQJS_DISABLE_PARSER.
906907
*/
907908
JS_EXTERN bool JS_DetectModule(const char *input, size_t input_len);
908909
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */

0 commit comments

Comments
 (0)