diff --git a/Makefile b/Makefile index 1239e0efb..854ca00e0 100644 --- a/Makefile +++ b/Makefile @@ -251,7 +251,9 @@ all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o -QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) +QJS_BC_OBJS=$(OBJDIR)/quickjs-bytecode.o + +QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_BC_OBJS) $(QJS_LIB_OBJS) HOST_LIBS=-lm -ldl -lpthread LIBS=-lm -lpthread @@ -269,7 +271,7 @@ qjs$(EXE): $(QJS_OBJS) qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS)) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS) +qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_BC_OBJS) $(QJS_LIB_OBJS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) fuzz_eval: $(OBJDIR)/fuzz_eval.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a @@ -286,7 +288,7 @@ libfuzzer: fuzz_eval fuzz_compile fuzz_regexp ifneq ($(CROSS_PREFIX),) $(QJSC): $(OBJDIR)/qjsc.host.o \ - $(patsubst %.o, %.host.o, $(QJS_LIB_OBJS)) + $(patsubst %.o, %.host.o, $(QJS_BC_OBJS) $(QJS_LIB_OBJS)) $(HOST_CC) $(LDFLAGS) -o $@ $^ $(HOST_LIBS) endif #CROSS_PREFIX @@ -369,7 +371,7 @@ clean: rm -f repl.c out.c rm -f *.a *.o *.d *~ unicode_gen regexp_test fuzz_eval fuzz_compile fuzz_regexp $(PROGS) rm -f hello.c test_fib.c - rm -f examples/*.so tests/*.so + rm -f examples/*.so tests/*.so tests/*.qbc examples/*.qbc rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug$(EXE) rm -rf run-test262-debug$(EXE) rm -f run_octane run_sunspider_like @@ -452,21 +454,44 @@ ifdef CONFIG_SHARED_LIBS test: tests/bjson.so examples/point.so endif -test: qjs$(EXE) +test: qjs$(EXE) qjsc$(EXE) $(WINE) ./qjs$(EXE) tests/test_closure.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_closure.qbc tests/test_closure.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_closure.qbc $(WINE) ./qjs$(EXE) tests/test_language.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_language.qbc tests/test_language.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_language.qbc $(WINE) ./qjs$(EXE) --std tests/test_builtin.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_builtin.qbc tests/test_builtin.js + $(WINE) ./qjs$(EXE) --std --bytecode tests/test_builtin.qbc $(WINE) ./qjs$(EXE) tests/test_loop.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_loop.qbc tests/test_loop.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_loop.qbc $(WINE) ./qjs$(EXE) tests/test_bigint.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_bigint.qbc tests/test_bigint.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_bigint.qbc $(WINE) ./qjs$(EXE) tests/test_cyclic_import.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_cyclic_import.qbc tests/test_cyclic_import.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_cyclic_import.qbc $(WINE) ./qjs$(EXE) tests/test_worker.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_worker.qbc tests/test_worker.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_worker.qbc ifndef CONFIG_WIN32 $(WINE) ./qjs$(EXE) tests/test_std.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_std.qbc tests/test_std.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_std.qbc $(WINE) ./qjs$(EXE) tests/test_rw_handler.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_rw_handler.qbc tests/test_rw_handler.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_rw_handler.qbc + ./tests/test_bytecode_file.sh endif ifdef CONFIG_SHARED_LIBS $(WINE) ./qjs$(EXE) tests/test_bjson.js + $(WINE) ./qjsc$(EXE) --bytecode -o tests/test_bjson.qbc tests/test_bjson.js + $(WINE) ./qjs$(EXE) --bytecode tests/test_bjson.qbc $(WINE) ./qjs$(EXE) examples/test_point.js + $(WINE) ./qjsc$(EXE) --bytecode -o examples/test_point.qbc examples/test_point.js + $(WINE) ./qjs$(EXE) --bytecode examples/test_point.qbc endif stats: qjs$(EXE) diff --git a/qjs.c b/qjs.c index 3ee63408d..a784ca03b 100644 --- a/qjs.c +++ b/qjs.c @@ -42,6 +42,7 @@ #include "cutils.h" #include "quickjs-libc.h" +#include "quickjs-bytecode.h" extern const uint8_t qjsc_repl[]; extern const uint32_t qjsc_repl_size; @@ -103,6 +104,37 @@ static int eval_file(JSContext *ctx, const char *filename, int module, int stric return ret; } +static int eval_bytecode_file(JSContext *ctx, const char *filename) +{ + JSValue obj, val; + int ret = -1; + + obj = qjs_bytecode_read_file(ctx, filename); + if (JS_IsException(obj)) + goto exception; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + js_module_set_import_meta(ctx, obj, FALSE, TRUE); + val = JS_EvalFunction(ctx, obj); + val = js_std_await(ctx, val); + } else { + val = JS_EvalFunction(ctx, obj); + } + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, val); + ret = 0; + return ret; + +exception: + js_std_dump_error(ctx); + return ret; +} + /* also used to initialize the worker context */ static JSContext *JS_NewCustomContext(JSRuntime *rt) { @@ -297,6 +329,7 @@ void help(void) "-i --interactive go to interactive mode\n" "-m --module load as ES6 module (default=autodetect)\n" " --script load as ES6 script (default=autodetect)\n" + " --bytecode file load a checked bytecode file\n" " --strict force strict mode\n" "-I --include file include an additional file\n" " --std make 'std' and 'os' available to the loaded script\n" @@ -324,6 +357,8 @@ int main(int argc, char **argv) int empty_run = 0; int module = -1; int strict = 0; + char *bytecode_file = NULL; + int bytecode_arg_index = -1; int load_std = 0; int dump_unhandled_promise_rejection = 1; size_t memory_limit = 0; @@ -393,6 +428,15 @@ int main(int argc, char **argv) module = 0; continue; } + if (!strcmp(longopt, "bytecode")) { + if (optind >= argc) { + fprintf(stderr, "qjs: expecting bytecode filename\n"); + exit(1); + } + bytecode_arg_index = optind; + bytecode_file = argv[optind++]; + goto done_options; + } if (!strcmp(longopt, "strict")) { strict = 1; continue; @@ -449,6 +493,12 @@ int main(int argc, char **argv) help(); } } + done_options: + + if (bytecode_file && expr) { + fprintf(stderr, "qjs: --bytecode cannot be combined with -e\n"); + exit(1); + } if (trace_memory) { js_trace_malloc_init(&trace_data); @@ -482,7 +532,9 @@ int main(int argc, char **argv) } if (!empty_run) { - js_std_add_helpers(ctx, argc - optind, argv + optind); + int script_arg_index; + script_arg_index = bytecode_file ? bytecode_arg_index : optind; + js_std_add_helpers(ctx, argc - script_arg_index, argv + script_arg_index); /* make 'std' and 'os' visible to non module code */ if (load_std) { @@ -510,6 +562,10 @@ int main(int argc, char **argv) if (eval_buf(ctx, expr, strlen(expr), "", eval_flags)) goto fail; } else + if (bytecode_file) { + if (eval_bytecode_file(ctx, bytecode_file)) + goto fail; + } else if (optind >= argc) { /* interactive mode */ interactive = 1; diff --git a/qjsc.c b/qjsc.c index e55ca61ce..0adbb1b34 100644 --- a/qjsc.c +++ b/qjsc.c @@ -35,6 +35,7 @@ #include "cutils.h" #include "quickjs-libc.h" +#include "quickjs-bytecode.h" typedef struct { char *name; @@ -296,13 +297,15 @@ JSModuleDef *jsc_module_loader(JSContext *ctx, find_unique_cname(cname, sizeof(cname)); } - /* output the module name */ - fprintf(outfile, "static const uint8_t %s_module_name[] = {\n", - cname); - dump_hex(outfile, (const uint8_t *)module_name, strlen(module_name) + 1); - fprintf(outfile, "};\n\n"); + if (outfile) { + /* output the module name */ + fprintf(outfile, "static const uint8_t %s_module_name[] = {\n", + cname); + dump_hex(outfile, (const uint8_t *)module_name, strlen(module_name) + 1); + fprintf(outfile, "};\n\n"); - output_object_code(ctx, outfile, val, cname, CNAME_TYPE_JSON_MODULE); + output_object_code(ctx, outfile, val, cname, CNAME_TYPE_JSON_MODULE); + } JS_FreeValue(ctx, val); } else { JSValue func_val; @@ -317,7 +320,8 @@ JSModuleDef *jsc_module_loader(JSContext *ctx, if (namelist_find(&cname_list, cname)) { find_unique_cname(cname, sizeof(cname)); } - output_object_code(ctx, outfile, func_val, cname, CNAME_TYPE_MODULE); + if (outfile) + output_object_code(ctx, outfile, func_val, cname, CNAME_TYPE_MODULE); /* the module is already referenced, so we must free it */ m = JS_VALUE_GET_PTR(func_val); @@ -327,13 +331,10 @@ JSModuleDef *jsc_module_loader(JSContext *ctx, return m; } -static void compile_file(JSContext *ctx, FILE *fo, - const char *filename, - const char *c_name1, - int module) +static JSValue compile_file_to_object(JSContext *ctx, const char *filename, + int module) { uint8_t *buf; - char c_name[1024]; int eval_flags; JSValue obj; size_t buf_len; @@ -358,6 +359,18 @@ static void compile_file(JSContext *ctx, FILE *fo, exit(1); } js_free(ctx, buf); + return obj; +} + +static void compile_file(JSContext *ctx, FILE *fo, + const char *filename, + const char *c_name1, + int module) +{ + char c_name[1024]; + JSValue obj; + + obj = compile_file_to_object(ctx, filename, module); if (c_name1) { pstrcpy(c_name, sizeof(c_name), c_name1); } else { @@ -398,6 +411,7 @@ void help(void) "options are:\n" "-c only output bytecode to a C file\n" "-e output main() and bytecode to a C file (default = executable output)\n" + "-b --bytecode output checked bytecode to a file\n" "-o output set the output filename\n" "-N cname set the C name of the generated data\n" "-m compile as Javascript module (default=autodetect)\n" @@ -555,6 +569,7 @@ static size_t get_suffixed_size(const char *str) typedef enum { OUTPUT_C, OUTPUT_C_MAIN, + OUTPUT_BYTECODE, OUTPUT_EXECUTABLE, } OutputTypeEnum; @@ -639,6 +654,10 @@ int main(int argc, char **argv) output_type = OUTPUT_C_MAIN; continue; } + if (opt == 'b' || !strcmp(longopt, "bytecode")) { + output_type = OUTPUT_BYTECODE; + continue; + } if (opt == 'N') { cname = get_short_optarg(&optind, opt, arg, argc, argv); break; @@ -732,11 +751,23 @@ int main(int argc, char **argv) if (!out_filename) { if (output_type == OUTPUT_EXECUTABLE) { out_filename = "a.out"; + } else if (output_type == OUTPUT_BYTECODE) { + out_filename = "out.qbc"; } else { out_filename = "out.c"; } } + if (output_type == OUTPUT_BYTECODE && argc - optind != 1) { + fprintf(stderr, "qjsc: bytecode output expects exactly one input file\n"); + exit(1); + } + + if (output_type == OUTPUT_BYTECODE && byte_swap) { + fprintf(stderr, "qjsc: -x is not supported with --bytecode\n"); + exit(1); + } + if (output_type == OUTPUT_EXECUTABLE) { #if defined(_WIN32) || defined(__ANDROID__) /* XXX: find a /tmp directory ? */ @@ -748,13 +779,6 @@ int main(int argc, char **argv) pstrcpy(cfilename, sizeof(cfilename), out_filename); } - fo = fopen(cfilename, "w"); - if (!fo) { - perror(cfilename); - exit(1); - } - outfile = fo; - rt = JS_NewRuntime(); ctx = JS_NewContext(rt); @@ -763,6 +787,31 @@ int main(int argc, char **argv) /* loader for ES6 modules */ JS_SetModuleLoaderFunc2(rt, NULL, jsc_module_loader, NULL, NULL); + if (output_type == OUTPUT_BYTECODE) { + JSValue obj; + int ret; + + obj = compile_file_to_object(ctx, argv[optind], module); + ret = qjs_bytecode_write_file(ctx, out_filename, obj, 0); + JS_FreeValue(ctx, obj); + if (ret) { + js_std_dump_error(ctx); + } + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + namelist_free(&cname_list); + namelist_free(&cmodule_list); + namelist_free(&init_module_list); + return ret ? 1 : 0; + } + + fo = fopen(cfilename, "w"); + if (!fo) { + perror(cfilename); + exit(1); + } + outfile = fo; + fprintf(fo, "/* File generated automatically by the QuickJS compiler. */\n" "\n" ); diff --git a/quickjs-bytecode.c b/quickjs-bytecode.c new file mode 100644 index 000000000..071ef0817 --- /dev/null +++ b/quickjs-bytecode.c @@ -0,0 +1,161 @@ +/* + * QuickJS bytecode file wrapper + */ +#include +#include +#include +#include + +#include "cutils.h" +#include "quickjs-libc.h" +#include "quickjs-bytecode.h" + +#define QJS_BYTECODE_HEADER_SIZE 64 +#define QJS_BYTECODE_VERSION_OFFSET 12 +#define QJS_BYTECODE_VERSION_SIZE 32 +#define QJS_BYTECODE_PAYLOAD_SIZE_OFFSET 44 +#define QJS_BYTECODE_CHECKSUM_OFFSET 52 +#define QJS_BYTECODE_FLAGS_OFFSET 60 + +static const uint8_t qjs_bytecode_magic[8] = { + 'Q', 'J', 'S', 'B', 'C', 0, '\r', '\n' +}; + +static uint64_t qjs_bytecode_checksum(const uint8_t *buf, size_t len) +{ + uint64_t h = UINT64_C(1469598103934665603); + size_t i; + + for (i = 0; i < len; i++) { + h ^= buf[i]; + h *= UINT64_C(1099511628211); + } + return h; +} + +static int qjs_bytecode_write_all(FILE *f, const void *buf, size_t len) +{ + return fwrite(buf, 1, len, f) == len ? 0 : -1; +} + +int qjs_bytecode_write_file(JSContext *ctx, const char *filename, + JSValueConst obj, int write_flags) +{ + uint8_t header[QJS_BYTECODE_HEADER_SIZE]; + uint8_t *payload; + size_t payload_len; + FILE *f; + int ret = -1; + + payload = JS_WriteObject(ctx, &payload_len, obj, + write_flags | JS_WRITE_OBJ_BYTECODE); + if (!payload) + return -1; + + memset(header, 0, sizeof(header)); + memcpy(header, qjs_bytecode_magic, sizeof(qjs_bytecode_magic)); + put_u32(header + 8, QJS_BYTECODE_HEADER_SIZE); + snprintf((char *)header + QJS_BYTECODE_VERSION_OFFSET, + QJS_BYTECODE_VERSION_SIZE, "%s", CONFIG_VERSION); + put_u64(header + QJS_BYTECODE_PAYLOAD_SIZE_OFFSET, payload_len); + put_u64(header + QJS_BYTECODE_CHECKSUM_OFFSET, + qjs_bytecode_checksum(payload, payload_len)); + put_u32(header + QJS_BYTECODE_FLAGS_OFFSET, 0); + + f = fopen(filename, "wb"); + if (!f) { + JS_ThrowReferenceError(ctx, "could not open bytecode file '%s': %s", + filename, strerror(errno)); + goto done; + } + if (qjs_bytecode_write_all(f, header, sizeof(header)) || + qjs_bytecode_write_all(f, payload, payload_len)) { + JS_ThrowReferenceError(ctx, "could not write bytecode file '%s': %s", + filename, strerror(errno)); + goto close_file; + } + ret = 0; + +close_file: + if (fclose(f) != 0 && ret == 0) { + JS_ThrowReferenceError(ctx, "could not close bytecode file '%s': %s", + filename, strerror(errno)); + ret = -1; + } +done: + js_free(ctx, payload); + return ret; +} + +JSValue qjs_bytecode_read_file(JSContext *ctx, const char *filename) +{ + uint8_t *buf; + const uint8_t *payload; + size_t buf_len, payload_len; + uint64_t payload_len64, checksum; + uint32_t header_size, flags; + char version[QJS_BYTECODE_VERSION_SIZE + 1]; + JSValue obj = JS_EXCEPTION; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load bytecode file '%s'", + filename); + return JS_EXCEPTION; + } + + if (buf_len < QJS_BYTECODE_HEADER_SIZE) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: header is too short"); + goto done; + } + if (memcmp(buf, qjs_bytecode_magic, sizeof(qjs_bytecode_magic)) != 0) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: bad magic"); + goto done; + } + + header_size = get_u32(buf + 8); + if (header_size != QJS_BYTECODE_HEADER_SIZE) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: unsupported header size"); + goto done; + } + + memcpy(version, buf + QJS_BYTECODE_VERSION_OFFSET, + QJS_BYTECODE_VERSION_SIZE); + version[QJS_BYTECODE_VERSION_SIZE] = '\0'; + if (strcmp(version, CONFIG_VERSION) != 0) { + JS_ThrowSyntaxError(ctx, + "bytecode version mismatch: file uses '%s', runtime uses '%s'", + version, CONFIG_VERSION); + goto done; + } + + flags = get_u32(buf + QJS_BYTECODE_FLAGS_OFFSET); + if (flags != 0) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: unsupported flags"); + goto done; + } + + payload_len64 = get_u64(buf + QJS_BYTECODE_PAYLOAD_SIZE_OFFSET); + if (payload_len64 > SIZE_MAX) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: payload is too large"); + goto done; + } + payload_len = (size_t)payload_len64; + if (payload_len != buf_len - QJS_BYTECODE_HEADER_SIZE) { + JS_ThrowSyntaxError(ctx, "invalid bytecode file: size mismatch"); + goto done; + } + + payload = buf + QJS_BYTECODE_HEADER_SIZE; + checksum = get_u64(buf + QJS_BYTECODE_CHECKSUM_OFFSET); + if (checksum != qjs_bytecode_checksum(payload, payload_len)) { + JS_ThrowSyntaxError(ctx, "bytecode checksum mismatch"); + goto done; + } + + obj = JS_ReadObject(ctx, payload, payload_len, JS_READ_OBJ_BYTECODE); + +done: + js_free(ctx, buf); + return obj; +} diff --git a/quickjs-bytecode.h b/quickjs-bytecode.h new file mode 100644 index 000000000..1cf4c465b --- /dev/null +++ b/quickjs-bytecode.h @@ -0,0 +1,13 @@ +/* + * QuickJS bytecode file wrapper + */ +#ifndef QUICKJS_BYTECODE_H +#define QUICKJS_BYTECODE_H + +#include "quickjs.h" + +int qjs_bytecode_write_file(JSContext *ctx, const char *filename, + JSValueConst obj, int write_flags); +JSValue qjs_bytecode_read_file(JSContext *ctx, const char *filename); + +#endif /* QUICKJS_BYTECODE_H */ diff --git a/tests/test_bytecode_file.sh b/tests/test_bytecode_file.sh new file mode 100755 index 000000000..a16b90e7a --- /dev/null +++ b/tests/test_bytecode_file.sh @@ -0,0 +1,80 @@ +#!/bin/sh +set -eu + +QJS="${QJS:-./qjs}" +QJSC="${QJSC:-./qjsc}" +TMP_DIR="${TMPDIR:-/tmp}/quickjs-bytecode-file-test-$$" + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT INT TERM + +mkdir -p "$TMP_DIR" + +cat > "$TMP_DIR/hello.js" <<'EOF' +var name = scriptArgs[1] || "world"; +console.log("hello " + name); +EOF + +"$QJSC" --bytecode -o "$TMP_DIR/hello.qbc" "$TMP_DIR/hello.js" + +if [ ! -s "$TMP_DIR/hello.qbc" ]; then + echo "bytecode file was not created" >&2 + exit 1 +fi + +"$QJS" --bytecode "$TMP_DIR/hello.qbc" QuickJS > "$TMP_DIR/out.txt" +if ! grep -q '^hello QuickJS$' "$TMP_DIR/out.txt"; then + echo "bytecode execution produced unexpected output" >&2 + cat "$TMP_DIR/out.txt" >&2 + exit 1 +fi + +cat > "$TMP_DIR/module.mjs" <<'EOF' +await 0; +console.log("module bytecode ok"); +EOF + +"$QJSC" --bytecode -m -o "$TMP_DIR/module.qbc" "$TMP_DIR/module.mjs" +"$QJS" --bytecode "$TMP_DIR/module.qbc" > "$TMP_DIR/module.out" +if ! grep -q '^module bytecode ok$' "$TMP_DIR/module.out"; then + echo "module bytecode execution produced unexpected output" >&2 + cat "$TMP_DIR/module.out" >&2 + exit 1 +fi + +dd if=/dev/zero of="$TMP_DIR/not-bytecode.qbc" bs=1 count=64 2>/dev/null +if "$QJS" --bytecode "$TMP_DIR/not-bytecode.qbc" > "$TMP_DIR/not-bytecode.out" 2> "$TMP_DIR/not-bytecode.err"; then + echo "qjs accepted a non-bytecode file" >&2 + exit 1 +fi +if ! grep -q 'bad magic' "$TMP_DIR/not-bytecode.err"; then + echo "missing bad magic diagnostic" >&2 + cat "$TMP_DIR/not-bytecode.err" >&2 + exit 1 +fi + +cp "$TMP_DIR/hello.qbc" "$TMP_DIR/bad-version.qbc" +printf '0' | dd of="$TMP_DIR/bad-version.qbc" bs=1 seek=12 count=1 conv=notrunc 2>/dev/null +if "$QJS" --bytecode "$TMP_DIR/bad-version.qbc" > "$TMP_DIR/bad-version.out" 2> "$TMP_DIR/bad-version.err"; then + echo "qjs accepted a bytecode file with a corrupted version" >&2 + exit 1 +fi +if ! grep -q 'version mismatch' "$TMP_DIR/bad-version.err"; then + echo "missing version mismatch diagnostic" >&2 + cat "$TMP_DIR/bad-version.err" >&2 + exit 1 +fi + +cp "$TMP_DIR/hello.qbc" "$TMP_DIR/bad-checksum.qbc" +dd if=/dev/zero of="$TMP_DIR/bad-checksum.qbc" bs=1 seek=52 count=8 conv=notrunc 2>/dev/null +if "$QJS" --bytecode "$TMP_DIR/bad-checksum.qbc" > "$TMP_DIR/bad-checksum.out" 2> "$TMP_DIR/bad-checksum.err"; then + echo "qjs accepted a bytecode file with a corrupted payload" >&2 + exit 1 +fi +if ! grep -q 'checksum mismatch' "$TMP_DIR/bad-checksum.err"; then + echo "missing checksum mismatch diagnostic" >&2 + cat "$TMP_DIR/bad-checksum.err" >&2 + exit 1 +fi