diff --git a/Makefile b/Makefile index a3abc98..77886dd 100644 --- a/Makefile +++ b/Makefile @@ -296,7 +296,7 @@ libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS)) $(AR) rcs $@ $^ repl.c: $(QJSC) repl.js - $(QJSC) -c -o $@ -m repl.js + $(QJSC) -s -c -o $@ -m repl.js ifneq ($(wildcard unicode/UnicodeData.txt),) $(OBJDIR)/libunicode.o $(OBJDIR)/libunicode.nolto.o: libunicode-table.h diff --git a/doc/quickjs.texi b/doc/quickjs.texi index cf76ff5..f9ffab4 100644 --- a/doc/quickjs.texi +++ b/doc/quickjs.texi @@ -264,16 +264,6 @@ The following features are not supported yet: ECMA402 (Internationalization API) is not supported. -@subsection Extensions - -@itemize - -@item The directive @code{"use strip"} indicates that the debug information (including the source code of the functions) should not be retained to save memory. As @code{"use strict"}, the directive can be global to a script or local to a function. - -@item The first line of a script beginning with @code{#!} is ignored. - -@end itemize - @section Modules ES6 modules are fully supported. The default name resolution is the diff --git a/qjs.c b/qjs.c index b6bfca3..2eaa9ee 100644 --- a/qjs.c +++ b/qjs.c @@ -301,6 +301,8 @@ void help(void) " --memory-limit n limit the memory usage to 'n' bytes (SI suffixes allowed)\n" " --stack-size n limit the stack size to 'n' bytes (SI suffixes allowed)\n" " --no-unhandled-rejection ignore unhandled promise rejections\n" + "-s strip all the debug info\n" + " --strip-source strip the source code\n" "-q --quit just instantiate the interpreter and quit\n"); exit(1); } @@ -322,6 +324,7 @@ int main(int argc, char **argv) size_t memory_limit = 0; char *include_list[32]; int i, include_count = 0; + int strip_flags = 0; size_t stack_size = 0; /* cannot use getopt because we want to pass the command line to @@ -421,6 +424,14 @@ int main(int argc, char **argv) stack_size = get_suffixed_size(argv[optind++]); continue; } + if (opt == 's') { + strip_flags = JS_STRIP_DEBUG; + continue; + } + if (!strcmp(longopt, "strip-source")) { + strip_flags = JS_STRIP_SOURCE; + continue; + } if (opt) { fprintf(stderr, "qjs: unknown option '-%c'\n", opt); } else { @@ -444,6 +455,7 @@ int main(int argc, char **argv) JS_SetMemoryLimit(rt, memory_limit); if (stack_size != 0) JS_SetMaxStackSize(rt, stack_size); + JS_SetStripInfo(rt, strip_flags); js_std_set_worker_new_context_func(JS_NewCustomContext); js_std_init_handlers(rt); ctx = JS_NewCustomContext(rt); diff --git a/qjsc.c b/qjsc.c index de8ebd1..13e6fa5 100644 --- a/qjsc.c +++ b/qjsc.c @@ -352,7 +352,9 @@ void help(void) "-M module_name[,cname] add initialization code for an external C module\n" "-x byte swapped output\n" "-p prefix set the prefix of the generated C names\n" - "-S n set the maximum stack size to 'n' bytes (default=%d)\n", + "-S n set the maximum stack size to 'n' bytes (default=%d)\n" + "-s strip all the debug info\n" + "--keep-source keep the source code\n", JS_DEFAULT_STACK_SIZE); #ifdef CONFIG_LTO { @@ -471,6 +473,31 @@ static int output_executable(const char *out_filename, const char *cfilename, } #endif +static size_t get_suffixed_size(const char *str) +{ + char *p; + size_t v; + v = (size_t)strtod(str, &p); + switch(*p) { + case 'G': + v <<= 30; + break; + case 'M': + v <<= 20; + break; + case 'k': + case 'K': + v <<= 10; + break; + default: + if (*p != '\0') { + fprintf(stderr, "qjs: invalid suffix: %s\n", p); + exit(1); + } + break; + } + return v; +} typedef enum { OUTPUT_C, @@ -478,9 +505,24 @@ typedef enum { OUTPUT_EXECUTABLE, } OutputTypeEnum; +static const char *get_short_optarg(int *poptind, int opt, + const char *arg, int argc, char **argv) +{ + const char *optarg; + if (*arg) { + optarg = arg; + } else if (*poptind < argc) { + optarg = argv[(*poptind)++]; + } else { + fprintf(stderr, "qjsc: expecting parameter for -%c\n", opt); + exit(1); + } + return optarg; +} + int main(int argc, char **argv) { - int c, i, verbose; + int i, verbose, strip_flags; const char *out_filename, *cname; char cfilename[1024]; FILE *fo; @@ -499,6 +541,7 @@ int main(int argc, char **argv) module = -1; byte_swap = FALSE; verbose = 0; + strip_flags = JS_STRIP_SOURCE; use_lto = FALSE; stack_size = 0; memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); @@ -507,30 +550,51 @@ int main(int argc, char **argv) namelist_add(&cmodule_list, "std", "std", 0); namelist_add(&cmodule_list, "os", "os", 0); - for(;;) { - c = getopt(argc, argv, "ho:cN:f:mxevM:p:S:D:"); - if (c == -1) + optind = 1; + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + const char *optarg; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) break; - switch(c) { - case 'h': - help(); - case 'o': - out_filename = optarg; - break; - case 'c': - output_type = OUTPUT_C; - break; - case 'e': - output_type = OUTPUT_C_MAIN; - break; - case 'N': - cname = optarg; - break; - case 'f': - { + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'o') { + out_filename = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'c') { + output_type = OUTPUT_C; + continue; + } + if (opt == 'e') { + output_type = OUTPUT_C_MAIN; + continue; + } + if (opt == 'N') { + cname = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'f') { const char *p; + optarg = get_short_optarg(&optind, opt, arg, argc, argv); p = optarg; - if (!strcmp(optarg, "lto")) { + if (!strcmp(p, "lto")) { use_lto = TRUE; } else if (strstart(p, "no-", &p)) { use_lto = TRUE; @@ -547,16 +611,18 @@ int main(int argc, char **argv) fprintf(stderr, "unsupported feature: %s\n", optarg); exit(1); } + break; } - break; - case 'm': - module = 1; - break; - case 'M': - { + if (opt == 'm') { + module = 1; + continue; + } + if (opt == 'M') { char *p; char path[1024]; char cname[1024]; + + optarg = get_short_optarg(&optind, opt, arg, argc, argv); pstrcpy(path, sizeof(path), optarg); p = strchr(path, ','); if (p) { @@ -566,25 +632,44 @@ int main(int argc, char **argv) get_c_name(cname, sizeof(cname), path); } namelist_add(&cmodule_list, path, cname, 0); + break; } - break; - case 'D': - namelist_add(&dynamic_module_list, optarg, NULL, 0); - break; - case 'x': - byte_swap = TRUE; - break; - case 'v': - verbose++; - break; - case 'p': - c_ident_prefix = optarg; - break; - case 'S': - stack_size = (size_t)strtod(optarg, NULL); - break; - default: - break; + if (opt == 'D') { + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + namelist_add(&dynamic_module_list, optarg, NULL, 0); + break; + } + if (opt == 'x') { + byte_swap = 1; + continue; + } + if (opt == 'v') { + verbose++; + continue; + } + if (opt == 'p') { + c_ident_prefix = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'S') { + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + stack_size = get_suffixed_size(optarg); + break; + } + if (opt == 's') { + strip_flags = JS_STRIP_DEBUG; + continue; + } + if (!strcmp(longopt, "keep-source")) { + strip_flags = 0; + continue; + } + if (opt) { + fprintf(stderr, "qjsc: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjsc: unknown option '--%s'\n", longopt); + } + help(); } } @@ -620,6 +705,8 @@ int main(int argc, char **argv) rt = JS_NewRuntime(); ctx = JS_NewContext(rt); + JS_SetStripInfo(rt, strip_flags); + /* loader for ES6 modules */ JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL); diff --git a/quickjs-libc.c b/quickjs-libc.c index 2c71132..a9fff6a 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3220,6 +3220,7 @@ typedef struct { char *filename; /* module filename */ char *basename; /* module base name */ JSWorkerMessagePipe *recv_pipe, *send_pipe; + int strip_flags; } WorkerFuncArgs; typedef struct { @@ -3367,6 +3368,7 @@ static void *worker_func(void *opaque) fprintf(stderr, "JS_NewRuntime failure"); exit(1); } + JS_SetStripInfo(rt, args->strip_flags); js_std_init_handlers(rt); JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); @@ -3484,6 +3486,8 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, if (!args->send_pipe) goto oom_fail; + args->strip_flags = JS_GetStripInfo(rt); + obj = js_worker_ctor_internal(ctx, new_target, args->send_pipe, args->recv_pipe); if (JS_IsException(obj)) diff --git a/quickjs.c b/quickjs.c index eb8ae6a..8fb5f65 100644 --- a/quickjs.c +++ b/quickjs.c @@ -285,7 +285,9 @@ struct JSRuntime { BOOL can_block : 8; /* TRUE if Atomics.wait can block */ /* used to allocate, free and clone SharedArrayBuffers */ JSSharedArrayBufferFunctions sab_funcs; - + /* see JS_SetStripInfo() */ + uint8_t strip_flags; + /* Shape hash table */ int shape_hash_bits; int shape_hash_size; @@ -305,7 +307,6 @@ struct JSClass { }; #define JS_MODE_STRICT (1 << 0) -#define JS_MODE_STRIP (1 << 1) #define JS_MODE_ASYNC (1 << 2) /* async function */ #define JS_MODE_BACKTRACE_BARRIER (1 << 3) /* stop backtrace before this frame */ @@ -633,9 +634,9 @@ typedef struct JSFunctionBytecode { /* debug info, move to separate structure to save memory? */ JSAtom filename; int line_num; - int source_len; int pc2line_len; uint8_t *pc2line_buf; + int source_len; char *source; } debug; } JSFunctionBytecode; @@ -1723,6 +1724,16 @@ void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, rt->sab_funcs = *sf; } +void JS_SetStripInfo(JSRuntime *rt, int flags) +{ + rt->strip_flags = flags; +} + +int JS_GetStripInfo(JSRuntime *rt) +{ + return rt->strip_flags; +} + /* return 0 if OK, < 0 if exception */ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValueConst *argv) @@ -19957,6 +19968,8 @@ typedef struct JSFunctionDef { int line_number_last_pc; /* pc2line table */ + BOOL strip_debug : 1; /* strip all debug info (implies strip_source = TRUE) */ + BOOL strip_source : 1; /* strip only source code */ JSAtom filename; int line_num; DynBuf pc2line; @@ -23242,7 +23255,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx); /* store the class source code in the constructor. */ - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { js_free(ctx, ctor_fd->source); ctor_fd->source_len = s->buf_ptr - class_start_ptr; ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr, @@ -29313,6 +29326,8 @@ static JSFunctionDef *js_new_function_def(JSContext *ctx, fd->js_mode = parent->js_mode; fd->parent_scope_level = parent->scope_level; } + fd->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0); + fd->strip_source = ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0); fd->is_eval = is_eval; fd->is_func_expr = is_func_expr; @@ -31744,7 +31759,7 @@ static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num) static void compute_pc2line_info(JSFunctionDef *s) { - if (!(s->js_mode & JS_MODE_STRIP) && s->line_number_slots) { + if (!s->strip_debug && s->line_number_slots) { int last_line_num = s->line_num; uint32_t last_pc = 0; int i; @@ -31985,7 +32000,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) } #endif /* XXX: Should skip this phase if not generating SHORT_OPCODES */ - if (s->line_number_size && !(s->js_mode & JS_MODE_STRIP)) { + if (s->line_number_size && !s->strip_debug) { s->line_number_slots = js_mallocz(s->ctx, sizeof(*s->line_number_slots) * s->line_number_size); if (s->line_number_slots == NULL) return -1; @@ -33214,7 +33229,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) } #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!s->strip_debug) { printf("pass 1\n"); dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, @@ -33229,7 +33244,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) goto fail; #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!s->strip_debug) { printf("pass 2\n"); dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, @@ -33246,7 +33261,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) if (compute_stack_size(ctx, fd, &stack_size) < 0) goto fail; - if (fd->js_mode & JS_MODE_STRIP) { + if (fd->strip_debug) { function_size = offsetof(JSFunctionBytecode, debug); } else { function_size = sizeof(*b); @@ -33254,7 +33269,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) cpool_offset = function_size; function_size += fd->cpool_count * sizeof(*fd->cpool); vardefs_offset = function_size; - if (!(fd->js_mode & JS_MODE_STRIP) || fd->has_eval_call) { + if (!fd->strip_debug || fd->has_eval_call) { function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs); } closure_var_offset = function_size; @@ -33275,7 +33290,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b->func_name = fd->func_name; if (fd->arg_count + fd->var_count > 0) { - if ((fd->js_mode & JS_MODE_STRIP) && !fd->has_eval_call) { + if (fd->strip_debug && !fd->has_eval_call) { /* Strip variable definitions not needed at runtime */ int i; for(i = 0; i < fd->var_count; i++) { @@ -33309,7 +33324,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b->stack_size = stack_size; - if (fd->js_mode & JS_MODE_STRIP) { + if (fd->strip_debug) { JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); // probably useless } else { @@ -33359,7 +33374,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!s->strip_debug) { js_dump_function_bytecode(ctx, b); } #endif @@ -33501,11 +33516,6 @@ static __exception int js_parse_directives(JSParseState *s) s->cur_func->has_use_strict = TRUE; s->cur_func->js_mode |= JS_MODE_STRICT; } -#if !defined(DUMP_BYTECODE) || !(DUMP_BYTECODE & 8) - else if (!strcmp(str, "use strip")) { - s->cur_func->js_mode |= JS_MODE_STRIP; - } -#endif } return js_parse_seek_token(s, &pos); } @@ -33988,7 +33998,7 @@ static __exception int js_parse_function_decl2(JSParseState *s, else emit_op(s, OP_return); - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { /* save the function source code */ /* the end of the function source code is after the last token of the function source stored into s->last_ptr */ @@ -34017,7 +34027,7 @@ static __exception int js_parse_function_decl2(JSParseState *s, if (js_parse_source_element(s)) goto fail; } - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { /* save the function source code */ fd->source_len = s->buf_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); @@ -34304,8 +34314,6 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, js_mode = 0; if (flags & JS_EVAL_FLAG_STRICT) js_mode |= JS_MODE_STRICT; - if (flags & JS_EVAL_FLAG_STRIP) - js_mode |= JS_MODE_STRIP; if (eval_type == JS_EVAL_TYPE_MODULE) { JSAtom module_name = JS_NewAtom(ctx, filename); if (module_name == JS_ATOM_NULL) @@ -34982,6 +34990,12 @@ static int JS_WriteFunctionTag(BCWriterState *s, JSValueConst obj) bc_put_leb128(s, b->debug.line_num); bc_put_leb128(s, b->debug.pc2line_len); dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); + if (b->debug.source) { + bc_put_leb128(s, b->debug.source_len); + dbuf_put(&s->dbuf, (uint8_t *)b->debug.source, b->debug.source_len); + } else { + bc_put_leb128(s, 0); + } } for(i = 0; i < b->cpool_count; i++) { @@ -35936,6 +35950,9 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) goto fail; if (bc_get_leb128_int(s, &b->debug.line_num)) goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf(" line: %d\n", b->debug.line_num); +#endif if (bc_get_leb128_int(s, &b->debug.pc2line_len)) goto fail; if (b->debug.pc2line_len) { @@ -35945,9 +35962,16 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) goto fail; } -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); -#endif + if (bc_get_leb128_int(s, &b->debug.source_len)) + goto fail; + if (b->debug.source_len) { + bc_read_trace(s, "source: %d bytes\n", b->source_len); + b->debug.source = js_mallocz(ctx, b->debug.source_len); + if (!b->debug.source) + goto fail; + if (bc_get_buf(s, (uint8_t *)b->debug.source, b->debug.source_len)) + goto fail; + } bc_read_trace(s, "}\n"); } if (b->cpool_count != 0) { diff --git a/quickjs.h b/quickjs.h index 2d4b1f6..a75d990 100644 --- a/quickjs.h +++ b/quickjs.h @@ -334,7 +334,6 @@ static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int64_t d) #define JS_EVAL_TYPE_MASK (3 << 0) #define JS_EVAL_FLAG_STRICT (1 << 3) /* force 'strict' mode */ -#define JS_EVAL_FLAG_STRIP (1 << 4) /* force 'strip' mode */ /* compile but do not run. The result is an object with a JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed with JS_EvalFunction(). */ @@ -902,6 +901,12 @@ typedef int JSInterruptHandler(JSRuntime *rt, void *opaque); void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque); /* if can_block is TRUE, Atomics.wait() can be used */ void JS_SetCanBlock(JSRuntime *rt, JS_BOOL can_block); +/* select which debug info is stripped from the compiled code */ +#define JS_STRIP_SOURCE (1 << 0) /* strip source code */ +#define JS_STRIP_DEBUG (1 << 1) /* strip all debug info including source code */ +void JS_SetStripInfo(JSRuntime *rt, int flags); +int JS_GetStripInfo(JSRuntime *rt); + /* set the [IsHTMLDDA] internal slot */ void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj); diff --git a/repl.js b/repl.js index 3f8393a..c65100d 100644 --- a/repl.js +++ b/repl.js @@ -22,8 +22,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -"use strip"; - import * as std from "std"; import * as os from "os"; diff --git a/run-test262.c b/run-test262.c index 4afb3f8..a42b9b5 100644 --- a/run-test262.c +++ b/run-test262.c @@ -1571,7 +1571,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, for (i = 0; i < ip->count; i++) { if (eval_file(ctx, harness, ip->array[i], - JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRIP)) { + JS_EVAL_TYPE_GLOBAL)) { fatal(1, "error including %s for %s", ip->array[i], filename); } }