diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 0c7d7f3ee..7cff79372 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -48,6 +48,14 @@ jobs: cd quickjs CFLAGS=$CC_OPT LDFLAGS=$LD_OPT $MAKE_UTILITY -j$(nproc) libquickjs.a + - name: Check out and build quickjs-ng + run: | + git clone https://github.com/quickjs-ng/quickjs quickjs-ng + cd quickjs-ng + git checkout v0.8.0 + CFLAGS="$CC_OPT -fPIC" LDFLAGS=$LD_OPT cmake -B build + cmake --build build --target qjs -j $(nproc) + - name: Configure and make njs run: | ./configure \ @@ -88,6 +96,20 @@ jobs: $MAKE_UTILITY test $MAKE_UTILITY clean + - name: Configure and make njs with quickjs-ng + run: | + ./configure \ + --with-quickjs \ + --cc-opt="$CC_OPT -Iquickjs-ng" \ + --ld-opt="$LD_OPT -Lquickjs-ng/build" \ + || cat build/autoconf.err + $MAKE_UTILITY -j$(nproc) + + - name: Test njs with quickjs-ng + run: | + $MAKE_UTILITY test + $MAKE_UTILITY clean + - name: Configure and build nginx and njs modules run: | cd nginx-source @@ -168,3 +190,62 @@ jobs: TEST_NGINX_VERBOSE: 1 ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0" LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Configure and build nginx and njs modules with quickjs-ng, asan, static modules + run: | + cd nginx-source + $NGINX_CONFIGURE_CMD --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs-ng -fsanitize=address -DNJS_DEBUG_MEMORY -DNGX_DEBUG_PALLOC -DNGX_DEBUG_MALLOC" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs-ng/build -fsanitize=address" --add-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, quickjs-ng, static modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Test njs modules (js_engine qjs), quickjs-ng, static modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;" + TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Configure and build nginx and njs modules with quickjs-ng, asan, dynamic modules + run: | + cd nginx-source + $NGINX_CONFIGURE_CMD --with-debug --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs-ng -fsanitize=address -DNJS_DEBUG_MEMORY -DNGX_DEBUG_PALLOC -DNGX_DEBUG_MALLOC" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs-ng/build -fsanitize=address" --add-dynamic-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) modules + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, quickjs-ng, dynamic modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Test njs modules (js_engine qjs), quickjs-ng, dynamic modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so;" + TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;" + TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" diff --git a/auto/quickjs b/auto/quickjs index 9aeb485ac..974321424 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -53,9 +53,19 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature fi + if [ $njs_found = no ]; then + njs_feature="QuickJS-NG library -lqjs" + njs_feature_incs="" + njs_feature_libs="-lqjs -lm -ldl -lpthread" + + . auto/feature + fi + + if [ $njs_found = yes ]; then njs_feature="QuickJS JS_GetClassID()" + njs_feature_name=NJS_HAVE_QUICKJS_GET_CLASS_ID njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) #pragma GCC diagnostic push #pragma GCC diagnostic ignored \"-Wcast-function-type\" @@ -64,7 +74,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then #include int main() { - (void) JS_GetClassID; + (void) JS_GetClassID(JS_UNDEFINED); return 0; }" @@ -78,6 +88,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then fi njs_feature="QuickJS JS_NewTypedArray()" + njs_feature_name=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) #pragma GCC diagnostic push #pragma GCC diagnostic ignored \"-Wcast-function-type\" @@ -86,15 +97,66 @@ if [ $NJS_TRY_QUICKJS = YES ]; then #include int main() { - (void) JS_NewTypedArray; + JSValue ta, argv; + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + argv = JS_NewInt64(ctx, 1); + ta = JS_NewTypedArray(ctx, 1, &argv, + JS_TYPED_ARRAY_UINT8); + JS_FreeValue(ctx, ta); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); return 0; }" . auto/feature - if [ $njs_found = yes ]; then - njs_define=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY . auto/define - fi + njs_feature="QuickJS JS_IsSameValue()" + njs_feature_name=NJS_HAVE_QUICKJS_IS_SAME_VALUE + njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored \"-Wcast-function-type\" + #endif + + #include + + int main() { + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + (void) JS_IsSameValue(ctx, JS_UNDEFINED, JS_UNDEFINED); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + }" + + . auto/feature + + njs_feature="QuickJS version" + njs_feature_name=NJS_QUICKJS_VERSION + njs_feature_run=value + njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored \"-Wcast-function-type\" + #endif + + #include + + int main() { +#if defined(QJS_VERSION_MAJOR) + printf(\"\\\"%s\\\"\", JS_GetVersion()); +#else + printf(\"\\\"Unknown\\\"\"); +#endif + return 0; + }" + + . auto/feature NJS_HAVE_QUICKJS=YES NJS_QUICKJS_LIB="$njs_feature_libs" diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index fc2222ffd..2adeef201 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -397,7 +397,7 @@ qjs_fs_access(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, mode, callback)) { + if (qjs_is_same_value(cx, mode, callback)) { mode = JS_UNDEFINED; } } @@ -586,7 +586,7 @@ qjs_fs_mkdir(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -894,7 +894,7 @@ qjs_fs_read_file(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -955,7 +955,7 @@ qjs_fs_read_file(JSContext *cx, JSValueConst this_val, int argc, str.length = sb.st_size; v = qjs_fs_fd_read(cx, fd, &str); - if (!JS_SameValue(cx, v, JS_TRUE)) { + if (!qjs_is_same_value(cx, v, JS_TRUE)) { if (JS_IsException(v)) { result = JS_EXCEPTION; @@ -1012,7 +1012,7 @@ qjs_fs_readlink(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -1150,7 +1150,7 @@ qjs_fs_readdir(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -1309,7 +1309,7 @@ qjs_fs_realpath(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -1655,7 +1655,7 @@ qjs_fs_rmdir(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -1817,7 +1817,7 @@ qjs_fs_stat(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } @@ -1916,7 +1916,7 @@ qjs_fs_symlink(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, type, callback)) { + if (qjs_is_same_value(cx, type, callback)) { type = JS_UNDEFINED; } } @@ -2168,7 +2168,7 @@ qjs_fs_write_file(JSContext *cx, JSValueConst this_val, int argc, return JS_EXCEPTION; } - if (JS_SameValue(cx, options, callback)) { + if (qjs_is_same_value(cx, options, callback)) { options = JS_UNDEFINED; } } diff --git a/nginx/config b/nginx/config index 436f06cbe..8e920477b 100644 --- a/nginx/config +++ b/nginx/config @@ -39,7 +39,6 @@ if [ $NJS_QUICKJS != NO ]; then ngx_feature_test="JSRuntime *rt; rt = JS_NewRuntime(); - (void) JS_GetClassID; JS_FreeRuntime(rt); return 0;" . auto/feature @@ -66,17 +65,44 @@ if [ $NJS_QUICKJS != NO ]; then . auto/feature fi + if [ $ngx_found = no ]; then + ngx_feature="QuickJS-NG library -lqjs" + ngx_feature_path="" + ngx_feature_libs="-lqjs -lm -ldl -lpthread" + + . auto/feature + fi + if [ $ngx_found = yes ]; then + ngx_feature="QuickJS JS_GetClassID()" + ngx_feature_name=NJS_HAVE_QUICKJS_GET_CLASS_ID + ngx_feature_run=no + ngx_feature_test="(void) JS_GetClassID(JS_UNDEFINED);" + + . auto/feature + + if [ $ngx_found = no ]; then + echo + echo $0: error: QuickJS library found, but JS_GetClassID\(\) is missing. + echo + exit 1; + fi + ngx_feature="QuickJS JS_NewTypedArray()" - ngx_feature_test="(void) JS_NewTypedArray; + ngx_feature_name=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY + ngx_feature_test="JSValue argv; + (void) JS_NewTypedArray(NULL, 1, &argv, + JS_TYPED_ARRAY_UINT8); return 0;" . auto/feature - if [ $ngx_found = yes ]; then - have=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY . auto/have - fi + ngx_feature="QuickJS JS_IsSameValue()" + ngx_feature_name=NJS_HAVE_QUICKJS_IS_SAME_VALUE + ngx_feature_test="(void) JS_IsSameValue(NULL, JS_UNDEFINED, JS_UNDEFINED);" + + . auto/feature NJS_HAVE_QUICKJS=YES NJS_QUICKJS_LIB="$ngx_feature_libs" diff --git a/nginx/config.make b/nginx/config.make index 2fa40063f..404a622b4 100644 --- a/nginx/config.make +++ b/nginx/config.make @@ -4,14 +4,14 @@ $ngx_addon_dir/../build/libnjs.a: $NGX_MAKEFILE cd $ngx_addon_dir/.. \\ && if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\ && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\ - --no-libxml2 --no-zlib --no-pcre --no-quickjs \\ + --no-libxml2 --no-zlib --no-pcre --no-quickjs --ld-opt="$NGX_LD_OPT" \\ && \$(MAKE) libnjs $ngx_addon_dir/../build/libqjs.a: $NGX_MAKEFILE cd $ngx_addon_dir/.. \\ && if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\ && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\ - --no-libxml2 --no-zlib --no-pcre \\ + --no-libxml2 --no-zlib --no-pcre --ld-opt="$NGX_LD_OPT" \\ && \$(MAKE) libnjs libqjs END diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index e900b7162..9b6d8ea13 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -4848,7 +4848,7 @@ ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val) return JS_EXCEPTION; } - val = qjs_string_create(cx, v + 1, p - v - 1); + val = qjs_string_create(cx, v + 1, (p == v) ? 0 : p - v - 1); if (JS_IsException(val)) { chain.free(cx, decoded.start); JS_FreeAtom(cx, key); diff --git a/src/njs.h b/src/njs.h index 42a6f405e..c7e67aca9 100644 --- a/src/njs.h +++ b/src/njs.h @@ -11,8 +11,8 @@ #include -#define NJS_VERSION "0.8.9" -#define NJS_VERSION_NUMBER 0x000809 +#define NJS_VERSION "0.8.10" +#define NJS_VERSION_NUMBER 0x00080a #include diff --git a/src/qjs.c b/src/qjs.c index e21e6568d..8c5b0b6d7 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -45,7 +45,6 @@ extern char **environ; static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); static JSValue qjs_njs_to_string_tag(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_to_string_tag(JSContext *ctx, JSValueConst this_val); -static JSValue qjs_process_argv(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -137,7 +136,6 @@ static const JSCFunctionListEntry qjs_njs_proto[] = { static const JSCFunctionListEntry qjs_process_proto[] = { JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_process_to_string_tag, NULL), - JS_CGETSET_DEF("argv", qjs_process_argv, NULL), JS_CGETSET_DEF("env", qjs_process_env, NULL), JS_CFUNC_DEF("kill", 2, qjs_process_kill), JS_CGETSET_DEF("pid", qjs_process_pid, NULL), @@ -262,51 +260,6 @@ qjs_process_to_string_tag(JSContext *ctx, JSValueConst this_val) } -static JSValue -qjs_process_argv(JSContext *ctx, JSValueConst this_val) -{ - int i, ret, argc; - JSValue val, str; - const char **argv; - - val = JS_GetPropertyStr(ctx, this_val, "argc"); - if (JS_IsException(val)) { - return JS_EXCEPTION; - } - - if (JS_ToInt32(ctx, &argc, val) < 0) { - return JS_EXCEPTION; - } - - argv = JS_GetOpaque(this_val, JS_GetClassID(this_val)); - if (argv == NULL) { - return JS_NewArray(ctx); - } - - val = JS_NewArray(ctx); - if (JS_IsException(val)) { - return JS_EXCEPTION; - } - - for (i = 0; i < argc; i++) { - str = JS_NewStringLen(ctx, argv[i], njs_strlen(argv[i])); - if (JS_IsException(str)) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - - ret = JS_DefinePropertyValueUint32(ctx, val, i, str, JS_PROP_C_W_E); - if (ret < 0) { - JS_FreeValue(ctx, str); - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - } - - return val; -} - - static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val) { @@ -451,20 +404,39 @@ qjs_process_ppid(JSContext *ctx, JSValueConst this_val) JSValue qjs_process_object(JSContext *ctx, int argc, const char **argv) { - JSValue obj; + int i; + JSValue obj, str, val; + + val = JS_NewArray(ctx); + if (JS_IsException(val)) { + return JS_EXCEPTION; + } + + for (i = 0; i < argc; i++) { + str = JS_NewStringLen(ctx, argv[i], njs_strlen(argv[i])); + if (JS_IsException(str)) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + + if (JS_DefinePropertyValueUint32(ctx, val, i, str, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + } obj = JS_NewObject(ctx); if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); return JS_EXCEPTION; } JS_SetPropertyFunctionList(ctx, obj, qjs_process_proto, njs_nitems(qjs_process_proto)); - JS_SetOpaque(obj, argv); - - if (JS_SetPropertyStr(ctx, obj, "argc", JS_NewInt32(ctx, argc)) < 0) { - JS_FreeValue(ctx, obj); + if (JS_SetPropertyStr(ctx, obj, "argv", val) < 0) { + JS_FreeValue(ctx, val); return JS_EXCEPTION; } diff --git a/src/qjs.h b/src/qjs.h index 76bf5c3df..71dd297e5 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -29,7 +29,6 @@ #if defined(__GNUC__) && (__GNUC__ >= 8) #pragma GCC diagnostic pop #endif -#define NJS_QUICKJS_VERSION "Unknown version" #include @@ -101,6 +100,13 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) } +#ifdef NJS_HAVE_QUICKJS_IS_SAME_VALUE +#define qjs_is_same_value(cx, a, b) JS_IsSameValue(cx, a, b) +#else +#define qjs_is_same_value(cx, a, b) JS_SameValue(cx, a, b) +#endif + + extern qjs_module_t *qjs_modules[]; #endif /* _QJS_H_INCLUDED_ */ diff --git a/test/shell_test.exp b/test/shell_test.exp index b713ed09b..028282708 100644 --- a/test/shell_test.exp +++ b/test/shell_test.exp @@ -302,11 +302,11 @@ njs_test { njs_test { {"function f() { return ({}.a.a); }\r\n" - "undefined"} - {"var e; try {f()} catch (ee) {e = ee}\r\n" - "undefined"} + "undefined\r\n>> "} + {"var e; try {f()} catch (ee) {e = ee}; undefined\r\n" + "undefined\r\n>> "} {"Object.keys(null)\r\n" - "Thrown:\r\nTypeError: cannot convert*to object"} + "Thrown:\r\nTypeError: *annot convert*to object"} {"e\r\n" "TypeError: cannot * property *a* of undefined"} } @@ -382,11 +382,11 @@ njs_test { njs_run {"-c" "setTimeout(() => {console.log('A'.repeat(1024))}, 0); ref"} \ "^Thrown: -ReferenceError: \['\"\]ref\['\"\] is not defined.*" +ReferenceError: .* is not defined.*" njs_run {"-c" "setTimeout(() => {ref}, 0); setTimeout(() => {console.log('A'.repeat(1024))}, 0)"} \ "^Thrown: -ReferenceError: \['\"\]ref\['\"\] is not defined.*" +ReferenceError: .* is not defined.*" njs_test { {"setImmediate(() => { console.log('x'); return Promise.reject('xx'); })\r\n"