From 399dd8e6c470336c043a8273edc83227cb3f3f37 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 18 Jul 2023 21:44:46 +0200 Subject: [PATCH 01/20] deps: V8: cherry-pick c1a54d5ffcd1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: Skip regress-1320641 when the system does not have enough memory Change-Id: I23a5232f6437c4bc77390796ee2986f1600cb1bf Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4689686 Reviewed-by: Nico Hartmann Commit-Queue: Joyee Cheung Cr-Commit-Position: refs/heads/main@{#88973} Refs: https://github.com/v8/v8/commit/c1a54d5ffcd1a3b769376be3b46b57b860b5aa9e PR-URL: https://github.com/nodejs/node/pull/48830 Reviewed-By: Michaël Zasso Reviewed-By: Richard Lau Reviewed-By: Jiawen Geng Reviewed-By: Debadree Chatterjee --- common.gypi | 2 +- .../test/mjsunit/regress/regress-1320641.js | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/common.gypi b/common.gypi index 4b8e6f62ae0022..b68804ac668d32 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.11', + 'v8_embedder_string': '-node.12', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/test/mjsunit/regress/regress-1320641.js b/deps/v8/test/mjsunit/regress/regress-1320641.js index 6c52c9d178606d..cc6a11be3d89db 100644 --- a/deps/v8/test/mjsunit/regress/regress-1320641.js +++ b/deps/v8/test/mjsunit/regress/regress-1320641.js @@ -8,9 +8,21 @@ function foo(){ const xs = new Uint16Array(3775336418); return xs[-981886074]; } -%PrepareFunctionForOptimization(foo); -foo(); -assertEquals(undefined, foo()); -%OptimizeFunctionOnNextCall(foo); -assertEquals(undefined, foo()); +var skip = false; +try { + new Uint16Array(3775336418); +} catch (e) { + if (e.message.test(/Array buffer allocation failed/)) { + skip = true; // We don't have enough memory, just skip the test. + } +} + +if (!skip) { + %PrepareFunctionForOptimization(foo); + foo(); + + assertEquals(undefined, foo()); + %OptimizeFunctionOnNextCall(foo); + assertEquals(undefined, foo()); +} From 4a2e9bacab13859e9440685888ca4f94fe320eda Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 19 Jul 2023 19:07:07 +0200 Subject: [PATCH 02/20] deps: V8: cherry-pick 9f4b7699f68e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: Fix mistake in the skip branch of test/mjsunit/regress-1320641.js It was doing a `string.test(regex)` which was wrong. It's supposed to be `regex.test(string)`. It wasn't caught in the CI because the skip path is not normally taken in the V8 CI. Change-Id: Id1bdab5bbc41968bba8adc1cb3664e8f95fb5d72 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4697855 Commit-Queue: Joyee Cheung Reviewed-by: Nico Hartmann Cr-Commit-Position: refs/heads/main@{#89044} Refs: https://github.com/v8/v8/commit/9f4b7699f68edaed1de25ef0a1886c21d496032a PR-URL: https://github.com/nodejs/node/pull/48830 Refs: https://github.com/v8/v8/commit/c1a54d5ffcd1a3b769376be3b46b57b860b5aa9e Reviewed-By: Michaël Zasso Reviewed-By: Richard Lau Reviewed-By: Jiawen Geng Reviewed-By: Debadree Chatterjee --- common.gypi | 2 +- deps/v8/test/mjsunit/regress/regress-1320641.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common.gypi b/common.gypi index b68804ac668d32..87e9d4ebfc0ab2 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.12', + 'v8_embedder_string': '-node.13', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/test/mjsunit/regress/regress-1320641.js b/deps/v8/test/mjsunit/regress/regress-1320641.js index cc6a11be3d89db..6bd109421a94af 100644 --- a/deps/v8/test/mjsunit/regress/regress-1320641.js +++ b/deps/v8/test/mjsunit/regress/regress-1320641.js @@ -13,7 +13,7 @@ var skip = false; try { new Uint16Array(3775336418); } catch (e) { - if (e.message.test(/Array buffer allocation failed/)) { + if (/Array buffer allocation failed/.test(e.message)) { skip = true; // We don't have enough memory, just skip the test. } } From 01c8576a24eced5333c77fbfe33400b38963c4f2 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Thu, 20 Jul 2023 21:00:23 +0100 Subject: [PATCH 03/20] deps: update zlib to 1.2.13.1-motley-61dc0bd PR-URL: https://github.com/nodejs/node/pull/48788 Reviewed-By: Luigi Pinca Reviewed-By: Mohammed Keyvanzadeh --- deps/zlib/BUILD.gn | 63 ++++------ deps/zlib/README.chromium | 1 + deps/zlib/contrib/bench/zlib_bench.cc | 63 ++++++---- deps/zlib/contrib/minizip/README.chromium | 1 + deps/zlib/cpu_features.c | 26 +++-- deps/zlib/google/zip_internal.cc | 108 +++++++++--------- .../maintaining/maintaining-dependencies.md | 6 +- 7 files changed, 145 insertions(+), 123 deletions(-) diff --git a/deps/zlib/BUILD.gn b/deps/zlib/BUILD.gn index 9b3971041dffa0..074c2720fa460d 100644 --- a/deps/zlib/BUILD.gn +++ b/deps/zlib/BUILD.gn @@ -124,45 +124,40 @@ source_set("zlib_adler32_simd") { if (use_arm_neon_optimizations) { config("zlib_arm_crc32_config") { - # Disabled for iPhone, as described in DDI0487C_a_armv8_arm: - # "All implementations of the ARMv8.1 architecture are required to - # implement the CRC32* instructions. These are optional in ARMv8.0." - if (!is_ios) { - defines = [ "CRC32_ARMV8_CRC32" ] - if (is_android) { - defines += [ "ARMV8_OS_ANDROID" ] - } else if (is_linux || is_chromeos) { - defines += [ "ARMV8_OS_LINUX" ] - } else if (is_mac) { - defines += [ "ARMV8_OS_MACOS" ] - } else if (is_fuchsia) { - defines += [ "ARMV8_OS_FUCHSIA" ] - } else if (is_win) { - defines += [ "ARMV8_OS_WINDOWS" ] - } else { - assert(false, "Unsupported ARM OS") - } + defines = [ "CRC32_ARMV8_CRC32" ] + if (is_android) { + defines += [ "ARMV8_OS_ANDROID" ] + } else if (is_linux || is_chromeos) { + defines += [ "ARMV8_OS_LINUX" ] + } else if (is_mac) { + defines += [ "ARMV8_OS_MACOS" ] + } else if (is_ios) { + defines += [ "ARMV8_OS_IOS" ] + } else if (is_fuchsia) { + defines += [ "ARMV8_OS_FUCHSIA" ] + } else if (is_win) { + defines += [ "ARMV8_OS_WINDOWS" ] + } else { + assert(false, "Unsupported ARM OS") } } source_set("zlib_arm_crc32") { visibility = [ ":*" ] - if (!is_ios) { - include_dirs = [ "." ] - - if (!is_win && !is_clang) { - assert(!use_thin_lto, - "ThinLTO fails mixing different module-level targets") - cflags_c = [ "-march=armv8-a+aes+crc" ] - } + include_dirs = [ "." ] - sources = [ - "crc32_simd.c", - "crc32_simd.h", - ] + if (!is_win && !is_clang) { + assert(!use_thin_lto, + "ThinLTO fails mixing different module-level targets") + cflags_c = [ "-march=armv8-a+aes+crc" ] } + sources = [ + "crc32_simd.c", + "crc32_simd.h", + ] + configs += [ ":zlib_internal_config" ] public_configs = [ ":zlib_arm_crc32_config" ] @@ -332,14 +327,6 @@ component("zlib") { defines += [ "CPU_NO_SIMD" ] } - if (is_ios) { - # iOS@ARM is a special case where we always have NEON but don't check - # for crypto extensions. - # TODO(cavalcantii): verify what is the current state of CPU features - # shipped on latest iOS devices. - defines += [ "ARM_OS_IOS" ] - } - if (use_x86_x64_optimizations || use_arm_neon_optimizations) { deps += [ ":zlib_adler32_simd", diff --git a/deps/zlib/README.chromium b/deps/zlib/README.chromium index 190430f5325ce6..663ee0521ed30c 100644 --- a/deps/zlib/README.chromium +++ b/deps/zlib/README.chromium @@ -4,6 +4,7 @@ URL: http://zlib.net/ Version: 1.2.13 CPEPrefix: cpe:/a:zlib:zlib:1.2.13 Security Critical: yes +Shipped: yes License: Custom license License File: LICENSE License Android Compatible: yes diff --git a/deps/zlib/contrib/bench/zlib_bench.cc b/deps/zlib/contrib/bench/zlib_bench.cc index 8580319edf25bc..b65f9291bff500 100644 --- a/deps/zlib/contrib/bench/zlib_bench.cc +++ b/deps/zlib/contrib/bench/zlib_bench.cc @@ -236,7 +236,11 @@ void check_file(const Data& file, zlib_wrapper type, int mode) { error_exit("check file: error writing output", 3); } -void zlib_file(const char* name, zlib_wrapper type, int width, int check) { +void zlib_file(const char* name, + zlib_wrapper type, + int width, + int check, + bool output_csv_format) { /* * Read the file data. */ @@ -257,7 +261,9 @@ void zlib_file(const char* name, zlib_wrapper type, int width, int check) { * Report compression strategy and file name. */ const char* strategy = zlib_level_strategy_name(zlib_compression_level); - printf("%s%-40s :\n", strategy, name); + if (!output_csv_format) { + printf("%s%-40s :\n", strategy, name); + } /* * Chop the data into blocks. @@ -329,19 +335,28 @@ void zlib_file(const char* name, zlib_wrapper type, int width, int check) { std::sort(ctime, ctime + runs); std::sort(utime, utime + runs); - double deflate_rate_med = length * repeats / mega_byte / ctime[runs / 2]; - double inflate_rate_med = length * repeats / mega_byte / utime[runs / 2]; - double deflate_rate_max = length * repeats / mega_byte / ctime[0]; - double inflate_rate_max = length * repeats / mega_byte / utime[0]; - - // type, block size, compression ratio, etc - printf("%s: [b %dM] bytes %*d -> %*u %4.2f%%", - zlib_wrapper_name(type), block_size / (1 << 20), width, length, width, - unsigned(output_length), output_length * 100.0 / length); - - // compress / uncompress median (max) rates - printf(" comp %5.1f (%5.1f) MB/s uncomp %5.1f (%5.1f) MB/s\n", - deflate_rate_med, deflate_rate_max, inflate_rate_med, inflate_rate_max); + double deflate_rate_med, inflate_rate_med, deflate_rate_max, inflate_rate_max; + deflate_rate_med = length * repeats / mega_byte / ctime[runs / 2]; + inflate_rate_med = length * repeats / mega_byte / utime[runs / 2]; + deflate_rate_max = length * repeats / mega_byte / ctime[0]; + inflate_rate_max = length * repeats / mega_byte / utime[0]; + double compress_ratio = output_length * 100.0 / length; + + if (!output_csv_format) { + // type, block size, compression ratio, etc + printf("%s: [b %dM] bytes %*d -> %*u %4.2f%%", zlib_wrapper_name(type), + block_size / (1 << 20), width, length, width, + unsigned(output_length), compress_ratio); + + // compress / uncompress median (max) rates + printf(" comp %5.1f (%5.1f) MB/s uncomp %5.1f (%5.1f) MB/s\n", + deflate_rate_med, deflate_rate_max, inflate_rate_med, + inflate_rate_max); + } else { + printf("%s\t%.5lf\t%.5lf\t%.5lf\t%.5lf\t%.5lf\n", name, deflate_rate_med, + inflate_rate_med, deflate_rate_max, inflate_rate_max, + compress_ratio); + } } static int argn = 1; @@ -363,8 +378,10 @@ void get_field_width(int argc, char* argv[], int& value) { } void usage_exit(const char* program) { - static auto* options = "gzip|zlib|raw" - " [--compression 0:9] [--huffman|--rle] [--field width] [--check]"; + static auto* options = + "gzip|zlib|raw" + " [--compression 0:9] [--huffman|--rle] [--field width] [--check]" + " [--csv]"; printf("usage: %s %s files ...\n", program, options); printf("zlib version: %s\n", ZLIB_VERSION); exit(1); @@ -383,7 +400,7 @@ int main(int argc, char* argv[]) { int size_field_width = 0; int file_check = 0; - + bool output_csv = false; while (argn < argc && argv[argn][0] == '-') { if (get_option(argc, argv, "--compression")) { if (!get_compression(argc, argv, zlib_compression_level)) @@ -398,6 +415,11 @@ int main(int argc, char* argv[]) { file_check = 2; } else if (get_option(argc, argv, "--field")) { get_field_width(argc, argv, size_field_width); + } else if (get_option(argc, argv, "--csv")) { + output_csv = true; + printf( + "filename\tcompression\tdecompression\tcomp_max\t" + "decomp_max\tcompress_ratio\n"); } else { usage_exit(argv[0]); } @@ -408,8 +430,9 @@ int main(int argc, char* argv[]) { if (size_field_width < 6) size_field_width = 6; - while (argn < argc) - zlib_file(argv[argn++], type, size_field_width, file_check); + while (argn < argc) { + zlib_file(argv[argn++], type, size_field_width, file_check, output_csv); + } return 0; } diff --git a/deps/zlib/contrib/minizip/README.chromium b/deps/zlib/contrib/minizip/README.chromium index 1ad489a6780db2..5eac425720797f 100644 --- a/deps/zlib/contrib/minizip/README.chromium +++ b/deps/zlib/contrib/minizip/README.chromium @@ -4,6 +4,7 @@ URL: https://github.com/madler/zlib/tree/master/contrib/minizip Version: 1.2.12 License: Zlib Security Critical: yes +Shipped: yes Description: Minizip provides API on top of zlib that can enumerate and extract ZIP archive diff --git a/deps/zlib/cpu_features.c b/deps/zlib/cpu_features.c index ac6ee88e460e2e..64e0428cd2fc2d 100644 --- a/deps/zlib/cpu_features.c +++ b/deps/zlib/cpu_features.c @@ -35,7 +35,7 @@ int ZLIB_INTERNAL x86_cpu_enable_avx512 = 0; #ifndef CPU_NO_SIMD -#if defined(ARMV8_OS_ANDROID) || defined(ARMV8_OS_LINUX) || defined(ARMV8_OS_FUCHSIA) +#if defined(ARMV8_OS_ANDROID) || defined(ARMV8_OS_LINUX) || defined(ARMV8_OS_FUCHSIA) || defined(ARMV8_OS_IOS) #include #endif @@ -50,17 +50,19 @@ int ZLIB_INTERNAL x86_cpu_enable_avx512 = 0; #include #elif defined(ARMV8_OS_WINDOWS) || defined(X86_WINDOWS) #include +#elif defined(ARMV8_OS_IOS) +#include #elif !defined(_MSC_VER) #include #else #error cpu_features.c CPU feature detection in not defined for your platform #endif -#if !defined(CPU_NO_SIMD) && !defined(ARMV8_OS_MACOS) && !defined(ARM_OS_IOS) +#if !defined(CPU_NO_SIMD) && !defined(ARMV8_OS_MACOS) static void _cpu_check_features(void); #endif -#if defined(ARMV8_OS_ANDROID) || defined(ARMV8_OS_LINUX) || defined(ARMV8_OS_MACOS) || defined(ARMV8_OS_FUCHSIA) || defined(X86_NOT_WINDOWS) +#if defined(ARMV8_OS_ANDROID) || defined(ARMV8_OS_LINUX) || defined(ARMV8_OS_MACOS) || defined(ARMV8_OS_FUCHSIA) || defined(X86_NOT_WINDOWS) || defined(ARMV8_OS_IOS) #if !defined(ARMV8_OS_MACOS) // _cpu_check_features() doesn't need to do anything on mac/arm since all // features are known at build time, so don't call it. @@ -89,11 +91,7 @@ void ZLIB_INTERNAL cpu_check_features(void) #endif #if (defined(__ARM_NEON__) || defined(__ARM_NEON)) -/* - * iOS@ARM is a special case where we always have NEON but don't check - * for crypto extensions. - */ -#if !defined(ARMV8_OS_MACOS) && !defined(ARM_OS_IOS) +#if !defined(ARMV8_OS_MACOS) /* * See http://bit.ly/2CcoEsr for run-time detection of ARM features and also * crbug.com/931275 for android_getCpuFeatures() use in the Android sandbox. @@ -127,6 +125,18 @@ static void _cpu_check_features(void) #elif defined(ARMV8_OS_WINDOWS) arm_cpu_enable_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); arm_cpu_enable_pmull = IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +#elif defined(ARMV8_OS_IOS) + // Determine what features are supported dynamically. This code is applicable to macOS + // as well if we wish to do that dynamically on that platform in the future. + // See https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics + int val = 0; + size_t len = sizeof(val); + arm_cpu_enable_crc32 = sysctlbyname("hw.optional.armv8_crc32", &val, &len, 0, 0) == 0 + && val != 0; + val = 0; + len = sizeof(val); + arm_cpu_enable_pmull = sysctlbyname("hw.optional.arm.FEAT_PMULL", &val, &len, 0, 0) == 0 + && val != 0; #endif } #endif diff --git a/deps/zlib/google/zip_internal.cc b/deps/zlib/google/zip_internal.cc index c986e763cccda3..9b20b421e24765 100644 --- a/deps/zlib/google/zip_internal.cc +++ b/deps/zlib/google/zip_internal.cc @@ -8,12 +8,12 @@ #include #include -#include +#include "base/containers/fixed_flat_set.h" #include "base/files/file_path.h" #include "base/logging.h" -#include "base/no_destructor.h" #include "base/notreached.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -407,59 +407,59 @@ Compression GetCompressionMethod(const base::FilePath& path) { // Well known filename extensions of files that a likely to be already // compressed. The extensions are in lower case without the leading dot. - static const base::NoDestructor> exts( - std::initializer_list{ - FILE_PATH_LITERAL("3g2"), // - FILE_PATH_LITERAL("3gp"), // - FILE_PATH_LITERAL("7z"), // - FILE_PATH_LITERAL("7zip"), // - FILE_PATH_LITERAL("aac"), // - FILE_PATH_LITERAL("avi"), // - FILE_PATH_LITERAL("bz"), // - FILE_PATH_LITERAL("bz2"), // - FILE_PATH_LITERAL("crx"), // - FILE_PATH_LITERAL("gif"), // - FILE_PATH_LITERAL("gz"), // - FILE_PATH_LITERAL("jar"), // - FILE_PATH_LITERAL("jpeg"), // - FILE_PATH_LITERAL("jpg"), // - FILE_PATH_LITERAL("lz"), // - FILE_PATH_LITERAL("m2v"), // - FILE_PATH_LITERAL("m4p"), // - FILE_PATH_LITERAL("m4v"), // - FILE_PATH_LITERAL("mng"), // - FILE_PATH_LITERAL("mov"), // - FILE_PATH_LITERAL("mp2"), // - FILE_PATH_LITERAL("mp3"), // - FILE_PATH_LITERAL("mp4"), // - FILE_PATH_LITERAL("mpe"), // - FILE_PATH_LITERAL("mpeg"), // - FILE_PATH_LITERAL("mpg"), // - FILE_PATH_LITERAL("mpv"), // - FILE_PATH_LITERAL("ogg"), // - FILE_PATH_LITERAL("ogv"), // - FILE_PATH_LITERAL("png"), // - FILE_PATH_LITERAL("qt"), // - FILE_PATH_LITERAL("rar"), // - FILE_PATH_LITERAL("taz"), // - FILE_PATH_LITERAL("tb2"), // - FILE_PATH_LITERAL("tbz"), // - FILE_PATH_LITERAL("tbz2"), // - FILE_PATH_LITERAL("tgz"), // - FILE_PATH_LITERAL("tlz"), // - FILE_PATH_LITERAL("tz"), // - FILE_PATH_LITERAL("tz2"), // - FILE_PATH_LITERAL("vob"), // - FILE_PATH_LITERAL("webm"), // - FILE_PATH_LITERAL("wma"), // - FILE_PATH_LITERAL("wmv"), // - FILE_PATH_LITERAL("xz"), // - FILE_PATH_LITERAL("z"), // - FILE_PATH_LITERAL("zip"), // - }); - - if (exts->count(ext_without_dot)) + static constexpr auto kExts = base::MakeFixedFlatSet({ + FILE_PATH_LITERAL("3g2"), // + FILE_PATH_LITERAL("3gp"), // + FILE_PATH_LITERAL("7z"), // + FILE_PATH_LITERAL("7zip"), // + FILE_PATH_LITERAL("aac"), // + FILE_PATH_LITERAL("avi"), // + FILE_PATH_LITERAL("bz"), // + FILE_PATH_LITERAL("bz2"), // + FILE_PATH_LITERAL("crx"), // + FILE_PATH_LITERAL("gif"), // + FILE_PATH_LITERAL("gz"), // + FILE_PATH_LITERAL("jar"), // + FILE_PATH_LITERAL("jpeg"), // + FILE_PATH_LITERAL("jpg"), // + FILE_PATH_LITERAL("lz"), // + FILE_PATH_LITERAL("m2v"), // + FILE_PATH_LITERAL("m4p"), // + FILE_PATH_LITERAL("m4v"), // + FILE_PATH_LITERAL("mng"), // + FILE_PATH_LITERAL("mov"), // + FILE_PATH_LITERAL("mp2"), // + FILE_PATH_LITERAL("mp3"), // + FILE_PATH_LITERAL("mp4"), // + FILE_PATH_LITERAL("mpe"), // + FILE_PATH_LITERAL("mpeg"), // + FILE_PATH_LITERAL("mpg"), // + FILE_PATH_LITERAL("mpv"), // + FILE_PATH_LITERAL("ogg"), // + FILE_PATH_LITERAL("ogv"), // + FILE_PATH_LITERAL("png"), // + FILE_PATH_LITERAL("qt"), // + FILE_PATH_LITERAL("rar"), // + FILE_PATH_LITERAL("taz"), // + FILE_PATH_LITERAL("tb2"), // + FILE_PATH_LITERAL("tbz"), // + FILE_PATH_LITERAL("tbz2"), // + FILE_PATH_LITERAL("tgz"), // + FILE_PATH_LITERAL("tlz"), // + FILE_PATH_LITERAL("tz"), // + FILE_PATH_LITERAL("tz2"), // + FILE_PATH_LITERAL("vob"), // + FILE_PATH_LITERAL("webm"), // + FILE_PATH_LITERAL("wma"), // + FILE_PATH_LITERAL("wmv"), // + FILE_PATH_LITERAL("xz"), // + FILE_PATH_LITERAL("z"), // + FILE_PATH_LITERAL("zip"), // + }); + + if (kExts.count(ext_without_dot)) { return kStored; + } return kDeflated; } diff --git a/doc/contributing/maintaining/maintaining-dependencies.md b/doc/contributing/maintaining/maintaining-dependencies.md index 41bbfd5ee70e74..8e462e995b17f0 100644 --- a/doc/contributing/maintaining/maintaining-dependencies.md +++ b/doc/contributing/maintaining/maintaining-dependencies.md @@ -31,7 +31,7 @@ This a list of all the dependencies: * [undici 5.22.1][] * [uvwasi 0.0.16][] * [V8 11.3.244.8][] -* [zlib 1.2.13.1-motley-f81f385][] +* [zlib 1.2.13.1-motley-61dc0bd][] Any code which meets one or more of these conditions should be managed as a dependency: @@ -311,7 +311,7 @@ See [maintaining-web-assembly][] for more informations. high-performance JavaScript and WebAssembly engine, written in C++. See [maintaining-V8][] for more informations. -### zlib 1.2.13.1-motley-f81f385 +### zlib 1.2.13.1-motley-61dc0bd The [zlib](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/zlib) dependency lossless data-compression library, @@ -349,4 +349,4 @@ performance improvements not currently available in standard zlib. [update-openssl-action]: ../../../.github/workflows/update-openssl.yml [uvwasi 0.0.16]: #uvwasi-0016 [v8 11.3.244.8]: #v8-1132448 -[zlib 1.2.13.1-motley-f81f385]: #zlib-12131-motley-f81f385 +[zlib 1.2.13.1-motley-61dc0bd]: #zlib-12131-motley-61dc0bd From d5761a4f8e631ddd93aa9468a2e53794ecee5b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Arboleda?= Date: Tue, 18 Jul 2023 13:45:17 -0500 Subject: [PATCH 04/20] 2023-07-18, Version 20.5.0 (Current) Notable changes: doc: * add atlowChemi to collaborators (atlowChemi) https://github.com/nodejs/node/pull/48757 events: * (SEMVER-MINOR) allow safely adding listener to abortSignal (Chemi Atlow) https://github.com/nodejs/node/pull/48596 fs: * add a fast-path for readFileSync utf-8 (Yagiz Nizipli) https://github.com/nodejs/node/pull/48658 test_runner: * (SEMVER-MINOR) add shards support (Raz Luvaton) https://github.com/nodejs/node/pull/48639 PR-URL: https://github.com/nodejs/node/pull/48761 --- CHANGELOG.md | 3 +- doc/api/child_process.md | 2 +- doc/api/cli.md | 2 +- doc/api/dgram.md | 2 +- doc/api/events.md | 2 +- doc/api/net.md | 2 +- doc/api/timers.md | 4 +- doc/changelogs/CHANGELOG_V20.md | 69 +++++++++++++++++++++++++++++++++ 8 files changed, 78 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6c27cdb4d9fb..ad264fd956c245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,8 @@ release. -20.4.0
+20.5.0
+20.4.0
20.3.1
20.3.0
20.2.0
diff --git a/doc/api/child_process.md b/doc/api/child_process.md index 2c644c61c5f1ad..a32917997d37f1 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -1405,7 +1405,7 @@ setTimeout(() => { ### `subprocess[Symbol.dispose]()` > Stability: 1 - Experimental diff --git a/doc/api/cli.md b/doc/api/cli.md index a5a07619da91c2..cf509503dc838a 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1507,7 +1507,7 @@ option set. ### `--test-shard` Test suite shard to execute in a format of `/`, where diff --git a/doc/api/dgram.md b/doc/api/dgram.md index aa5030d94bce01..c46511994a72b9 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -375,7 +375,7 @@ provided, it is added as a listener for the [`'close'`][] event. ### `socket[Symbol.asyncDispose]()` > Stability: 1 - Experimental diff --git a/doc/api/events.md b/doc/api/events.md index d728a7aa49b8ad..817cf73fbdcc3d 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -1806,7 +1806,7 @@ setMaxListeners(5, target, emitter); ## `events.addAbortListener(signal, resource)` > Stability: 1 - Experimental diff --git a/doc/api/net.md b/doc/api/net.md index c642752349fdc7..cfd11926057215 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -360,7 +360,7 @@ was not open when it was closed. ### `server[Symbol.asyncDispose]()` > Stability: 1 - Experimental diff --git a/doc/api/timers.md b/doc/api/timers.md index 768f59fd6f1d2b..5116ce6989b293 100644 --- a/doc/api/timers.md +++ b/doc/api/timers.md @@ -66,7 +66,7 @@ invoked. Calling `immediate.unref()` multiple times will have no effect. ### `immediate[Symbol.dispose]()` > Stability: 1 - Experimental @@ -170,7 +170,7 @@ thread. This allows enhanced compatibility with browser ### `timeout[Symbol.dispose]()` > Stability: 1 - Experimental diff --git a/doc/changelogs/CHANGELOG_V20.md b/doc/changelogs/CHANGELOG_V20.md index 9a9f76334017bb..78d0f53bcca81a 100644 --- a/doc/changelogs/CHANGELOG_V20.md +++ b/doc/changelogs/CHANGELOG_V20.md @@ -8,6 +8,7 @@ +20.5.0
20.4.0
20.3.1
20.3.0
@@ -40,6 +41,74 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2023-07-18, Version 20.5.0 (Current), @juanarbol + +### Notable Changes + +* \[[`45be29d89f`](https://github.com/nodejs/node/commit/45be29d89f)] - **doc**: add atlowChemi to collaborators (atlowChemi) [#48757](https://github.com/nodejs/node/pull/48757) +* \[[`a316808136`](https://github.com/nodejs/node/commit/a316808136)] - **(SEMVER-MINOR)** **events**: allow safely adding listener to abortSignal (Chemi Atlow) [#48596](https://github.com/nodejs/node/pull/48596) +* \[[`986b46a567`](https://github.com/nodejs/node/commit/986b46a567)] - **fs**: add a fast-path for readFileSync utf-8 (Yagiz Nizipli) [#48658](https://github.com/nodejs/node/pull/48658) +* \[[`0ef73ff6f0`](https://github.com/nodejs/node/commit/0ef73ff6f0)] - **(SEMVER-MINOR)** **test\_runner**: add shards support (Raz Luvaton) [#48639](https://github.com/nodejs/node/pull/48639) + +### Commits + +* \[[`eb0aba59b8`](https://github.com/nodejs/node/commit/eb0aba59b8)] - **bootstrap**: use correct descriptor for Symbol.{dispose,asyncDispose} (Jordan Harband) [#48703](https://github.com/nodejs/node/pull/48703) +* \[[`e2d0195dcf`](https://github.com/nodejs/node/commit/e2d0195dcf)] - **bootstrap**: hide experimental web globals with flag kNoBrowserGlobals (Chengzhong Wu) [#48545](https://github.com/nodejs/node/pull/48545) +* \[[`67a1018389`](https://github.com/nodejs/node/commit/67a1018389)] - **build**: do not pass target toolchain flags to host toolchain (Ivan Trubach) [#48597](https://github.com/nodejs/node/pull/48597) +* \[[`7d843bb942`](https://github.com/nodejs/node/commit/7d843bb942)] - **child\_process**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`4e08160f8c`](https://github.com/nodejs/node/commit/4e08160f8c)] - **child\_process**: support `Symbol.dispose` (Moshe Atlow) [#48551](https://github.com/nodejs/node/pull/48551) +* \[[`ef7728bf36`](https://github.com/nodejs/node/commit/ef7728bf36)] - **deps**: update nghttp2 to 1.55.1 (Node.js GitHub Bot) [#48790](https://github.com/nodejs/node/pull/48790) +* \[[`1454f02499`](https://github.com/nodejs/node/commit/1454f02499)] - **deps**: update nghttp2 to 1.55.0 (Node.js GitHub Bot) [#48746](https://github.com/nodejs/node/pull/48746) +* \[[`fa94debf46`](https://github.com/nodejs/node/commit/fa94debf46)] - **deps**: update minimatch to 9.0.3 (Node.js GitHub Bot) [#48704](https://github.com/nodejs/node/pull/48704) +* \[[`c73cfcc144`](https://github.com/nodejs/node/commit/c73cfcc144)] - **deps**: update acorn to 8.10.0 (Node.js GitHub Bot) [#48713](https://github.com/nodejs/node/pull/48713) +* \[[`b7a076a052`](https://github.com/nodejs/node/commit/b7a076a052)] - **deps**: V8: cherry-pick cb00db4dba6c (Keyhan Vakil) [#48671](https://github.com/nodejs/node/pull/48671) +* \[[`150e15536b`](https://github.com/nodejs/node/commit/150e15536b)] - **deps**: upgrade npm to 9.8.0 (npm team) [#48665](https://github.com/nodejs/node/pull/48665) +* \[[`c47b2cbd35`](https://github.com/nodejs/node/commit/c47b2cbd35)] - **dgram**: socket add `asyncDispose` (atlowChemi) [#48717](https://github.com/nodejs/node/pull/48717) +* \[[`002ce31cca`](https://github.com/nodejs/node/commit/002ce31cca)] - **dgram**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`45be29d89f`](https://github.com/nodejs/node/commit/45be29d89f)] - **doc**: add atlowChemi to collaborators (atlowChemi) [#48757](https://github.com/nodejs/node/pull/48757) +* \[[`69b55d2261`](https://github.com/nodejs/node/commit/69b55d2261)] - **doc**: fix ambiguity in http.md and https.md (an5er) [#48692](https://github.com/nodejs/node/pull/48692) +* \[[`caccb051c7`](https://github.com/nodejs/node/commit/caccb051c7)] - **doc**: clarify transform.\_transform() callback argument logic (Rafael Sofi-zada) [#48680](https://github.com/nodejs/node/pull/48680) +* \[[`999ae0c8c3`](https://github.com/nodejs/node/commit/999ae0c8c3)] - **doc**: fix copy node executable in Windows (Yoav Vainrich) [#48624](https://github.com/nodejs/node/pull/48624) +* \[[`7daefaeb44`](https://github.com/nodejs/node/commit/7daefaeb44)] - **doc**: drop \ of v20 changelog (Rafael Gonzaga) [#48649](https://github.com/nodejs/node/pull/48649) +* \[[`dd7ea3e1df`](https://github.com/nodejs/node/commit/dd7ea3e1df)] - **doc**: mention git node release prepare (Rafael Gonzaga) [#48644](https://github.com/nodejs/node/pull/48644) +* \[[`cc7809df21`](https://github.com/nodejs/node/commit/cc7809df21)] - **esm**: fix emit deprecation on legacy main resolve (Antoine du Hamel) [#48664](https://github.com/nodejs/node/pull/48664) +* \[[`67b13d1dba`](https://github.com/nodejs/node/commit/67b13d1dba)] - **events**: fix bug listenerCount don't compare wrapped listener (yuzheng14) [#48592](https://github.com/nodejs/node/pull/48592) +* \[[`a316808136`](https://github.com/nodejs/node/commit/a316808136)] - **(SEMVER-MINOR)** **events**: allow safely adding listener to abortSignal (Chemi Atlow) [#48596](https://github.com/nodejs/node/pull/48596) +* \[[`986b46a567`](https://github.com/nodejs/node/commit/986b46a567)] - **fs**: add a fast-path for readFileSync utf-8 (Yagiz Nizipli) [#48658](https://github.com/nodejs/node/pull/48658) +* \[[`e4333ac41f`](https://github.com/nodejs/node/commit/e4333ac41f)] - **http2**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`4a0b66e4f9`](https://github.com/nodejs/node/commit/4a0b66e4f9)] - **http2**: send RST code 8 on AbortController signal (Devraj Mehta) [#48573](https://github.com/nodejs/node/pull/48573) +* \[[`1295c76fce`](https://github.com/nodejs/node/commit/1295c76fce)] - **lib**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`dff6c25a36`](https://github.com/nodejs/node/commit/dff6c25a36)] - **meta**: bump actions/checkout from 3.5.2 to 3.5.3 (dependabot\[bot]) [#48625](https://github.com/nodejs/node/pull/48625) +* \[[`b5cb69ceaa`](https://github.com/nodejs/node/commit/b5cb69ceaa)] - **meta**: bump step-security/harden-runner from 2.4.0 to 2.4.1 (dependabot\[bot]) [#48626](https://github.com/nodejs/node/pull/48626) +* \[[`332e480b46`](https://github.com/nodejs/node/commit/332e480b46)] - **meta**: bump ossf/scorecard-action from 2.1.3 to 2.2.0 (dependabot\[bot]) [#48628](https://github.com/nodejs/node/pull/48628) +* \[[`25c5a0aaee`](https://github.com/nodejs/node/commit/25c5a0aaee)] - **meta**: bump github/codeql-action from 2.3.6 to 2.20.1 (dependabot\[bot]) [#48627](https://github.com/nodejs/node/pull/48627) +* \[[`6406f50ab1`](https://github.com/nodejs/node/commit/6406f50ab1)] - **module**: add SourceMap.lineLengths (Isaac Z. Schlueter) [#48461](https://github.com/nodejs/node/pull/48461) +* \[[`cfa69bd48c`](https://github.com/nodejs/node/commit/cfa69bd48c)] - **net**: server add `asyncDispose` (atlowChemi) [#48717](https://github.com/nodejs/node/pull/48717) +* \[[`ac11264cc5`](https://github.com/nodejs/node/commit/ac11264cc5)] - **net**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`82d6b13bf6`](https://github.com/nodejs/node/commit/82d6b13bf6)] - **permission**: add debug log when inserting fs nodes (Rafael Gonzaga) [#48677](https://github.com/nodejs/node/pull/48677) +* \[[`f4333b1cdd`](https://github.com/nodejs/node/commit/f4333b1cdd)] - **permission**: v8.writeHeapSnapshot and process.report (Rafael Gonzaga) [#48564](https://github.com/nodejs/node/pull/48564) +* \[[`f691dca6c9`](https://github.com/nodejs/node/commit/f691dca6c9)] - **readline**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`227e6bd898`](https://github.com/nodejs/node/commit/227e6bd898)] - **src**: pass syscall on `fs.readFileSync` fail operation (Yagiz Nizipli) [#48815](https://github.com/nodejs/node/pull/48815) +* \[[`a9a4b73653`](https://github.com/nodejs/node/commit/a9a4b73653)] - **src**: make BaseObject iteration order deterministic (Joyee Cheung) [#48702](https://github.com/nodejs/node/pull/48702) +* \[[`d99ea4845a`](https://github.com/nodejs/node/commit/d99ea4845a)] - **src**: remove kEagerCompile for CompileFunction (Keyhan Vakil) [#48671](https://github.com/nodejs/node/pull/48671) +* \[[`df363d0010`](https://github.com/nodejs/node/commit/df363d0010)] - **src**: deduplicate X509 getter implementations (Tobias Nießen) [#48563](https://github.com/nodejs/node/pull/48563) +* \[[`9cf2e1f55b`](https://github.com/nodejs/node/commit/9cf2e1f55b)] - **src,lib**: reducing C++ calls of esm legacy main resolve (Vinicius Lourenço) [#48325](https://github.com/nodejs/node/pull/48325) +* \[[`daeb21dde9`](https://github.com/nodejs/node/commit/daeb21dde9)] - **stream**: fix deadlock when pipeing to full sink (Robert Nagy) [#48691](https://github.com/nodejs/node/pull/48691) +* \[[`5a382d02d6`](https://github.com/nodejs/node/commit/5a382d02d6)] - **stream**: use addAbortListener (atlowChemi) [#48550](https://github.com/nodejs/node/pull/48550) +* \[[`6e82077dd4`](https://github.com/nodejs/node/commit/6e82077dd4)] - **test**: deflake test-net-throttle (Luigi Pinca) [#48599](https://github.com/nodejs/node/pull/48599) +* \[[`d378b2c822`](https://github.com/nodejs/node/commit/d378b2c822)] - **test**: move test-net-throttle to parallel (Luigi Pinca) [#48599](https://github.com/nodejs/node/pull/48599) +* \[[`dfa0aee5bf`](https://github.com/nodejs/node/commit/dfa0aee5bf)] - _**Revert**_ "**test**: remove test-crypto-keygen flaky designation" (Luigi Pinca) [#48652](https://github.com/nodejs/node/pull/48652) +* \[[`0ef73ff6f0`](https://github.com/nodejs/node/commit/0ef73ff6f0)] - **(SEMVER-MINOR)** **test\_runner**: add shards support (Raz Luvaton) [#48639](https://github.com/nodejs/node/pull/48639) +* \[[`e2442bb7ef`](https://github.com/nodejs/node/commit/e2442bb7ef)] - **timers**: support Symbol.dispose (Moshe Atlow) [#48633](https://github.com/nodejs/node/pull/48633) +* \[[`4398ade426`](https://github.com/nodejs/node/commit/4398ade426)] - **tools**: run fetch\_deps.py with Python 3 (Richard Lau) [#48729](https://github.com/nodejs/node/pull/48729) +* \[[`38ce95d054`](https://github.com/nodejs/node/commit/38ce95d054)] - **tools**: update doc to unist-util-select\@5.0.0 unist-util-visit\@5.0.0 (Node.js GitHub Bot) [#48714](https://github.com/nodejs/node/pull/48714) +* \[[`b25e78a998`](https://github.com/nodejs/node/commit/b25e78a998)] - **tools**: update lint-md-dependencies to rollup\@3.26.2 (Node.js GitHub Bot) [#48705](https://github.com/nodejs/node/pull/48705) +* \[[`a1f4ff7c59`](https://github.com/nodejs/node/commit/a1f4ff7c59)] - **tools**: update eslint to 8.44.0 (Node.js GitHub Bot) [#48632](https://github.com/nodejs/node/pull/48632) +* \[[`42dc6eb698`](https://github.com/nodejs/node/commit/42dc6eb698)] - **tools**: update lint-md-dependencies to rollup\@3.26.0 (Node.js GitHub Bot) [#48631](https://github.com/nodejs/node/pull/48631) +* \[[`07bfcc45ab`](https://github.com/nodejs/node/commit/07bfcc45ab)] - **url**: fix `canParse` false value when v8 optimizes (Yagiz Nizipli) [#48817](https://github.com/nodejs/node/pull/48817) + ## 2023-07-05, Version 20.4.0 (Current), @RafaelGSS From ac34e7561ab4771ed1a953efc92dc851ed468e3d Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 21 Jul 2023 00:57:00 +0200 Subject: [PATCH 05/20] src: support snapshot in single executable applications This patch adds snapshot support to single executable applications. To build a snapshot from the main script when preparing the blob that will be injected into the single executable application, add `"useSnapshot": true` to the configuration passed to `--experimental-sea-config`. For example: ``` { "main": "snapshot.js", "output": "sea-prep.blob", "useSnapshot": true } ``` The main script used to build the snapshot must invoke `v8.startupSnapshot.setDeserializeMainFunction()` to configure the entry point. The generated startup snapshot would be part of the preparation blob and get injected into the final executable. When the single executable application is launched, instead of running the `main` script from scratch, Node.js would instead deserialize the snapshot to get to the state initialized during build-time directly. PR-URL: https://github.com/nodejs/node/pull/46824 Refs: https://github.com/nodejs/single-executable/discussions/57 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Darshan Sen --- doc/api/single-executable-applications.md | 41 ++++++- lib/internal/main/mksnapshot.js | 9 ++ lib/internal/process/pre_execution.js | 2 +- src/node.cc | 83 +++++++++---- src/node_main_instance.cc | 10 +- src/node_sea.cc | 112 ++++++++++++++++-- src/node_sea.h | 11 +- src/node_snapshotable.cc | 7 +- ...-single-executable-application-snapshot.js | 89 ++++++++++++++ 9 files changed, 322 insertions(+), 42 deletions(-) create mode 100644 test/sequential/test-single-executable-application-snapshot.js diff --git a/doc/api/single-executable-applications.md b/doc/api/single-executable-applications.md index 2db06fdd7239e1..607158643cbc3c 100644 --- a/doc/api/single-executable-applications.md +++ b/doc/api/single-executable-applications.md @@ -6,6 +6,10 @@ added: - v19.7.0 - v18.16.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/46824 + description: Added support for "useSnapshot". --> > Stability: 1 - Experimental: This feature is being designed and will change. @@ -169,7 +173,8 @@ The configuration currently reads the following top-level fields: { "main": "/path/to/bundled/script.js", "output": "/path/to/write/the/generated/blob.blob", - "disableExperimentalSEAWarning": true // Default: false + "disableExperimentalSEAWarning": true, // Default: false + "useSnapshot": false // Default: false } ``` @@ -177,6 +182,37 @@ If the paths are not absolute, Node.js will use the path relative to the current working directory. The version of the Node.js binary used to produce the blob must be the same as the one to which the blob will be injected. +### Startup snapshot support + +The `useSnapshot` field can be used to enable startup snapshot support. In this +case the `main` script would not be when the final executable is launched. +Instead, it would be run when the single executable application preparation +blob is generated on the building machine. The generated preparation blob would +then include a snapshot capturing the states initialized by the `main` script. +The final executable with the preparation blob injected would deserialize +the snapshot at run time. + +When `useSnapshot` is true, the main script must invoke the +[`v8.startupSnapshot.setDeserializeMainFunction()`][] API to configure code +that needs to be run when the final executable is launched by the users. + +The typical pattern for an application to use snapshot in a single executable +application is: + +1. At build time, on the building machine, the main script is run to + initialize the heap to a state that's ready to take user input. The script + should also configure a main function with + [`v8.startupSnapshot.setDeserializeMainFunction()`][]. This function will be + compiled and serialized into the snapshot, but not invoked at build time. +2. At run time, the main function will be run on top of the deserialized heap + on the user machine to process user input and generate output. + +The general constraints of the startup snapshot scripts also apply to the main +script when it's used to build snapshot for the single executable application, +and the main script can use the [`v8.startupSnapshot` API][] to adapt to +these constraints. See +[documentation about startup snapshot support in Node.js][]. + ## Notes ### `require(id)` in the injected module is not file based @@ -249,6 +285,9 @@ to help us document them. [`process.execPath`]: process.md#processexecpath [`require()`]: modules.md#requireid [`require.main`]: modules.md#accessing-the-main-module +[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data +[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api +[documentation about startup snapshot support in Node.js]: cli.md#--build-snapshot [fuse]: https://www.electronjs.org/docs/latest/tutorial/fuses [postject]: https://github.com/nodejs/postject [signtool]: https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool diff --git a/lib/internal/main/mksnapshot.js b/lib/internal/main/mksnapshot.js index 2207d9253d7ec4..52d859d491a93f 100644 --- a/lib/internal/main/mksnapshot.js +++ b/lib/internal/main/mksnapshot.js @@ -16,6 +16,10 @@ const { anonymousMainPath, } = internalBinding('mksnapshot'); +const { isExperimentalSeaWarningNeeded } = internalBinding('sea'); + +const { emitExperimentalWarning } = require('internal/util'); + const { getOptionValue, } = require('internal/options'); @@ -126,6 +130,7 @@ function requireForUserSnapshot(id) { return require(normalizedId); } + function main() { prepareMainThreadExecution(true, false); initializeCallbacks(); @@ -167,6 +172,10 @@ function main() { const serializeMainArgs = [process, requireForUserSnapshot, minimalRunCjs]; + if (isExperimentalSeaWarningNeeded()) { + emitExperimentalWarning('Single executable application'); + } + if (getOptionValue('--inspect-brk')) { internalBinding('inspector').callAndPauseOnStart( runEmbedderEntryPoint, undefined, ...serializeMainArgs); diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 54258c572c07f6..4286f9cb0e5038 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -174,7 +174,7 @@ function patchProcessObject(expandArgv1) { __proto__: null, enumerable: true, // Only set it to true during snapshot building. - configurable: getOptionValue('--build-snapshot'), + configurable: isBuildingSnapshot(), value: process.argv[0], }); diff --git a/src/node.cc b/src/node.cc index f483e59dd155a8..7ca3e14ee06c3a 100644 --- a/src/node.cc +++ b/src/node.cc @@ -292,6 +292,17 @@ MaybeLocal StartExecution(Environment* env, StartExecutionCallback cb) { CHECK(!env->isolate_data()->is_building_snapshot()); +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (sea::IsSingleExecutable()) { + sea::SeaResource sea = sea::FindSingleExecutableResource(); + // The SEA preparation blob building process should already enforce this, + // this check is just here to guard against the unlikely case where + // the SEA preparation blob has been manually modified by someone. + CHECK_IMPLIES(sea.use_snapshot(), + !env->snapshot_deserialize_main().IsEmpty()); + } +#endif + // TODO(joyeecheung): move these conditions into JS land and let the // deserialize main function take precedence. For workers, we need to // move the pre-execution part into a different file that can be @@ -1198,49 +1209,66 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, return exit_code; } -ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, - const InitializationResultImpl* result) { - ExitCode exit_code = result->exit_code_enum(); +bool LoadSnapshotData(const SnapshotData** snapshot_data_ptr) { // nullptr indicates there's no snapshot data. DCHECK_NULL(*snapshot_data_ptr); + + bool is_sea = false; +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (sea::IsSingleExecutable()) { + is_sea = true; + sea::SeaResource sea = sea::FindSingleExecutableResource(); + if (sea.use_snapshot()) { + std::unique_ptr read_data = + std::make_unique(); + std::string_view snapshot = sea.main_code_or_snapshot; + if (SnapshotData::FromBlob(read_data.get(), snapshot)) { + *snapshot_data_ptr = read_data.release(); + return true; + } else { + fprintf(stderr, "Invalid snapshot data in single executable binary\n"); + return false; + } + } + } +#endif + // --snapshot-blob indicates that we are reading a customized snapshot. - if (!per_process::cli_options->snapshot_blob.empty()) { + // Ignore it when we are loading from SEA. + if (!is_sea && !per_process::cli_options->snapshot_blob.empty()) { std::string filename = per_process::cli_options->snapshot_blob; FILE* fp = fopen(filename.c_str(), "rb"); if (fp == nullptr) { fprintf(stderr, "Cannot open %s", filename.c_str()); - exit_code = ExitCode::kStartupSnapshotFailure; - return exit_code; + return false; } std::unique_ptr read_data = std::make_unique(); bool ok = SnapshotData::FromFile(read_data.get(), fp); fclose(fp); if (!ok) { - // If we fail to read the customized snapshot, - // simply exit with kStartupSnapshotFailure. - exit_code = ExitCode::kStartupSnapshotFailure; - return exit_code; + return false; } *snapshot_data_ptr = read_data.release(); - } else if (per_process::cli_options->node_snapshot) { - // If --snapshot-blob is not specified, we are reading the embedded - // snapshot, but we will skip it if --no-node-snapshot is specified. + return true; + } + + if (per_process::cli_options->node_snapshot) { + // If --snapshot-blob is not specified or if the SEA contains no snapshot, + // we are reading the embedded snapshot, but we will skip it if + // --no-node-snapshot is specified. const node::SnapshotData* read_data = SnapshotBuilder::GetEmbeddedSnapshotData(); - if (read_data != nullptr && read_data->Check()) { + if (read_data != nullptr) { + if (!read_data->Check()) { + return false; + } // If we fail to read the embedded snapshot, treat it as if Node.js // was built without one. *snapshot_data_ptr = read_data; } } - NodeMainInstance main_instance(*snapshot_data_ptr, - uv_default_loop(), - per_process::v8_platform.Platform(), - result->args(), - result->exec_args()); - exit_code = main_instance.Run(); - return exit_code; + return true; } static ExitCode StartInternal(int argc, char** argv) { @@ -1275,7 +1303,8 @@ static ExitCode StartInternal(int argc, char** argv) { std::string sea_config = per_process::cli_options->experimental_sea_config; if (!sea_config.empty()) { - return sea::BuildSingleExecutableBlob(sea_config); + return sea::BuildSingleExecutableBlob( + sea_config, result->args(), result->exec_args()); } // --build-snapshot indicates that we are in snapshot building mode. @@ -1290,7 +1319,15 @@ static ExitCode StartInternal(int argc, char** argv) { } // Without --build-snapshot, we are in snapshot loading mode. - return LoadSnapshotDataAndRun(&snapshot_data, result.get()); + if (!LoadSnapshotData(&snapshot_data)) { + return ExitCode::kStartupSnapshotFailure; + } + NodeMainInstance main_instance(snapshot_data, + uv_default_loop(), + per_process::v8_platform.Platform(), + result->args(), + result->exec_args()); + return main_instance.Run(); } int Start(int argc, char** argv) { diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 41e5bee353a579..2ef56f80dfc8f6 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -92,12 +92,16 @@ void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) { bool runs_sea_code = false; #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION if (sea::IsSingleExecutable()) { - runs_sea_code = true; sea::SeaResource sea = sea::FindSingleExecutableResource(); - std::string_view code = sea.code; - LoadEnvironment(env, code); + if (!sea.use_snapshot()) { + runs_sea_code = true; + std::string_view code = sea.main_code_or_snapshot; + LoadEnvironment(env, code); + } } #endif + // Either there is already a snapshot main function from SEA, or it's not + // a SEA at all. if (!runs_sea_code) { LoadEnvironment(env, StartExecutionCallback{}); } diff --git a/src/node_sea.cc b/src/node_sea.cc index 88741a5fce9d48..b9eabef8196750 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -6,7 +6,9 @@ #include "json_parser.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_snapshot_builder.h" #include "node_union_bytes.h" +#include "node_v8_platform-inl.h" // The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by // the Node.js project that is present only once in the entire binary. It is @@ -64,7 +66,7 @@ class SeaSerializer : public BlobSerializer { template <> size_t SeaSerializer::Write(const SeaResource& sea) { - sink.reserve(SeaResource::kHeaderSize + sea.code.size()); + sink.reserve(SeaResource::kHeaderSize + sea.main_code_or_snapshot.size()); Debug("Write SEA magic %x\n", kMagic); size_t written_total = WriteArithmetic(kMagic); @@ -74,10 +76,14 @@ size_t SeaSerializer::Write(const SeaResource& sea) { written_total += WriteArithmetic(flags); DCHECK_EQ(written_total, SeaResource::kHeaderSize); - Debug("Write SEA resource code %p, size=%zu\n", - sea.code.data(), - sea.code.size()); - written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent); + Debug("Write SEA resource %s %p, size=%zu\n", + sea.use_snapshot() ? "snapshot" : "code", + sea.main_code_or_snapshot.data(), + sea.main_code_or_snapshot.size()); + written_total += + WriteStringView(sea.main_code_or_snapshot, + sea.use_snapshot() ? StringLogMode::kAddressOnly + : StringLogMode::kAddressAndContent); return written_total; } @@ -103,8 +109,15 @@ SeaResource SeaDeserializer::Read() { Debug("Read SEA flags %x\n", static_cast(flags)); CHECK_EQ(read_total, SeaResource::kHeaderSize); - std::string_view code = ReadStringView(StringLogMode::kAddressAndContent); - Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size()); + bool use_snapshot = static_cast(flags & SeaFlags::kUseSnapshot); + std::string_view code = + ReadStringView(use_snapshot ? StringLogMode::kAddressOnly + : StringLogMode::kAddressAndContent); + + Debug("Read SEA resource %s %p, size=%zu\n", + use_snapshot ? "snapshot" : "code", + code.data(), + code.size()); return {flags, code}; } @@ -133,6 +146,10 @@ std::string_view FindSingleExecutableBlob() { } // anonymous namespace +bool SeaResource::use_snapshot() const { + return static_cast(flags & SeaFlags::kUseSnapshot); +} + SeaResource FindSingleExecutableResource() { static const SeaResource sea_resource = []() -> SeaResource { std::string_view blob = FindSingleExecutableBlob(); @@ -151,6 +168,13 @@ bool IsSingleExecutable() { } void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo& args) { + bool is_building_sea = + !per_process::cli_options->experimental_sea_config.empty(); + if (is_building_sea) { + args.GetReturnValue().Set(true); + return; + } + if (!IsSingleExecutable()) { args.GetReturnValue().Set(false); return; @@ -235,10 +259,58 @@ std::optional ParseSingleExecutableConfig( result.flags |= SeaFlags::kDisableExperimentalSeaWarning; } + std::optional use_snapshot = parser.GetTopLevelBoolField("useSnapshot"); + if (!use_snapshot.has_value()) { + FPrintF( + stderr, "\"useSnapshot\" field of %s is not a Boolean\n", config_path); + return std::nullopt; + } + if (use_snapshot.value()) { + result.flags |= SeaFlags::kUseSnapshot; + } + return result; } -ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { +ExitCode GenerateSnapshotForSEA(const SeaConfig& config, + const std::vector& args, + const std::vector& exec_args, + const std::string& main_script, + std::vector* snapshot_blob) { + SnapshotData snapshot; + // TODO(joyeecheung): make the arguments configurable through the JSON + // config or a programmatic API. + std::vector patched_args = {args[0], config.main_path}; + ExitCode exit_code = SnapshotBuilder::Generate( + &snapshot, patched_args, exec_args, main_script); + if (exit_code != ExitCode::kNoFailure) { + return exit_code; + } + auto& persistents = snapshot.env_info.principal_realm.persistent_values; + auto it = std::find_if( + persistents.begin(), persistents.end(), [](const PropInfo& prop) { + return prop.name == "snapshot_deserialize_main"; + }); + if (it == persistents.end()) { + FPrintF( + stderr, + "%s does not invoke " + "v8.startupSnapshot.setDeserializeMainFunction(), which is required " + "for snapshot scripts used to build single executable applications." + "\n", + config.main_path); + return ExitCode::kGenericUserError; + } + // We need the temporary variable for copy elision. + std::vector temp = snapshot.ToBlob(); + *snapshot_blob = std::move(temp); + return ExitCode::kNoFailure; +} + +ExitCode GenerateSingleExecutableBlob( + const SeaConfig& config, + const std::vector& args, + const std::vector& exec_args) { std::string main_script; // TODO(joyeecheung): unify the file utils. int r = ReadFileSync(&main_script, config.main_path.c_str()); @@ -248,7 +320,22 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { return ExitCode::kGenericUserError; } - SeaResource sea{config.flags, main_script}; + std::vector snapshot_blob; + bool builds_snapshot_from_main = + static_cast(config.flags & SeaFlags::kUseSnapshot); + if (builds_snapshot_from_main) { + ExitCode exit_code = GenerateSnapshotForSEA( + config, args, exec_args, main_script, &snapshot_blob); + if (exit_code != ExitCode::kNoFailure) { + return exit_code; + } + } + + SeaResource sea{ + config.flags, + builds_snapshot_from_main + ? std::string_view{snapshot_blob.data(), snapshot_blob.size()} + : std::string_view{main_script.data(), main_script.size()}}; SeaSerializer serializer; serializer.Write(sea); @@ -269,11 +356,14 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { } // anonymous namespace -ExitCode BuildSingleExecutableBlob(const std::string& config_path) { +ExitCode BuildSingleExecutableBlob(const std::string& config_path, + const std::vector& args, + const std::vector& exec_args) { std::optional config_opt = ParseSingleExecutableConfig(config_path); if (config_opt.has_value()) { - ExitCode code = GenerateSingleExecutableBlob(config_opt.value()); + ExitCode code = + GenerateSingleExecutableBlob(config_opt.value(), args, exec_args); return code; } diff --git a/src/node_sea.h b/src/node_sea.h index 8b0877df3eb0d7..f9eb5ca79d4543 100644 --- a/src/node_sea.h +++ b/src/node_sea.h @@ -6,8 +6,10 @@ #if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION) #include +#include #include #include +#include #include "node_exit_code.h" namespace node { @@ -21,19 +23,24 @@ const uint32_t kMagic = 0x143da20; enum class SeaFlags : uint32_t { kDefault = 0, kDisableExperimentalSeaWarning = 1 << 0, + kUseSnapshot = 1 << 1, }; struct SeaResource { SeaFlags flags = SeaFlags::kDefault; - std::string_view code; + std::string_view main_code_or_snapshot; + bool use_snapshot() const; static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); }; bool IsSingleExecutable(); SeaResource FindSingleExecutableResource(); std::tuple FixupArgsForSEA(int argc, char** argv); -node::ExitCode BuildSingleExecutableBlob(const std::string& config_path); +node::ExitCode BuildSingleExecutableBlob( + const std::string& config_path, + const std::vector& args, + const std::vector& exec_args); } // namespace sea } // namespace node diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 59bcea00d3e7a6..797cd16d87c603 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -585,7 +585,9 @@ size_t SnapshotSerializer::Write(const SnapshotMetadata& data) { // [ ... ] code_cache std::vector SnapshotData::ToBlob() const { + std::vector result; SnapshotSerializer w; + w.Debug("SnapshotData::ToBlob()\n"); size_t written_total = 0; @@ -603,7 +605,10 @@ std::vector SnapshotData::ToBlob() const { w.Debug("Write code_cache\n"); written_total += w.WriteVector(code_cache); w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total); - return w.sink; + + // Return using the temporary value to enable copy elision. + std::swap(result, w.sink); + return result; } void SnapshotData::ToFile(FILE* out) const { diff --git a/test/sequential/test-single-executable-application-snapshot.js b/test/sequential/test-single-executable-application-snapshot.js new file mode 100644 index 00000000000000..d1c44b6dbab3b7 --- /dev/null +++ b/test/sequential/test-single-executable-application-snapshot.js @@ -0,0 +1,89 @@ +'use strict'; + +require('../common'); + +const { + injectAndCodeSign, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the snapshot support in single executable applications. + +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSync } = require('child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = join(tmpdir.path, 'sea-config.json'); +const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob'); +const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea'); + +{ + tmpdir.refresh(); + + writeFileSync(join(tmpdir.path, 'snapshot.js'), '', 'utf-8'); + writeFileSync(configFile, ` + { + "main": "snapshot.js", + "output": "sea-prep.blob", + "useSnapshot": true + } + `); + + const child = spawnSync( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { + cwd: tmpdir.path + }); + + assert.match( + child.stderr.toString(), + /snapshot\.js does not invoke v8\.startupSnapshot\.setDeserializeMainFunction\(\)/); +} + +{ + tmpdir.refresh(); + const code = ` + const { + setDeserializeMainFunction, + } = require('v8').startupSnapshot; + + setDeserializeMainFunction(() => { + console.log('Hello from snapshot'); + }); + `; + + writeFileSync(join(tmpdir.path, 'snapshot.js'), code, 'utf-8'); + writeFileSync(configFile, ` + { + "main": "snapshot.js", + "output": "sea-prep.blob", + "useSnapshot": true + } + `); + + let child = spawnSync( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { + cwd: tmpdir.path + }); + assert.match( + child.stderr.toString(), + /Single executable application is an experimental feature/); + + assert(existsSync(seaPrepBlob)); + + copyFileSync(process.execPath, outputFile); + injectAndCodeSign(outputFile, seaPrepBlob); + + child = spawnSync(outputFile); + assert.strictEqual(child.stdout.toString().trim(), 'Hello from snapshot'); + assert.doesNotMatch( + child.stderr.toString(), + /Single executable application is an experimental feature/); +} From 4ee4718857e8b52191a7e0f9e7bbd82ca5d36342 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 21 Jul 2023 01:07:41 +0200 Subject: [PATCH 06/20] src: save the performance milestone time origin in the AliasedArray Previously we cache the time origin for the milestones in the user land, and refresh it at pre-execution. As result the time origin gets serialized into the snapshot and is therefore not deterministic. Now we store it in the milestone array as an internal value and reset the milestones at serialization time instead of deserialization time. This improves the determinism of the snapshot. Drive-by: remove the unused MarkMilestone() binding. PR-URL: https://github.com/nodejs/node/pull/48708 Refs: https://github.com/nodejs/build/issues/3043 Reviewed-By: Yagiz Nizipli --- lib/internal/perf/utils.js | 26 ++++++------ lib/internal/process/pre_execution.js | 5 --- src/env.cc | 4 +- src/node_perf.cc | 59 +++++++++++++++------------ src/node_perf_common.h | 22 ++++++---- 5 files changed, 60 insertions(+), 56 deletions(-) diff --git a/lib/internal/perf/utils.js b/lib/internal/perf/utils.js index a92e177e4246e9..a0b7955c70481c 100644 --- a/lib/internal/perf/utils.js +++ b/lib/internal/perf/utils.js @@ -1,33 +1,33 @@ 'use strict'; -const binding = internalBinding('performance'); const { + constants: { + NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN, + }, milestones, - getTimeOrigin, -} = binding; +} = internalBinding('performance'); -// TODO(joyeecheung): we may want to warn about access to -// this during snapshot building. -let timeOrigin = getTimeOrigin(); +function getTimeOrigin() { + // Do not cache this to prevent it from being serialized into the + // snapshot. + return milestones[NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN] / 1e6; +} +// Returns the time relative to the process start time in milliseconds. function now() { const hr = process.hrtime(); - return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin; + return (hr[0] * 1000 + hr[1] / 1e6) - getTimeOrigin(); } +// Returns the milestone relative to the process start time in milliseconds. function getMilestoneTimestamp(milestoneIdx) { const ns = milestones[milestoneIdx]; if (ns === -1) return ns; - return ns / 1e6 - timeOrigin; -} - -function refreshTimeOrigin() { - timeOrigin = getTimeOrigin(); + return ns / 1e6 - getTimeOrigin(); } module.exports = { now, getMilestoneTimestamp, - refreshTimeOrigin, }; diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 4286f9cb0e5038..f785fa8f80a3c1 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -67,7 +67,6 @@ function prepareExecution(options) { // Patch the process object with legacy properties and normalizations patchProcessObject(expandArgv1); setupTraceCategoryState(); - setupPerfHooks(); setupInspectorHooks(); setupWarningHandler(); setupFetch(); @@ -423,10 +422,6 @@ function setupTraceCategoryState() { toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks')); } -function setupPerfHooks() { - require('internal/perf/utils').refreshTimeOrigin(); -} - function setupInspectorHooks() { // If Debugger.setAsyncCallStackDepth is sent during bootstrap, // we cannot immediately call into JS to enable the hooks, which could diff --git a/src/env.cc b/src/env.cc index 64b8fa6e34cc28..ab9c7aa75af488 100644 --- a/src/env.cc +++ b/src/env.cc @@ -783,7 +783,7 @@ Environment::Environment(IsolateData* isolate_data, destroy_async_id_list_.reserve(512); performance_state_ = std::make_unique( - isolate, MAYBE_FIELD_PTR(env_info, performance_state)); + isolate, time_origin_, MAYBE_FIELD_PTR(env_info, performance_state)); if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACING_CATEGORY_NODE1(environment)) != 0) { @@ -1732,7 +1732,7 @@ void Environment::DeserializeProperties(const EnvSerializeInfo* info) { immediate_info_.Deserialize(ctx); timeout_info_.Deserialize(ctx); tick_info_.Deserialize(ctx); - performance_state_->Deserialize(ctx); + performance_state_->Deserialize(ctx, time_origin_); exit_info_.Deserialize(ctx); stream_base_state_.Deserialize(ctx); should_abort_on_uncaught_toggle_.Deserialize(ctx); diff --git a/src/node_perf.cc b/src/node_perf.cc index 555a90b0a76091..1acaa9dfe47145 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -20,7 +20,6 @@ using v8::Function; using v8::FunctionCallbackInfo; using v8::GCCallbackFlags; using v8::GCType; -using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Local; @@ -43,6 +42,7 @@ const double performance_process_start_timestamp = uint64_t performance_v8_start; PerformanceState::PerformanceState(Isolate* isolate, + uint64_t time_origin, const PerformanceState::SerializeInfo* info) : root(isolate, sizeof(performance_state_internal), @@ -58,24 +58,51 @@ PerformanceState::PerformanceState(Isolate* isolate, root, MAYBE_FIELD_PTR(info, observers)) { if (info == nullptr) { - for (size_t i = 0; i < milestones.Length(); i++) milestones[i] = -1.; + // For performance states initialized from scratch, reset + // all the milestones and initialize the time origin. + // For deserialized performance states, we will do the + // initialization in the deserialize callback. + ResetMilestones(); + Initialize(time_origin); + } +} + +void PerformanceState::ResetMilestones() { + size_t milestones_length = milestones.Length(); + for (size_t i = 0; i < milestones_length; ++i) { + milestones[i] = -1; } } PerformanceState::SerializeInfo PerformanceState::Serialize( v8::Local context, v8::SnapshotCreator* creator) { + // Reset all the milestones to improve determinism in the snapshot. + // We'll re-initialize them after deserialization. + ResetMilestones(); + SerializeInfo info{root.Serialize(context, creator), milestones.Serialize(context, creator), observers.Serialize(context, creator)}; return info; } -void PerformanceState::Deserialize(v8::Local context) { +void PerformanceState::Initialize(uint64_t time_origin) { + // We are only reusing the milestone array to store the time origin, so do + // not use the Mark() method. The time origin milestone is not exposed + // to user land. + this->milestones[NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN] = + static_cast(time_origin); +} + +void PerformanceState::Deserialize(v8::Local context, + uint64_t time_origin) { + // Resets the pointers. root.Deserialize(context); - // This is just done to set up the pointers, we will actually reset - // all the milestones after deserialization. milestones.Deserialize(context); observers.Deserialize(context); + + // Re-initialize the time origin i.e. the process start time. + Initialize(time_origin); } std::ostream& operator<<(std::ostream& o, @@ -96,18 +123,6 @@ void PerformanceState::Mark(PerformanceMilestone milestone, uint64_t ts) { TRACE_EVENT_SCOPE_THREAD, ts / 1000); } -// Allows specific Node.js lifecycle milestones to be set from JavaScript -void MarkMilestone(const FunctionCallbackInfo& args) { - Realm* realm = Realm::GetCurrent(args); - // TODO(legendecas): Remove this check once the sub-realms are supported. - CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); - Environment* env = realm->env(); - PerformanceMilestone milestone = - static_cast(args[0].As()->Value()); - if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) - env->performance_state()->Mark(milestone); -} - void SetupPerformanceObservers(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); // TODO(legendecas): Remove this check once the sub-realms are supported. @@ -275,12 +290,6 @@ void CreateELDHistogram(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(histogram->object()); } -void GetTimeOrigin(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - args.GetReturnValue().Set( - Number::New(args.GetIsolate(), env->time_origin() / NANOS_PER_MILLIS)); -} - void GetTimeOriginTimeStamp(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); args.GetReturnValue().Set(Number::New( @@ -300,7 +309,6 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, HistogramBase::Initialize(isolate_data, target); - SetMethod(isolate, target, "markMilestone", MarkMilestone); SetMethod(isolate, target, "setupObservers", SetupPerformanceObservers); SetMethod(isolate, target, @@ -312,7 +320,6 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, RemoveGarbageCollectionTracking); SetMethod(isolate, target, "notify", Notify); SetMethod(isolate, target, "loopIdleTime", LoopIdleTime); - SetMethod(isolate, target, "getTimeOrigin", GetTimeOrigin); SetMethod(isolate, target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); SetMethod(isolate, target, "createELDHistogram", CreateELDHistogram); SetMethod(isolate, target, "markBootstrapComplete", MarkBootstrapComplete); @@ -373,13 +380,11 @@ void CreatePerContextProperties(Local target, } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - registry->Register(MarkMilestone); registry->Register(SetupPerformanceObservers); registry->Register(InstallGarbageCollectionTracking); registry->Register(RemoveGarbageCollectionTracking); registry->Register(Notify); registry->Register(LoopIdleTime); - registry->Register(GetTimeOrigin); registry->Register(GetTimeOriginTimeStamp); registry->Register(CreateELDHistogram); registry->Register(MarkBootstrapComplete); diff --git a/src/node_perf_common.h b/src/node_perf_common.h index d519222616a174..dd757651c09e9f 100644 --- a/src/node_perf_common.h +++ b/src/node_perf_common.h @@ -24,15 +24,15 @@ extern const uint64_t performance_process_start; extern const double performance_process_start_timestamp; extern uint64_t performance_v8_start; -#define NODE_PERFORMANCE_MILESTONES(V) \ - V(ENVIRONMENT, "environment") \ - V(NODE_START, "nodeStart") \ - V(V8_START, "v8Start") \ - V(LOOP_START, "loopStart") \ - V(LOOP_EXIT, "loopExit") \ +#define NODE_PERFORMANCE_MILESTONES(V) \ + V(TIME_ORIGIN, "timeOrigin") \ + V(ENVIRONMENT, "environment") \ + V(NODE_START, "nodeStart") \ + V(V8_START, "v8Start") \ + V(LOOP_START, "loopStart") \ + V(LOOP_EXIT, "loopExit") \ V(BOOTSTRAP_COMPLETE, "bootstrapComplete") - #define NODE_PERFORMANCE_ENTRY_TYPES(V) \ V(GC, "gc") \ V(HTTP, "http") \ @@ -62,10 +62,12 @@ class PerformanceState { AliasedBufferIndex observers; }; - explicit PerformanceState(v8::Isolate* isolate, const SerializeInfo* info); + explicit PerformanceState(v8::Isolate* isolate, + uint64_t time_origin, + const SerializeInfo* info); SerializeInfo Serialize(v8::Local context, v8::SnapshotCreator* creator); - void Deserialize(v8::Local context); + void Deserialize(v8::Local context, uint64_t time_origin); friend std::ostream& operator<<(std::ostream& o, const SerializeInfo& i); AliasedUint8Array root; @@ -79,6 +81,8 @@ class PerformanceState { uint64_t ts = PERFORMANCE_NOW()); private: + void Initialize(uint64_t time_origin); + void ResetMilestones(); struct performance_state_internal { // doubles first so that they are always sizeof(double)-aligned double milestones[NODE_PERFORMANCE_MILESTONE_INVALID]; From 24c3d8a1b33abb3f292c7d3c145d46e552eaddb1 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:53:58 +0300 Subject: [PATCH 07/20] test_runner: call abort on test finish PR-URL: https://github.com/nodejs/node/pull/48827 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow --- lib/internal/test_runner/test.js | 6 + .../aborts/failed-test-still-call-abort.js | 25 ++++ .../successful-test-still-call-abort.js | 23 ++++ .../aborts/wait-for-abort-helper.js | 19 +++ test/parallel/test-runner-run.mjs | 111 +++++++++++++++--- 5 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 test/fixtures/test-runner/aborts/failed-test-still-call-abort.js create mode 100644 test/fixtures/test-runner/aborts/successful-test-still-call-abort.js create mode 100644 test/fixtures/test-runner/aborts/wait-for-abort-helper.js diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index f5cc0fb98f6271..9a333e1457fbc0 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -601,6 +601,12 @@ class Test extends AsyncResource { } else { this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); } + } finally { + // Do not abort hooks and the root test as hooks instance are shared between tests suite so aborting them will + // cause them to not run for further tests. + if (this.parent !== null) { + this.#abortController.abort(); + } } // Clean up the test. Then, try to report the results and execute any diff --git a/test/fixtures/test-runner/aborts/failed-test-still-call-abort.js b/test/fixtures/test-runner/aborts/failed-test-still-call-abort.js new file mode 100644 index 00000000000000..496d2331422c00 --- /dev/null +++ b/test/fixtures/test-runner/aborts/failed-test-still-call-abort.js @@ -0,0 +1,25 @@ +const {test, afterEach} = require('node:test'); +const assert = require('node:assert'); +const { waitForAbort } = require('./wait-for-abort-helper'); + +let testCount = 0; +let signal; + +afterEach(() => { + assert.equal(signal.aborted, false); + + waitForAbort({ testNumber: ++testCount, signal }); +}); + +test("sync", (t) => { + signal = t.signal; + assert.equal(signal.aborted, false); + throw new Error('failing the sync test'); +}); + +test("async", async (t) => { + await null; + signal = t.signal; + assert.equal(signal.aborted, false); + throw new Error('failing the async test'); +}); diff --git a/test/fixtures/test-runner/aborts/successful-test-still-call-abort.js b/test/fixtures/test-runner/aborts/successful-test-still-call-abort.js new file mode 100644 index 00000000000000..4f3879c624de44 --- /dev/null +++ b/test/fixtures/test-runner/aborts/successful-test-still-call-abort.js @@ -0,0 +1,23 @@ +const {test, afterEach} = require('node:test'); +const assert = require('node:assert'); +const {waitForAbort} = require("./wait-for-abort-helper"); + +let testCount = 0; +let signal; + +afterEach(() => { + assert.equal(signal.aborted, false); + + waitForAbort({ testNumber: ++testCount, signal }); +}); + +test("sync", (t) => { + signal = t.signal; + assert.equal(signal.aborted, false); +}); + +test("async", async (t) => { + await null; + signal = t.signal; + assert.equal(signal.aborted, false); +}); diff --git a/test/fixtures/test-runner/aborts/wait-for-abort-helper.js b/test/fixtures/test-runner/aborts/wait-for-abort-helper.js new file mode 100644 index 00000000000000..89eda7ed9138ca --- /dev/null +++ b/test/fixtures/test-runner/aborts/wait-for-abort-helper.js @@ -0,0 +1,19 @@ +module.exports = { + waitForAbort: function ({ testNumber, signal }) { + let retries = 0; + + const interval = setInterval(() => { + retries++; + if(signal.aborted) { + console.log(`abort called for test ${testNumber}`); + clearInterval(interval); + return; + } + + if(retries > 100) { + clearInterval(interval); + throw new Error(`abort was not called for test ${testNumber}`); + } + }, 10); + } +} diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index b5c41b8ae925de..7e4a8fbe76753a 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -118,29 +118,104 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { assert.strictEqual(result[5], 'ok 2 - this should be executed\n'); }); - it('should stop watch mode when abortSignal aborts', async () => { + it('should emit "test:watch:drained" event on watch mode', async () => { const controller = new AbortController(); - const result = await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal }) - .compose(async function* (source) { - for await (const chunk of source) { - if (chunk.type === 'test:pass') { - controller.abort(); - yield chunk.data.name; - } - } - }) - .toArray(); - assert.deepStrictEqual(result, ['this should pass']); + await run({ + files: [join(testFixtures, 'test/random.cjs')], + watch: true, + signal: controller.signal, + }).on('data', function({ type }) { + if (type === 'test:watch:drained') { + controller.abort(); + } + }); }); - it('should emit "test:watch:drained" event on watch mode', async () => { - const controller = new AbortController(); - await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal }) - .on('data', function({ type }) { - if (type === 'test:watch:drained') { - controller.abort(); + describe('AbortSignal', () => { + it('should stop watch mode when abortSignal aborts', async () => { + const controller = new AbortController(); + const result = await run({ + files: [join(testFixtures, 'test/random.cjs')], + watch: true, + signal: controller.signal, + }) + .compose(async function* (source) { + for await (const chunk of source) { + if (chunk.type === 'test:pass') { + controller.abort(); + yield chunk.data.name; + } + } + }) + .toArray(); + assert.deepStrictEqual(result, ['this should pass']); + }); + + it('should abort when test succeeded', async () => { + const stream = run({ + files: [ + fixtures.path( + 'test-runner', + 'aborts', + 'successful-test-still-call-abort.js' + ), + ], + }); + + let passedTestCount = 0; + let failedTestCount = 0; + + let output = ''; + for await (const data of stream) { + if (data.type === 'test:stdout') { + output += data.data.message.toString(); + } + if (data.type === 'test:fail') { + failedTestCount++; } + if (data.type === 'test:pass') { + passedTestCount++; + } + } + + assert.match(output, /abort called for test 1/); + assert.match(output, /abort called for test 2/); + assert.strictEqual(failedTestCount, 0, new Error('no tests should fail')); + assert.strictEqual(passedTestCount, 2); + }); + + it('should abort when test failed', async () => { + const stream = run({ + files: [ + fixtures.path( + 'test-runner', + 'aborts', + 'failed-test-still-call-abort.js' + ), + ], }); + + let passedTestCount = 0; + let failedTestCount = 0; + + let output = ''; + for await (const data of stream) { + if (data.type === 'test:stdout') { + output += data.data.message.toString(); + } + if (data.type === 'test:fail') { + failedTestCount++; + } + if (data.type === 'test:pass') { + passedTestCount++; + } + } + + assert.match(output, /abort called for test 1/); + assert.match(output, /abort called for test 2/); + assert.strictEqual(passedTestCount, 0, new Error('no tests should pass')); + assert.strictEqual(failedTestCount, 2); + }); }); describe('sharding', () => { From 3f4c127f92654aa5b8e4b18f7e89cb4d80be113c Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Fri, 21 Jul 2023 10:51:55 -0300 Subject: [PATCH 08/20] doc: include experimental features assessment PR-URL: https://github.com/nodejs/node/pull/48824 Refs: https://github.com/nodejs-private/node-private/issues/420 Reviewed-By: Luigi Pinca Reviewed-By: Ruy Adorno Reviewed-By: Michael Dawson Reviewed-By: Matteo Collina --- SECURITY.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index e815fbbd538217..85c185df6006cb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -199,6 +199,12 @@ the community they pose. that artifact is large enough to impact performance or cause the runtime to run out of resources. +## Assessing experimental features reports + +Experimental features are eligible to reports as any other stable feature of +Node.js. They will also be susceptible to receiving the same severity score +as any other stable feature. + ## Receiving security updates Security notifications will be distributed via the following methods. From 44027fbca89bb43d48f0c787a03271e8d4201986 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 26 Jun 2023 19:40:22 +0200 Subject: [PATCH 09/20] deps: V8: cherry-pick 93275031284c Original commit message: [cppgc] expose wrapper descriptor on CppHeap This makes it possible for embedders to: 1. Avoid creating wrapper objects that happen to have a layout that leads V8 to consider the object cppgc-managed while it's not. Refs: https://github.com/nodejs/node/pull/43521 2. Create cppgc-managed wrapper objects when they do not own the CppHeap. Refs: https://github.com/nodejs/node/pull/45704 Bug: v8:13960 Change-Id: If31f4d56c5ead59dc0d56f937494d23d631f7438 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4598833 Reviewed-by: Michael Lippautz Commit-Queue: Michael Lippautz Cr-Commit-Position: refs/heads/main@{#88490} Refs: https://github.com/v8/v8/commit/93275031284c66be7852b13f1b18a8bbe7f3a0a9 PR-URL: https://github.com/nodejs/node/pull/48660 Reviewed-By: Chengzhong Wu Reviewed-By: Jiawen Geng --- common.gypi | 2 +- deps/v8/include/v8-cppgc.h | 5 +++ deps/v8/src/heap/cppgc-js/cpp-heap.cc | 4 ++ .../heap/cppgc-js/unified-heap-unittest.cc | 41 +++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index 87e9d4ebfc0ab2..6b170a52c37725 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.13', + 'v8_embedder_string': '-node.14', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-cppgc.h b/deps/v8/include/v8-cppgc.h index 4a457027c9f76b..e0d76f45016e87 100644 --- a/deps/v8/include/v8-cppgc.h +++ b/deps/v8/include/v8-cppgc.h @@ -177,6 +177,11 @@ class V8_EXPORT CppHeap { void CollectGarbageInYoungGenerationForTesting( cppgc::EmbedderStackState stack_state); + /** + * \returns the wrapper descriptor of this CppHeap. + */ + v8::WrapperDescriptor wrapper_descriptor() const; + private: CppHeap() = default; diff --git a/deps/v8/src/heap/cppgc-js/cpp-heap.cc b/deps/v8/src/heap/cppgc-js/cpp-heap.cc index 7481a81c4a832e..81fe3fb4497d89 100644 --- a/deps/v8/src/heap/cppgc-js/cpp-heap.cc +++ b/deps/v8/src/heap/cppgc-js/cpp-heap.cc @@ -147,6 +147,10 @@ void CppHeap::CollectGarbageInYoungGenerationForTesting( internal::CppHeap::CollectionType::kMinor, stack_state); } +v8::WrapperDescriptor CppHeap::wrapper_descriptor() const { + return internal::CppHeap::From(this)->wrapper_descriptor(); +} + namespace internal { namespace { diff --git a/deps/v8/test/unittests/heap/cppgc-js/unified-heap-unittest.cc b/deps/v8/test/unittests/heap/cppgc-js/unified-heap-unittest.cc index 3934eb8b008515..31b0ee07aac74d 100644 --- a/deps/v8/test/unittests/heap/cppgc-js/unified-heap-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc-js/unified-heap-unittest.cc @@ -706,4 +706,45 @@ TEST_F(UnifiedHeapTest, TracedReferenceHandlesDoNotLeak) { EXPECT_EQ(initial_count, final_count + 1); } +namespace { +class Wrappable2 final : public cppgc::GarbageCollected { + public: + static size_t destructor_call_count; + void Trace(cppgc::Visitor* visitor) const {} + ~Wrappable2() { destructor_call_count++; } +}; + +size_t Wrappable2::destructor_call_count = 0; +} // namespace + +TEST_F(UnifiedHeapTest, WrapperDescriptorGetter) { + v8::Isolate* isolate = v8_isolate(); + v8::HandleScope scope(isolate); + auto* wrappable_object = + cppgc::MakeGarbageCollected(allocation_handle()); + v8::WrapperDescriptor descriptor = + isolate->GetCppHeap()->wrapper_descriptor(); + v8::Local tmpl = v8::ObjectTemplate::New(isolate); + int size = std::max(descriptor.wrappable_type_index, + descriptor.wrappable_instance_index) + + 1; + tmpl->SetInternalFieldCount(size); + v8::Local api_object = + tmpl->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); + api_object->SetAlignedPointerInInternalField( + descriptor.wrappable_type_index, + &descriptor.embedder_id_for_garbage_collected); + api_object->SetAlignedPointerInInternalField( + descriptor.wrappable_instance_index, wrappable_object); + + Wrappable2::destructor_call_count = 0; + EXPECT_EQ(0u, Wrappable2::destructor_call_count); + CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); + EXPECT_EQ(0u, Wrappable2::destructor_call_count); + api_object->SetAlignedPointerInInternalField( + descriptor.wrappable_instance_index, nullptr); + CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); + EXPECT_EQ(1u, Wrappable2::destructor_call_count); +} + } // namespace v8::internal From b68fa599607f69f2ce3b1a3104e0d5984f6bc0d8 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 30 Jun 2023 17:33:50 +0200 Subject: [PATCH 10/20] src: use effective cppgc wrapper id to deduce non-cppgc id Previously we hard-code a wrapper id to be used in BaseObjects to avoid accidentally triggering cppgc on these non-cppgc-managed objects, but hard-coding can be be hacky and result in mismatch when we start to create CppHeap ourselves. This patch makes it more robust by deducing non-cppgc id from the effective cppgc id, if there is one. PR-URL: https://github.com/nodejs/node/pull/48660 Refs: https://github.com/v8/v8/commit/93275031284c66be7852b13f1b18a8bbe7f3a0a9 Reviewed-By: Chengzhong Wu Reviewed-By: Jiawen Geng --- node.gyp | 1 + src/base_object-inl.h | 23 +++--- src/base_object.cc | 13 +--- src/base_object.h | 11 +-- src/env-inl.h | 8 ++ src/env.cc | 47 ++++++++++++ src/env.h | 14 ++++ src/node_messaging.cc | 8 +- src/node_snapshotable.cc | 6 +- test/cctest/node_test_fixture.cc | 1 + test/cctest/test_cppgc.cc | 127 +++++++++++++++++++++++++++++++ 11 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 test/cctest/test_cppgc.cc diff --git a/node.gyp b/node.gyp index cf016ef468c275..db6d7455bf1dbd 100644 --- a/node.gyp +++ b/node.gyp @@ -1047,6 +1047,7 @@ 'test/cctest/test_aliased_buffer.cc', 'test/cctest/test_base64.cc', 'test/cctest/test_base_object_ptr.cc', + 'test/cctest/test_cppgc.cc', 'test/cctest/test_node_postmortem_metadata.cc', 'test/cctest/test_environment.cc', 'test/cctest/test_linked_binding.cc', diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 99a438a4963717..9ac29633449b7f 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -70,23 +70,28 @@ Realm* BaseObject::realm() const { return realm_; } -bool BaseObject::IsBaseObject(v8::Local obj) { +bool BaseObject::IsBaseObject(IsolateData* isolate_data, + v8::Local obj) { if (obj->InternalFieldCount() < BaseObject::kInternalFieldCount) { return false; } - void* ptr = - obj->GetAlignedPointerFromInternalField(BaseObject::kEmbedderType); - return ptr == &kNodeEmbedderId; + + uint16_t* ptr = static_cast( + obj->GetAlignedPointerFromInternalField(BaseObject::kEmbedderType)); + return ptr == isolate_data->embedder_id_for_non_cppgc(); } -void BaseObject::TagBaseObject(v8::Local object) { +void BaseObject::TagBaseObject(IsolateData* isolate_data, + v8::Local object) { DCHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount); - object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, - &kNodeEmbedderId); + object->SetAlignedPointerInInternalField( + BaseObject::kEmbedderType, isolate_data->embedder_id_for_non_cppgc()); } -void BaseObject::SetInternalFields(v8::Local object, void* slot) { - TagBaseObject(object); +void BaseObject::SetInternalFields(IsolateData* isolate_data, + v8::Local object, + void* slot) { + TagBaseObject(isolate_data, object); object->SetAlignedPointerInInternalField(BaseObject::kSlot, slot); } diff --git a/src/base_object.cc b/src/base_object.cc index 9fb3efe03869ab..58ceecca2f91d7 100644 --- a/src/base_object.cc +++ b/src/base_object.cc @@ -22,7 +22,7 @@ BaseObject::BaseObject(Realm* realm, Local object) : persistent_handle_(realm->isolate(), object), realm_(realm) { CHECK_EQ(false, object.IsEmpty()); CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount); - SetInternalFields(object, static_cast(this)); + SetInternalFields(realm->isolate_data(), object, static_cast(this)); realm->AddCleanupHook(DeleteMe, static_cast(this)); realm->modify_base_object_count(1); } @@ -71,18 +71,13 @@ void BaseObject::MakeWeak() { WeakCallbackType::kParameter); } -// This just has to be different from the Chromium ones: -// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a -// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will -// misinterpret the data stored in the embedder fields and try to garbage -// collect them. -uint16_t kNodeEmbedderId = 0x90de; - void BaseObject::LazilyInitializedJSTemplateConstructor( const FunctionCallbackInfo& args) { DCHECK(args.IsConstructCall()); CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount); - SetInternalFields(args.This(), nullptr); + Environment* env = Environment::GetCurrent(args); + DCHECK_NOT_NULL(env); + SetInternalFields(env->isolate_data(), args.This(), nullptr); } Local BaseObject::MakeLazilyInitializedJSTemplate( diff --git a/src/base_object.h b/src/base_object.h index de66a83e7fff76..b0ada1d7f38fed 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -41,8 +41,6 @@ namespace worker { class TransferData; } -extern uint16_t kNodeEmbedderId; - class BaseObject : public MemoryRetainer { public: enum InternalFields { kEmbedderType, kSlot, kInternalFieldCount }; @@ -74,10 +72,13 @@ class BaseObject : public MemoryRetainer { // was also passed to the `BaseObject()` constructor initially. // This may return `nullptr` if the C++ object has not been constructed yet, // e.g. when the JS object used `MakeLazilyInitializedJSTemplate`. - static inline void SetInternalFields(v8::Local object, + static inline void SetInternalFields(IsolateData* isolate_data, + v8::Local object, void* slot); - static inline bool IsBaseObject(v8::Local object); - static inline void TagBaseObject(v8::Local object); + static inline bool IsBaseObject(IsolateData* isolate_data, + v8::Local object); + static inline void TagBaseObject(IsolateData* isolate_data, + v8::Local object); static void LazilyInitializedJSTemplateConstructor( const v8::FunctionCallbackInfo& args); static inline BaseObject* FromJSObject(v8::Local object); diff --git a/src/env-inl.h b/src/env-inl.h index 43fc11217133c2..9d2d3bfd81308a 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -61,6 +61,14 @@ inline uv_loop_t* IsolateData::event_loop() const { return event_loop_; } +inline uint16_t* IsolateData::embedder_id_for_cppgc() const { + return &(wrapper_data_->cppgc_id); +} + +inline uint16_t* IsolateData::embedder_id_for_non_cppgc() const { + return &(wrapper_data_->non_cppgc_id); +} + inline NodeArrayBufferAllocator* IsolateData::node_allocator() const { return node_allocator_; } diff --git a/src/env.cc b/src/env.cc index ab9c7aa75af488..0452766b806062 100644 --- a/src/env.cc +++ b/src/env.cc @@ -19,6 +19,7 @@ #include "tracing/agent.h" #include "tracing/traced_value.h" #include "util-inl.h" +#include "v8-cppgc.h" #include "v8-profiler.h" #include @@ -28,6 +29,7 @@ #include #include #include +#include namespace node { @@ -497,6 +499,11 @@ void IsolateData::CreateProperties() { contextify::ContextifyContext::InitializeGlobalTemplates(this); } +constexpr uint16_t kDefaultCppGCEmebdderID = 0x90de; +Mutex IsolateData::isolate_data_mutex_; +std::unordered_map> + IsolateData::wrapper_data_map_; + IsolateData::IsolateData(Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform, @@ -510,6 +517,46 @@ IsolateData::IsolateData(Isolate* isolate, snapshot_data_(snapshot_data) { options_.reset( new PerIsolateOptions(*(per_process::cli_options->per_isolate))); + v8::CppHeap* cpp_heap = isolate->GetCppHeap(); + + uint16_t cppgc_id = kDefaultCppGCEmebdderID; + if (cpp_heap != nullptr) { + // The general convention of the wrappable layout for cppgc in the + // ecosystem is: + // [ 0 ] -> embedder id + // [ 1 ] -> wrappable instance + // If the Isolate includes a CppHeap attached by another embedder, + // And if they also use the field 0 for the ID, we DCHECK that + // the layout matches our layout, and record the embedder ID for cppgc + // to avoid accidentally enabling cppgc on non-cppgc-managed wrappers . + v8::WrapperDescriptor descriptor = cpp_heap->wrapper_descriptor(); + if (descriptor.wrappable_type_index == BaseObject::kEmbedderType) { + cppgc_id = descriptor.embedder_id_for_garbage_collected; + DCHECK_EQ(descriptor.wrappable_instance_index, BaseObject::kSlot); + } + // If the CppHeap uses the slot we use to put non-cppgc-traced BaseObject + // for embedder ID, V8 could accidentally enable cppgc on them. So + // safe guard against this. + DCHECK_NE(descriptor.wrappable_type_index, BaseObject::kSlot); + } + // We do not care about overflow since we just want this to be different + // from the cppgc id. + uint16_t non_cppgc_id = cppgc_id + 1; + + { + // GC could still be run after the IsolateData is destroyed, so we store + // the ids in a static map to ensure pointers to them are still valid + // then. In practice there should be very few variants of the cppgc id + // in one process so the size of this map should be very small. + node::Mutex::ScopedLock lock(isolate_data_mutex_); + auto it = wrapper_data_map_.find(cppgc_id); + if (it == wrapper_data_map_.end()) { + auto pair = wrapper_data_map_.emplace( + cppgc_id, new PerIsolateWrapperData{cppgc_id, non_cppgc_id}); + it = pair.first; + } + wrapper_data_ = it->second.get(); + } if (snapshot_data == nullptr) { CreateProperties(); diff --git a/src/env.h b/src/env.h index 231ca64db38e90..b57440e73d602a 100644 --- a/src/env.h +++ b/src/env.h @@ -124,6 +124,11 @@ struct IsolateDataSerializeInfo { const IsolateDataSerializeInfo& i); }; +struct PerIsolateWrapperData { + uint16_t cppgc_id; + uint16_t non_cppgc_id; +}; + class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { public: IsolateData(v8::Isolate* isolate, @@ -131,6 +136,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { MultiIsolatePlatform* platform = nullptr, ArrayBufferAllocator* node_allocator = nullptr, const SnapshotData* snapshot_data = nullptr); + SET_MEMORY_INFO_NAME(IsolateData) SET_SELF_SIZE(IsolateData) void MemoryInfo(MemoryTracker* tracker) const override; @@ -139,6 +145,9 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { bool is_building_snapshot() const { return is_building_snapshot_; } void set_is_building_snapshot(bool value) { is_building_snapshot_ = value; } + uint16_t* embedder_id_for_cppgc() const; + uint16_t* embedder_id_for_non_cppgc() const; + inline uv_loop_t* event_loop() const; inline MultiIsolatePlatform* platform() const; inline const SnapshotData* snapshot_data() const; @@ -223,6 +232,11 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { std::shared_ptr options_; worker::Worker* worker_context_ = nullptr; bool is_building_snapshot_ = false; + PerIsolateWrapperData* wrapper_data_; + + static Mutex isolate_data_mutex_; + static std::unordered_map> + wrapper_data_map_; }; struct ContextInfo { diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 15f6af079fdba7..d3d2070d623c80 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -307,7 +307,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { bool HasCustomHostObject(Isolate* isolate) override { return true; } Maybe IsHostObject(Isolate* isolate, Local object) override { - if (BaseObject::IsBaseObject(object)) { + if (BaseObject::IsBaseObject(env_->isolate_data(), object)) { return Just(true); } @@ -315,7 +315,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { } Maybe WriteHostObject(Isolate* isolate, Local object) override { - if (BaseObject::IsBaseObject(object)) { + if (BaseObject::IsBaseObject(env_->isolate_data(), object)) { return WriteHostObject( BaseObjectPtr { Unwrap(object) }); } @@ -529,7 +529,7 @@ Maybe Message::Serialize(Environment* env, return Nothing(); } BaseObjectPtr host_object; - if (BaseObject::IsBaseObject(entry)) { + if (BaseObject::IsBaseObject(env->isolate_data(), entry)) { host_object = BaseObjectPtr{Unwrap(entry)}; } else { if (!JSTransferable::IsJSTransferable(env, context, entry)) { @@ -1328,7 +1328,7 @@ JSTransferable::NestedTransferables() const { continue; } Local obj = value.As(); - if (BaseObject::IsBaseObject(obj)) { + if (BaseObject::IsBaseObject(env()->isolate_data(), obj)) { ret.emplace_back(Unwrap(obj)); continue; } diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 797cd16d87c603..6e60e636e744e8 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1164,14 +1164,16 @@ void DeserializeNodeInternalFields(Local holder, StartupData SerializeNodeContextInternalFields(Local holder, int index, - void* env) { + void* callback_data) { // We only do one serialization for the kEmbedderType slot, the result // contains everything necessary for deserializing the entire object, // including the fields whose index is bigger than kEmbedderType // (most importantly, BaseObject::kSlot). // For Node.js this design is enough for all the native binding that are // serializable. - if (index != BaseObject::kEmbedderType || !BaseObject::IsBaseObject(holder)) { + Environment* env = static_cast(callback_data); + if (index != BaseObject::kEmbedderType || + !BaseObject::IsBaseObject(env->isolate_data(), holder)) { return StartupData{nullptr, 0}; } diff --git a/test/cctest/node_test_fixture.cc b/test/cctest/node_test_fixture.cc index fe879eb5b46ded..d2662604d14ae9 100644 --- a/test/cctest/node_test_fixture.cc +++ b/test/cctest/node_test_fixture.cc @@ -34,6 +34,7 @@ void NodeTestEnvironment::SetUp() { } void NodeTestEnvironment::TearDown() { + cppgc::ShutdownProcess(); v8::V8::Dispose(); v8::V8::DisposePlatform(); NodeZeroIsolateTestFixture::platform->Shutdown(); diff --git a/test/cctest/test_cppgc.cc b/test/cctest/test_cppgc.cc new file mode 100644 index 00000000000000..49665174615870 --- /dev/null +++ b/test/cctest/test_cppgc.cc @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include "node_test_fixture.h" + +// This tests that Node.js can work with an existing CppHeap. + +// Mimic the Blink layout. +static int kWrappableTypeIndex = 0; +static int kWrappableInstanceIndex = 1; +static uint16_t kEmbedderID = 0x1; + +// Mimic a class that does not know about Node.js. +class CppGCed : public cppgc::GarbageCollected { + public: + static int kConstructCount; + static int kDestructCount; + static int kTraceCount; + + static void New(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::Local js_object = args.This(); + CppGCed* gc_object = cppgc::MakeGarbageCollected( + isolate->GetCppHeap()->GetAllocationHandle()); + js_object->SetAlignedPointerInInternalField(kWrappableTypeIndex, + &kEmbedderID); + js_object->SetAlignedPointerInInternalField(kWrappableInstanceIndex, + gc_object); + kConstructCount++; + args.GetReturnValue().Set(js_object); + } + + static v8::Local GetConstructor( + v8::Local context) { + auto ft = v8::FunctionTemplate::New(context->GetIsolate(), New); + auto ot = ft->InstanceTemplate(); + ot->SetInternalFieldCount(2); + return ft->GetFunction(context).ToLocalChecked(); + } + + CppGCed() = default; + + ~CppGCed() { kDestructCount++; } + + void Trace(cppgc::Visitor* visitor) const { kTraceCount++; } +}; + +int CppGCed::kConstructCount = 0; +int CppGCed::kDestructCount = 0; +int CppGCed::kTraceCount = 0; + +TEST_F(NodeZeroIsolateTestFixture, ExistingCppHeapTest) { + v8::Isolate* isolate = + node::NewIsolate(allocator.get(), ¤t_loop, platform.get()); + + // Create and attach the CppHeap before we set up the IsolateData so that + // it recognizes the existing heap. + std::unique_ptr cpp_heap = v8::CppHeap::Create( + platform.get(), + v8::CppHeapCreateParams( + {}, + v8::WrapperDescriptor( + kWrappableTypeIndex, kWrappableInstanceIndex, kEmbedderID))); + isolate->AttachCppHeap(cpp_heap.get()); + + // Try creating Context + IsolateData + Environment. + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + + std::unique_ptr + isolate_data{ + node::CreateIsolateData(isolate, ¤t_loop, platform.get()), + node::FreeIsolateData}; + CHECK(isolate_data); + + { + auto context = node::NewContext(isolate); + CHECK(!context.IsEmpty()); + v8::Context::Scope context_scope(context); + + // An Environment should already contain a few BaseObjects that are + // supposed to have non-cppgc IDs. + std::unique_ptr + environment{ + node::CreateEnvironment(isolate_data.get(), context, {}, {}), + node::FreeEnvironment}; + CHECK(environment); + + context->Global() + ->Set(context, + v8::String::NewFromUtf8(isolate, "CppGCed").ToLocalChecked(), + CppGCed::GetConstructor(context)) + .FromJust(); + + const char* code = + "globalThis.array = [];" + "for (let i = 0; i < 100; ++i) { array.push(new CppGCed()); }"; + node::LoadEnvironment(environment.get(), code).ToLocalChecked(); + // Request a GC and check if CppGCed::Trace() is invoked. + isolate->LowMemoryNotification(); + int exit_code = SpinEventLoop(environment.get()).FromJust(); + EXPECT_EQ(exit_code, 0); + } + + platform->DrainTasks(isolate); + + // Cleanup. + isolate->DetachCppHeap(); + cpp_heap->Terminate(); + platform->DrainTasks(isolate); + } + + platform->UnregisterIsolate(isolate); + isolate->Dispose(); + + // Check that all the objects are created and destroyed properly. + EXPECT_EQ(CppGCed::kConstructCount, 100); + EXPECT_EQ(CppGCed::kDestructCount, 100); + // GC does not have to feel pressured enough to visit all of them to + // reclaim memory before we are done with the isolate and the entire + // heap can be reclaimed. So just check at least some of them are traced. + EXPECT_GT(CppGCed::kTraceCount, 0); +} From f4863fa8e0accf2f82f4c10f4e435d8b7d572942 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Fri, 21 Jul 2023 13:19:08 -0400 Subject: [PATCH 11/20] test: validate host with commas on url.parse PR-URL: https://github.com/nodejs/node/pull/48878 Refs: https://github.com/nodejs/node/pull/48873 Refs: https://github.com/nodejs/node/issues/48855 Refs: https://github.com/nodejs/node/issues/48850 Reviewed-By: Luigi Pinca Reviewed-By: Benjamin Gruenbaum Reviewed-By: Antoine du Hamel --- test/parallel/test-url-parse-format.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/parallel/test-url-parse-format.js b/test/parallel/test-url-parse-format.js index ef4aad51d0e3ac..f8761514a30b72 100644 --- a/test/parallel/test-url-parse-format.js +++ b/test/parallel/test-url-parse-format.js @@ -1007,6 +1007,22 @@ const parseTests = { path: '/', href: 'https://evil.com$.example.com/' }, + + // Validate the output of hostname with commas. + 'x://0.0,1.1/': { + protocol: 'x:', + slashes: true, + auth: null, + host: '0.0,1.1', + port: null, + hostname: '0.0,1.1', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'x://0.0,1.1/' + } }; for (const u in parseTests) { From 9dd574c9e232515aa894795b8a2522f53024b6f7 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Fri, 21 Jul 2023 13:20:25 -0400 Subject: [PATCH 12/20] lib: remove invalid parameter to toASCII PR-URL: https://github.com/nodejs/node/pull/48878 Refs: https://github.com/nodejs/node/pull/48873 Refs: https://github.com/nodejs/node/issues/48855 Refs: https://github.com/nodejs/node/issues/48850 Reviewed-By: Luigi Pinca Reviewed-By: Benjamin Gruenbaum Reviewed-By: Antoine du Hamel --- lib/url.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/url.js b/lib/url.js index 2cb51ff362a295..1cf27ec371e581 100644 --- a/lib/url.js +++ b/lib/url.js @@ -401,10 +401,7 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { // It only converts parts of the domain name that // have non-ASCII characters, i.e. it doesn't matter if // you call it with a domain that already is ASCII-only. - - // Use lenient mode (`true`) to try to support even non-compliant - // URLs. - this.hostname = toASCII(this.hostname, true); + this.hostname = toASCII(this.hostname); // Prevent two potential routes of hostname spoofing. // 1. If this.hostname is empty, it must have become empty due to toASCII From 6c08b1fc026b5369086457a81e733938c14c0f45 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 22 Jul 2023 10:02:40 -0600 Subject: [PATCH 13/20] test_runner: fix test_runner `test:fail` event type PR-URL: https://github.com/nodejs/node/pull/48854 Reviewed-By: Luigi Pinca Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow --- doc/api/test.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/test.md b/doc/api/test.md index c24615e8b8d0ee..1a8bf3afadd75c 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2015,7 +2015,8 @@ Emitted when a test is enqueued for execution. * `data` {Object} * `details` {Object} Additional execution metadata. * `duration` {number} The duration of the test in milliseconds. - * `error` {Error} The error thrown by the test. + * `error` {Error} An error wrapping the error thrown by the test. + * `cause` {Error} The actual error thrown by the test. * `file` {string|undefined} The path of the test file, `undefined` if test was run through the REPL. * `name` {string} The test name. From 9c5e272daee12e7c2c422d117a78090f1183dd93 Mon Sep 17 00:00:00 2001 From: Hyunjin Kim Date: Sun, 23 Jul 2023 17:05:05 +0900 Subject: [PATCH 14/20] typings: sync JSDoc with the actual implementation JSDoc comment did not match the actual implementation of `FileHandle#readableWebStream`. Refs: https://github.com/nodejs/node/pull/46933 PR-URL: https://github.com/nodejs/node/pull/48853 Reviewed-By: Antoine du Hamel Reviewed-By: Deokjin Kim --- lib/internal/fs/promises.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 74af3c9782b7a9..a63575664509df 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -255,6 +255,9 @@ class FileHandle extends EventEmitter { /** * @typedef {import('../webstreams/readablestream').ReadableStream * } ReadableStream + * @param {{ + * type?: string; + * }} [options] * @returns {ReadableStream} */ readableWebStream(options = kEmptyObject) { From 841b29c49f9f96750f846a385862fd6bc821b1db Mon Sep 17 00:00:00 2001 From: Hyunjin Kim Date: Sun, 23 Jul 2023 19:11:17 +0900 Subject: [PATCH 15/20] src: use ARES_SUCCESS instead of 0 Since error messages are also using defined values, it is more clear to use the defined value even when the operation succeeds. PR-URL: https://github.com/nodejs/node/pull/48834 Reviewed-By: Paolo Insogna Reviewed-By: Luigi Pinca Reviewed-By: Daeyeon Jeong Reviewed-By: Minwoo Jung Reviewed-By: Deokjin Kim --- src/cares_wrap.cc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 96aee6df99850f..433c5822953071 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1052,7 +1052,7 @@ int AnyTraits::Parse( return status; wrap->CallOnComplete(ret); - return 0; + return ARES_SUCCESS; } int ATraits::Parse( @@ -1086,7 +1086,7 @@ int ATraits::Parse( Local ttls = AddrTTLToArray(env, addrttls, naddrttls); wrap->CallOnComplete(ret, ttls); - return 0; + return ARES_SUCCESS; } int AaaaTraits::Parse( @@ -1120,7 +1120,7 @@ int AaaaTraits::Parse( Local ttls = AddrTTLToArray(env, addrttls, naddrttls); wrap->CallOnComplete(ret, ttls); - return 0; + return ARES_SUCCESS; } int CaaTraits::Parse( @@ -1142,7 +1142,7 @@ int CaaTraits::Parse( return status; wrap->CallOnComplete(ret); - return 0; + return ARES_SUCCESS; } int CnameTraits::Parse( @@ -1165,7 +1165,7 @@ int CnameTraits::Parse( return status; wrap->CallOnComplete(ret); - return 0; + return ARES_SUCCESS; } int MxTraits::Parse( @@ -1188,7 +1188,7 @@ int MxTraits::Parse( return status; wrap->CallOnComplete(mx_records); - return 0; + return ARES_SUCCESS; } int NsTraits::Parse( @@ -1211,7 +1211,7 @@ int NsTraits::Parse( return status; wrap->CallOnComplete(names); - return 0; + return ARES_SUCCESS; } int TxtTraits::Parse( @@ -1233,7 +1233,7 @@ int TxtTraits::Parse( return status; wrap->CallOnComplete(txt_records); - return 0; + return ARES_SUCCESS; } int SrvTraits::Parse( @@ -1255,7 +1255,7 @@ int SrvTraits::Parse( return status; wrap->CallOnComplete(srv_records); - return 0; + return ARES_SUCCESS; } int PtrTraits::Parse( @@ -1279,7 +1279,7 @@ int PtrTraits::Parse( return status; wrap->CallOnComplete(aliases); - return 0; + return ARES_SUCCESS; } int NaptrTraits::Parse( @@ -1301,7 +1301,7 @@ int NaptrTraits::Parse( return status; wrap->CallOnComplete(naptr_records); - return 0; + return ARES_SUCCESS; } int SoaTraits::Parse( @@ -1352,7 +1352,7 @@ int SoaTraits::Parse( ares_free_data(soa_out); wrap->CallOnComplete(soa_record); - return 0; + return ARES_SUCCESS; } int ReverseTraits::Send(GetHostByAddrWrap* wrap, const char* name) { @@ -1396,7 +1396,7 @@ int ReverseTraits::Parse( HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); wrap->CallOnComplete(HostentToNames(env, host)); - return 0; + return ARES_SUCCESS; } namespace { From ab834ff861ea99b712e450c6283fcbb742d9fb0b Mon Sep 17 00:00:00 2001 From: npm CLI robot Date: Sun, 23 Jul 2023 03:11:27 -0700 Subject: [PATCH 16/20] deps: upgrade npm to 9.8.1 PR-URL: https://github.com/nodejs/node/pull/48838 Reviewed-By: Luke Karrys Reviewed-By: Luigi Pinca Reviewed-By: Mohammed Keyvanzadeh --- deps/npm/docs/content/commands/npm-ls.md | 2 +- deps/npm/docs/content/commands/npm.md | 2 +- deps/npm/docs/output/commands/npm-ls.html | 2 +- deps/npm/docs/output/commands/npm.html | 2 +- deps/npm/lib/commands/publish.js | 13 +- deps/npm/man/man1/npm-ls.1 | 2 +- deps/npm/man/man1/npm.1 | 2 +- .../@npmcli/arborist/package.json | 4 +- .../node_modules/@npmcli/config/package.json | 4 +- .../@npmcli/package-json/lib/index.js | 2 - .../@npmcli/package-json/lib/normalize.js | 236 ++++++++++++++++-- .../@npmcli/package-json/package.json | 11 +- .../node_modules/bin-links/lib/link-gently.js | 2 +- deps/npm/node_modules/bin-links/package.json | 7 +- deps/npm/node_modules/chalk/package.json | 6 +- .../source/vendor/supports-color/index.js | 3 +- .../node_modules/libnpmaccess/package.json | 4 +- deps/npm/node_modules/libnpmdiff/package.json | 4 +- deps/npm/node_modules/libnpmexec/lib/index.js | 8 +- deps/npm/node_modules/libnpmexec/package.json | 6 +- deps/npm/node_modules/libnpmfund/package.json | 4 +- deps/npm/node_modules/libnpmhook/package.json | 4 +- deps/npm/node_modules/libnpmorg/package.json | 4 +- deps/npm/node_modules/libnpmpack/package.json | 4 +- .../node_modules/libnpmpublish/package.json | 4 +- .../node_modules/libnpmsearch/package.json | 4 +- deps/npm/node_modules/libnpmteam/package.json | 4 +- .../node_modules/libnpmversion/package.json | 4 +- .../node_modules/minimatch/dist/cjs/ast.js | 71 ++++-- .../node_modules/minimatch/dist/mjs/ast.js | 71 ++++-- deps/npm/node_modules/minimatch/package.json | 6 +- deps/npm/node_modules/semver/README.md | 4 +- deps/npm/node_modules/semver/classes/range.js | 5 +- .../node_modules/semver/internal/constants.js | 5 + deps/npm/node_modules/semver/internal/re.js | 45 +++- deps/npm/node_modules/semver/package.json | 6 +- deps/npm/node_modules/supports-color/index.js | 2 +- .../node_modules/supports-color/package.json | 11 +- deps/npm/package.json | 26 +- .../test/lib/commands/publish.js.test.cjs | 105 ++++++++ deps/npm/test/lib/commands/exec.js | 35 +++ 41 files changed, 580 insertions(+), 166 deletions(-) diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md index 9f9e07445353f7..5a8056f18a8589 100644 --- a/deps/npm/docs/content/commands/npm-ls.md +++ b/deps/npm/docs/content/commands/npm-ls.md @@ -27,7 +27,7 @@ packages will *also* show the paths to the specified packages. For example, running `npm ls promzard` in npm's source tree will show: ```bash -npm@9.8.0 /path/to/npm +npm@9.8.1 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 ``` diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md index bf73b4670cf418..05d229bd3f6dcb 100644 --- a/deps/npm/docs/content/commands/npm.md +++ b/deps/npm/docs/content/commands/npm.md @@ -14,7 +14,7 @@ Note: This command is unaware of workspaces. ### Version -9.8.0 +9.8.1 ### Description diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index 85cc2d7a9a64cb..9be585ffa291ee 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -160,7 +160,7 @@

Description

the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm's source tree will show:

-
npm@9.8.0 /path/to/npm
+
npm@9.8.1 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 5e34bb2bffaad4..41932731f72d39 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -150,7 +150,7 @@

Table of contents

Note: This command is unaware of workspaces.

Version

-

9.8.0

+

9.8.1

Description

npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency diff --git a/deps/npm/lib/commands/publish.js b/deps/npm/lib/commands/publish.js index 8d2aa9e0e47f68..7b3e930922ecab 100644 --- a/deps/npm/lib/commands/publish.js +++ b/deps/npm/lib/commands/publish.js @@ -89,7 +89,7 @@ class Publish extends BaseCommand { // The purpose of re-reading the manifest is in case it changed, // so that we send the latest and greatest thing to the registry // note that publishConfig might have changed as well! - manifest = await this.getManifest(spec, opts) + manifest = await this.getManifest(spec, opts, true) // JSON already has the package contents if (!json) { @@ -196,11 +196,18 @@ class Publish extends BaseCommand { // if it's a directory, read it from the file system // otherwise, get the full metadata from whatever it is // XXX can't pacote read the manifest from a directory? - async getManifest (spec, opts) { + async getManifest (spec, opts, logWarnings = false) { let manifest if (spec.type === 'directory') { + const changes = [] + const pkg = await pkgJson.fix(spec.fetchSpec, { changes }) + if (changes.length && logWarnings) { + /* eslint-disable-next-line max-len */ + log.warn('publish', 'npm auto-corrected some errors in your package.json when publishing. Please run "npm pkg fix" to address these errors.') + log.warn('publish', `errors corrected:\n${changes.join('\n')}`) + } // Prepare is the special function for publishing, different than normalize - const { content } = await pkgJson.prepare(spec.fetchSpec) + const { content } = await pkg.prepare() manifest = content } else { manifest = await pacote.manifest(spec, { diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index 18be3b12c6599e..af399edb102b6f 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -20,7 +20,7 @@ Positional arguments are \fBname@version-range\fR identifiers, which will limit .P .RS 2 .nf -npm@9.8.0 /path/to/npm +npm@9.8.1 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 .fi diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 114d4defc34b8b..cbb25b2aa1a32d 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -12,7 +12,7 @@ npm Note: This command is unaware of workspaces. .SS "Version" .P -9.8.0 +9.8.1 .SS "Description" .P npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently. diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index 712d01b47b3345..a9ec27bacb0035 100644 --- a/deps/npm/node_modules/@npmcli/arborist/package.json +++ b/deps/npm/node_modules/@npmcli/arborist/package.json @@ -39,7 +39,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "benchmark": "^2.1.4", "minify-registry-metadata": "^3.0.0", "nock": "^13.3.0", @@ -91,7 +91,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" } } diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index 420981b4659fc1..76d193ba23ec4c 100644 --- a/deps/npm/node_modules/@npmcli/config/package.json +++ b/deps/npm/node_modules/@npmcli/config/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@npmcli/eslint-config": "^4.0.0", "@npmcli/mock-globals": "^1.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "tap": "^16.3.4" }, "dependencies": { @@ -50,6 +50,6 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1" + "version": "4.18.0" } } diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/index.js b/deps/npm/node_modules/@npmcli/package-json/lib/index.js index 53558a3977e4d1..0cc41c685a39e7 100644 --- a/deps/npm/node_modules/@npmcli/package-json/lib/index.js +++ b/deps/npm/node_modules/@npmcli/package-json/lib/index.js @@ -42,9 +42,7 @@ class PackageJson { 'fixNameField', 'fixVersionField', 'fixRepositoryField', - 'fixBinField', 'fixDependencies', - 'fixScriptsField', 'devDependencies', 'scriptpath', ]) diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/normalize.js b/deps/npm/node_modules/@npmcli/package-json/lib/normalize.js index 726b3f031115b9..204d4d8a8e7dd6 100644 --- a/deps/npm/node_modules/@npmcli/package-json/lib/normalize.js +++ b/deps/npm/node_modules/@npmcli/package-json/lib/normalize.js @@ -1,11 +1,89 @@ +const semver = require('semver') const fs = require('fs/promises') const { glob } = require('glob') -const normalizePackageBin = require('npm-normalize-package-bin') const legacyFixer = require('normalize-package-data/lib/fixer.js') const legacyMakeWarning = require('normalize-package-data/lib/make_warning.js') const path = require('path') const log = require('proc-log') const git = require('@npmcli/git') +const hostedGitInfo = require('hosted-git-info') + +// used to be npm-normalize-package-bin +function normalizePackageBin (pkg, changes) { + if (pkg.bin) { + if (typeof pkg.bin === 'string' && pkg.name) { + changes?.push('"bin" was converted to an object') + pkg.bin = { [pkg.name]: pkg.bin } + } else if (Array.isArray(pkg.bin)) { + changes?.push('"bin" was converted to an object') + pkg.bin = pkg.bin.reduce((acc, k) => { + acc[path.basename(k)] = k + return acc + }, {}) + } + if (typeof pkg.bin === 'object') { + for (const binKey in pkg.bin) { + if (typeof pkg.bin[binKey] !== 'string') { + delete pkg.bin[binKey] + changes?.push(`removed invalid "bin[${binKey}]"`) + continue + } + const base = path.join('/', path.basename(binKey.replace(/\\|:/g, '/'))).slice(1) + if (!base) { + delete pkg.bin[binKey] + changes?.push(`removed invalid "bin[${binKey}]"`) + continue + } + + const binTarget = path.join('/', pkg.bin[binKey].replace(/\\/g, '/')) + .replace(/\\/g, '/').slice(1) + + if (!binTarget) { + delete pkg.bin[binKey] + changes?.push(`removed invalid "bin[${binKey}]"`) + continue + } + + if (base !== binKey) { + delete pkg.bin[binKey] + changes?.push(`"bin[${binKey}]" was renamed to "bin[${base}]"`) + } + if (binTarget !== pkg.bin[binKey]) { + changes?.push(`"bin[${base}]" script name was cleaned`) + } + pkg.bin[base] = binTarget + } + + if (Object.keys(pkg.bin).length === 0) { + changes?.push('empty "bin" was removed') + delete pkg.bin + } + + return pkg + } + } + delete pkg.bin +} + +function isCorrectlyEncodedName (spec) { + return !spec.match(/[/@\s+%:]/) && + spec === encodeURIComponent(spec) +} + +function isValidScopedPackageName (spec) { + if (spec.charAt(0) !== '@') { + return false + } + + const rest = spec.slice(1).split('/') + if (rest.length !== 2) { + return false + } + + return rest[0] && rest[1] && + rest[0] === encodeURIComponent(rest[0]) && + rest[1] === encodeURIComponent(rest[1]) +} // We don't want the `changes` array in here by default because this is a hot // path for parsing packuments during install. So the calling method passes it @@ -18,17 +96,49 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) const scripts = data.scripts || {} const pkgId = `${data.name ?? ''}@${data.version ?? ''}` - legacyFixer.warn = function () { - changes?.push(legacyMakeWarning.apply(null, arguments)) - } - // name and version are load bearing so we have to clean them up first if (steps.includes('fixNameField') || steps.includes('normalizeData')) { - legacyFixer.fixNameField(data, { strict, allowLegacyCase }) + if (!data.name && !strict) { + changes?.push('Missing "name" field was set to an empty string') + data.name = '' + } else { + if (typeof data.name !== 'string') { + throw new Error('name field must be a string.') + } + if (!strict) { + const name = data.name.trim() + if (data.name !== name) { + changes?.push(`Whitespace was trimmed from "name"`) + data.name = name + } + } + + if (data.name.startsWith('.') || + !(isValidScopedPackageName(data.name) || isCorrectlyEncodedName(data.name)) || + (strict && (!allowLegacyCase) && data.name !== data.name.toLowerCase()) || + data.name.toLowerCase() === 'node_modules' || + data.name.toLowerCase() === 'favicon.ico') { + throw new Error('Invalid name: ' + JSON.stringify(data.name)) + } + } } if (steps.includes('fixVersionField') || steps.includes('normalizeData')) { - legacyFixer.fixVersionField(data, strict) + // allow "loose" semver 1.0 versions in non-strict mode + // enforce strict semver 2.0 compliance in strict mode + const loose = !strict + if (!data.version) { + data.version = '' + } else { + if (!semver.valid(data.version, loose)) { + throw new Error(`Invalid version: "${data.version}"`) + } + const version = semver.clean(data.version, loose) + if (version !== data.version) { + changes?.push(`"version" was cleaned and set to "${version}"`) + data.version = version + } + } } // remove attributes that start with "_" if (steps.includes('_attributes')) { @@ -49,6 +159,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) } // fix bundledDependencies typo + // normalize bundleDependencies if (steps.includes('bundledDependencies')) { if (data.bundleDependencies === undefined && data.bundledDependencies !== undefined) { data.bundleDependencies = data.bundledDependencies @@ -70,7 +181,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) changes?.push(`"bundleDependencies" was changed from an object to an array`) data.bundleDependencies = Object.keys(bd) } - } else { + } else if ('bundleDependencies' in data) { changes?.push(`"bundleDependencies" was removed`) delete data.bundleDependencies } @@ -84,11 +195,11 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) if (data.dependencies && data.optionalDependencies && typeof data.optionalDependencies === 'object') { for (const name in data.optionalDependencies) { - changes?.push(`optionalDependencies entry "${name}" was removed`) + changes?.push(`optionalDependencies."${name}" was removed`) delete data.dependencies[name] } if (!Object.keys(data.dependencies).length) { - changes?.push(`empty "optionalDependencies" was removed`) + changes?.push(`Empty "optionalDependencies" was removed`) delete data.dependencies } } @@ -121,20 +232,21 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) } // strip "node_modules/.bin" from scripts entries + // remove invalid scripts entries (non-strings) if (steps.includes('scripts') || steps.includes('scriptpath')) { const spre = /^(\.[/\\])?node_modules[/\\].bin[\\/]/ if (typeof data.scripts === 'object') { for (const name in data.scripts) { if (typeof data.scripts[name] !== 'string') { delete data.scripts[name] - changes?.push(`invalid scripts entry "${name}" was removed`) - } else if (steps.includes('scriptpath')) { + changes?.push(`Invalid scripts."${name}" was removed`) + } else if (steps.includes('scriptpath') && spre.test(data.scripts[name])) { data.scripts[name] = data.scripts[name].replace(spre, '') changes?.push(`scripts entry "${name}" was fixed to remove node_modules/.bin reference`) } } } else { - changes?.push(`removed invalid "scripts"`) + changes?.push(`Removed invalid "scripts"`) delete data.scripts } } @@ -154,7 +266,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) .map(line => line.replace(/^\s*#.*$/, '').trim()) .filter(line => line) data.contributors = authors - changes.push('"contributors" was auto-populated with the contents of the "AUTHORS" file') + changes?.push('"contributors" was auto-populated with the contents of the "AUTHORS" file') } catch { // do nothing } @@ -201,7 +313,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) } if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) { - normalizePackageBin(data) + normalizePackageBin(data, changes) } // expand "directories.bin" @@ -216,7 +328,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) return acc }, {}) // *sigh* - normalizePackageBin(data) + normalizePackageBin(data, changes) } // populate "gitHead" attribute @@ -320,22 +432,96 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) // Some steps are isolated so we can do a limited subset of these in `fix` if (steps.includes('fixRepositoryField') || steps.includes('normalizeData')) { - legacyFixer.fixRepositoryField(data) - } - - if (steps.includes('fixBinField') || steps.includes('normalizeData')) { - legacyFixer.fixBinField(data) + if (data.repositories) { + /* eslint-disable-next-line max-len */ + changes?.push(`"repository" was set to the first entry in "repositories" (${data.repository})`) + data.repository = data.repositories[0] + } + if (data.repository) { + if (typeof data.repository === 'string') { + changes?.push('"repository" was changed from a string to an object') + data.repository = { + type: 'git', + url: data.repository, + } + } + if (data.repository.url) { + const hosted = hostedGitInfo.fromUrl(data.repository.url) + let r + if (hosted) { + if (hosted.getDefaultRepresentation() === 'shortcut') { + r = hosted.https() + } else { + r = hosted.toString() + } + if (r !== data.repository.url) { + changes?.push(`"repository.url" was normalized to "${r}"`) + data.repository.url = r + } + } + } + } } if (steps.includes('fixDependencies') || steps.includes('normalizeData')) { - legacyFixer.fixDependencies(data, strict) - } + // peerDependencies? + // devDependencies is meaningless here, it's ignored on an installed package + for (const type of ['dependencies', 'devDependencies', 'optionalDependencies']) { + if (data[type]) { + let secondWarning = true + if (typeof data[type] === 'string') { + changes?.push(`"${type}" was converted from a string into an object`) + data[type] = data[type].trim().split(/[\n\r\s\t ,]+/) + secondWarning = false + } + if (Array.isArray(data[type])) { + if (secondWarning) { + changes?.push(`"${type}" was converted from an array into an object`) + } + const o = {} + for (const d of data[type]) { + if (typeof d === 'string') { + const dep = d.trim().split(/(:?[@\s><=])/) + const dn = dep.shift() + const dv = dep.join('').replace(/^@/, '').trim() + o[dn] = dv + } + } + data[type] = o + } + } + } + // normalize-package-data used to put optional dependencies BACK into + // dependencies here, we no longer do this - if (steps.includes('fixScriptsField') || steps.includes('normalizeData')) { - legacyFixer.fixScriptsField(data) + for (const deps of ['dependencies', 'devDependencies']) { + if (deps in data) { + if (!data[deps] || typeof data[deps] !== 'object') { + changes?.push(`Removed invalid "${deps}"`) + delete data[deps] + } else { + for (const d in data[deps]) { + const r = data[deps][d] + if (typeof r !== 'string') { + changes?.push(`Removed invalid "${deps}.${d}"`) + delete data[deps][d] + } + const hosted = hostedGitInfo.fromUrl(data[deps][d])?.toString() + if (hosted && hosted !== data[deps][d]) { + changes?.push(`Normalized git reference to "${deps}.${d}"`) + data[deps][d] = hosted.toString() + } + } + } + } + } } if (steps.includes('normalizeData')) { + legacyFixer.warn = function () { + changes?.push(legacyMakeWarning.apply(null, arguments)) + } + const legacySteps = [ 'fixDescriptionField', 'fixModulesField', diff --git a/deps/npm/node_modules/@npmcli/package-json/package.json b/deps/npm/node_modules/@npmcli/package-json/package.json index 4b9584dcad3707..33215b638db6ee 100644 --- a/deps/npm/node_modules/@npmcli/package-json/package.json +++ b/deps/npm/node_modules/@npmcli/package-json/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/package-json", - "version": "4.0.0", + "version": "4.0.1", "description": "Programmatic API to update package.json", "main": "lib/index.js", "files": [ @@ -25,7 +25,7 @@ "license": "ISC", "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.15.1", + "@npmcli/template-oss": "4.17.0", "read-package-json": "^6.0.4", "read-package-json-fast": "^3.0.2", "tap": "^16.0.1" @@ -33,10 +33,11 @@ "dependencies": { "@npmcli/git": "^4.1.0", "glob": "^10.2.2", + "hosted-git-info": "^6.1.1", "json-parse-even-better-errors": "^3.0.0", "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.1", - "proc-log": "^3.0.0" + "proc-log": "^3.0.0", + "semver": "^7.5.3" }, "repository": { "type": "git", @@ -47,7 +48,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.15.1", + "version": "4.17.0", "publish": "true" }, "tap": { diff --git a/deps/npm/node_modules/bin-links/lib/link-gently.js b/deps/npm/node_modules/bin-links/lib/link-gently.js index 89ca0f6bf6b995..d1e955ec99b029 100644 --- a/deps/npm/node_modules/bin-links/lib/link-gently.js +++ b/deps/npm/node_modules/bin-links/lib/link-gently.js @@ -28,7 +28,7 @@ const CLOBBER = Symbol('clobber - ours or in forceful mode') const linkGently = async ({ path, to, from, absFrom, force }) => { if (seen.has(to)) { - return true + return false } seen.add(to) diff --git a/deps/npm/node_modules/bin-links/package.json b/deps/npm/node_modules/bin-links/package.json index 589245a9313911..e6abb0b589808b 100644 --- a/deps/npm/node_modules/bin-links/package.json +++ b/deps/npm/node_modules/bin-links/package.json @@ -1,6 +1,6 @@ { "name": "bin-links", - "version": "4.0.1", + "version": "4.0.2", "description": "JavaScript package binary linker", "main": "./lib/index.js", "scripts": { @@ -30,7 +30,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.5.1", + "@npmcli/template-oss": "4.15.1", "require-inject": "^1.4.4", "tap": "^16.0.1" }, @@ -53,6 +53,7 @@ "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "windowsCI": false, - "version": "4.5.1" + "version": "4.15.1", + "publish": true } } diff --git a/deps/npm/node_modules/chalk/package.json b/deps/npm/node_modules/chalk/package.json index ddcf7589e9797d..3c500105bcbf25 100644 --- a/deps/npm/node_modules/chalk/package.json +++ b/deps/npm/node_modules/chalk/package.json @@ -1,6 +1,6 @@ { "name": "chalk", - "version": "5.2.0", + "version": "5.3.0", "description": "Terminal string styling done right", "license": "MIT", "repository": "chalk/chalk", @@ -61,12 +61,14 @@ "xo": "^0.53.0", "yoctodelay": "^2.0.0" }, + "sideEffects": false, "xo": { "rules": { "unicorn/prefer-string-slice": "off", "@typescript-eslint/consistent-type-imports": "off", "@typescript-eslint/consistent-type-exports": "off", - "@typescript-eslint/consistent-type-definitions": "off" + "@typescript-eslint/consistent-type-definitions": "off", + "unicorn/expiring-todo-comments": "off" } }, "c8": { diff --git a/deps/npm/node_modules/chalk/source/vendor/supports-color/index.js b/deps/npm/node_modules/chalk/source/vendor/supports-color/index.js index a7cea61e9eb5fd..4ce0a2da8d2242 100644 --- a/deps/npm/node_modules/chalk/source/vendor/supports-color/index.js +++ b/deps/npm/node_modules/chalk/source/vendor/supports-color/index.js @@ -3,6 +3,7 @@ import os from 'node:os'; import tty from 'node:tty'; // From: https://github.com/sindresorhus/has-flag/blob/main/index.js +/// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) { function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process.argv) { const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); const position = argv.indexOf(prefix + flag); @@ -111,7 +112,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { } if ('CI' in env) { - if ('GITHUB_ACTIONS' in env) { + if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) { return 3; } diff --git a/deps/npm/node_modules/libnpmaccess/package.json b/deps/npm/node_modules/libnpmaccess/package.json index 42ea3c661c2911..713cf8c264c986 100644 --- a/deps/npm/node_modules/libnpmaccess/package.json +++ b/deps/npm/node_modules/libnpmaccess/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@npmcli/eslint-config": "^4.0.0", "@npmcli/mock-registry": "^1.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "nock": "^13.3.0", "tap": "^16.3.4" }, @@ -41,7 +41,7 @@ ], "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmdiff/package.json b/deps/npm/node_modules/libnpmdiff/package.json index 2ef51fb8e03b70..ce6eb3531b32ed 100644 --- a/deps/npm/node_modules/libnpmdiff/package.json +++ b/deps/npm/node_modules/libnpmdiff/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "tap": "^16.3.4" }, "dependencies": { @@ -58,7 +58,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmexec/lib/index.js b/deps/npm/node_modules/libnpmexec/lib/index.js index b7aa43588c0fd8..34bb20769bc2c7 100644 --- a/deps/npm/node_modules/libnpmexec/lib/index.js +++ b/deps/npm/node_modules/libnpmexec/lib/index.js @@ -245,9 +245,12 @@ const exec = async (opts) => { if (add.length) { if (!yes) { + const missingPackages = add.map(a => `${a.replace(/@$/, '')}`) // set -n to always say no if (yes === false) { - throw new Error('canceled') + // Error message lists missing package(s) when process is canceled + /* eslint-disable-next-line max-len */ + throw new Error(`npx canceled due to missing packages and no YES option: ${JSON.stringify(missingPackages)}`) } if (noTTY() || ciInfo.isCI) { @@ -257,8 +260,7 @@ const exec = async (opts) => { add.map((pkg) => pkg.replace(/@$/, '')).join(', ') }`) } else { - const addList = add.map(a => ` ${a.replace(/@$/, '')}`) - .join('\n') + '\n' + const addList = missingPackages.join('\n') + '\n' const prompt = `Need to install the following packages:\n${ addList }Ok to proceed? ` diff --git a/deps/npm/node_modules/libnpmexec/package.json b/deps/npm/node_modules/libnpmexec/package.json index 290d895f5ee60e..9b86b81a998ef7 100644 --- a/deps/npm/node_modules/libnpmexec/package.json +++ b/deps/npm/node_modules/libnpmexec/package.json @@ -1,6 +1,6 @@ { "name": "libnpmexec", - "version": "6.0.2", + "version": "6.0.3", "files": [ "bin/", "lib/" @@ -51,7 +51,7 @@ "devDependencies": { "@npmcli/eslint-config": "^4.0.0", "@npmcli/mock-registry": "^1.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "bin-links": "^4.0.1", "chalk": "^5.2.0", "just-extend": "^6.2.0", @@ -73,7 +73,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" } } diff --git a/deps/npm/node_modules/libnpmfund/package.json b/deps/npm/node_modules/libnpmfund/package.json index 8e0b6d083715e3..0c863c2f92203a 100644 --- a/deps/npm/node_modules/libnpmfund/package.json +++ b/deps/npm/node_modules/libnpmfund/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "tap": "^16.3.4" }, "dependencies": { @@ -52,7 +52,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmhook/package.json b/deps/npm/node_modules/libnpmhook/package.json index bc439929e7c7a1..05b34dda75c416 100644 --- a/deps/npm/node_modules/libnpmhook/package.json +++ b/deps/npm/node_modules/libnpmhook/package.json @@ -35,7 +35,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "nock": "^13.3.0", "tap": "^16.3.4" }, @@ -44,7 +44,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmorg/package.json b/deps/npm/node_modules/libnpmorg/package.json index 0ee6abd7455a60..675d03b5b2437a 100644 --- a/deps/npm/node_modules/libnpmorg/package.json +++ b/deps/npm/node_modules/libnpmorg/package.json @@ -28,7 +28,7 @@ ], "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "minipass": "^5.0.0", "nock": "^13.3.0", "tap": "^16.3.4" @@ -49,7 +49,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmpack/package.json b/deps/npm/node_modules/libnpmpack/package.json index 12cf7aa8ee3b10..d8861c337c4d99 100644 --- a/deps/npm/node_modules/libnpmpack/package.json +++ b/deps/npm/node_modules/libnpmpack/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "nock": "^13.3.0", "spawk": "^1.7.1", "tap": "^16.3.4" @@ -46,7 +46,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmpublish/package.json b/deps/npm/node_modules/libnpmpublish/package.json index 6ea6a7181b0b71..7c7533a82c735f 100644 --- a/deps/npm/node_modules/libnpmpublish/package.json +++ b/deps/npm/node_modules/libnpmpublish/package.json @@ -26,7 +26,7 @@ "@npmcli/eslint-config": "^4.0.0", "@npmcli/mock-globals": "^1.0.0", "@npmcli/mock-registry": "^1.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "lodash.clonedeep": "^4.5.0", "nock": "^13.3.0", "tap": "^16.3.4" @@ -53,7 +53,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmsearch/package.json b/deps/npm/node_modules/libnpmsearch/package.json index e7dd7aca9baf16..32cb1f21b64221 100644 --- a/deps/npm/node_modules/libnpmsearch/package.json +++ b/deps/npm/node_modules/libnpmsearch/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "nock": "^13.3.0", "tap": "^16.3.4" }, @@ -45,7 +45,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmteam/package.json b/deps/npm/node_modules/libnpmteam/package.json index 5558224050eec7..33a77095fe8489 100644 --- a/deps/npm/node_modules/libnpmteam/package.json +++ b/deps/npm/node_modules/libnpmteam/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "nock": "^13.3.0", "tap": "^16.3.4" }, @@ -39,7 +39,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" }, "tap": { diff --git a/deps/npm/node_modules/libnpmversion/package.json b/deps/npm/node_modules/libnpmversion/package.json index 7bae86d8afe228..469f9c2bc00d67 100644 --- a/deps/npm/node_modules/libnpmversion/package.json +++ b/deps/npm/node_modules/libnpmversion/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "require-inject": "^1.4.4", "tap": "^16.3.4" }, @@ -48,7 +48,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "../../scripts/template-oss/index.js" } } diff --git a/deps/npm/node_modules/minimatch/dist/cjs/ast.js b/deps/npm/node_modules/minimatch/dist/cjs/ast.js index 191e7e871c51d9..0b0cc8f3c50b3d 100644 --- a/deps/npm/node_modules/minimatch/dist/cjs/ast.js +++ b/deps/npm/node_modules/minimatch/dist/cjs/ast.js @@ -10,7 +10,7 @@ const isExtglobType = (c) => types.has(c); // entire string, or just a single path portion, to prevent dots // and/or traversal patterns, when needed. // Exts don't need the ^ or / bit, because the root binds that already. -const startNoTraversal = '(?!\\.\\.?(?:$|/))'; +const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))'; const startNoDot = '(?!\\.)'; // characters that indicate a start of pattern needs the "no dots" bit, // because a dot *might* be matched. ( is not in the list, because in @@ -407,7 +407,8 @@ class AST { // - Since the start for a join is eg /(?!\.) and the start for a part // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root // or start or whatever) and prepend ^ or / at the Regexp construction. - toRegExpSource() { + toRegExpSource(allowDot) { + const dot = allowDot ?? !!this.#options.dot; if (this.#root === this) this.#fillNegs(); if (!this.type) { @@ -416,7 +417,7 @@ class AST { .map(p => { const [re, _, hasMagic, uflag] = typeof p === 'string' ? AST.#parseGlob(p, this.#hasMagic, noEmpty) - : p.toRegExpSource(); + : p.toRegExpSource(allowDot); this.#hasMagic = this.#hasMagic || hasMagic; this.#uflag = this.#uflag || uflag; return re; @@ -436,14 +437,14 @@ class AST { // and prevent that. const needNoTrav = // dots are allowed, and the pattern starts with [ or . - (this.#options.dot && aps.has(src.charAt(0))) || + (dot && aps.has(src.charAt(0))) || // the pattern starts with \., and then [ or . (src.startsWith('\\.') && aps.has(src.charAt(2))) || // the pattern starts with \.\., and then [ or . (src.startsWith('\\.\\.') && aps.has(src.charAt(4))); // no need to prevent dots if it can't match a dot, or if a // sub-pattern will be preventing it anyway. - const needNoDot = !this.#options.dot && aps.has(src.charAt(0)); + const needNoDot = !dot && !allowDot && aps.has(src.charAt(0)); start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ''; } } @@ -463,23 +464,13 @@ class AST { this.#uflag, ]; } + // We need to calculate the body *twice* if it's a repeat pattern + // at the start, once in nodot mode, then again in dot mode, so a + // pattern like *(?) can match 'x.y' + const repeated = this.type === '*' || this.type === '+'; // some kind of extglob const start = this.type === '!' ? '(?:(?!(?:' : '(?:'; - const body = this.#parts - .map(p => { - // extglob ASTs should only contain parent ASTs - /* c8 ignore start */ - if (typeof p === 'string') { - throw new Error('string type in extglob ast??'); - } - /* c8 ignore stop */ - // can ignore hasMagic, because extglobs are already always magic - const [re, _, _hasMagic, uflag] = p.toRegExpSource(); - this.#uflag = this.#uflag || uflag; - return re; - }) - .filter(p => !(this.isStart() && this.isEnd()) || !!p) - .join('|'); + let body = this.#partsToRegExp(dot); if (this.isStart() && this.isEnd() && !body && this.type !== '!') { // invalid extglob, has to at least be *something* present, if it's // the entire path portion. @@ -489,22 +480,37 @@ class AST { this.#hasMagic = undefined; return [s, (0, unescape_js_1.unescape)(this.toString()), false, false]; } + // XXX abstract out this map method + let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot + ? '' + : this.#partsToRegExp(true); + if (bodyDotAllowed === body) { + bodyDotAllowed = ''; + } + if (bodyDotAllowed) { + body = `(?:${body})(?:${bodyDotAllowed})*?`; + } // an empty !() is exactly equivalent to a starNoEmpty let final = ''; if (this.type === '!' && this.#emptyExt) { - final = - (this.isStart() && !this.#options.dot ? startNoDot : '') + starNoEmpty; + final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty; } else { const close = this.type === '!' ? // !() must match something,but !(x) can match '' '))' + - (this.isStart() && !this.#options.dot ? startNoDot : '') + + (this.isStart() && !dot && !allowDot ? startNoDot : '') + star + ')' : this.type === '@' ? ')' - : `)${this.type}`; + : this.type === '?' + ? ')?' + : this.type === '+' && bodyDotAllowed + ? ')' + : this.type === '*' && bodyDotAllowed + ? `)?` + : `)${this.type}`; final = start + body + close; } return [ @@ -514,6 +520,23 @@ class AST { this.#uflag, ]; } + #partsToRegExp(dot) { + return this.#parts + .map(p => { + // extglob ASTs should only contain parent ASTs + /* c8 ignore start */ + if (typeof p === 'string') { + throw new Error('string type in extglob ast??'); + } + /* c8 ignore stop */ + // can ignore hasMagic, because extglobs are already always magic + const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot); + this.#uflag = this.#uflag || uflag; + return re; + }) + .filter(p => !(this.isStart() && this.isEnd()) || !!p) + .join('|'); + } static #parseGlob(glob, hasMagic, noEmpty = false) { let escaping = false; let re = ''; diff --git a/deps/npm/node_modules/minimatch/dist/mjs/ast.js b/deps/npm/node_modules/minimatch/dist/mjs/ast.js index 9836fe7b1db023..7fb1f83e6182a0 100644 --- a/deps/npm/node_modules/minimatch/dist/mjs/ast.js +++ b/deps/npm/node_modules/minimatch/dist/mjs/ast.js @@ -7,7 +7,7 @@ const isExtglobType = (c) => types.has(c); // entire string, or just a single path portion, to prevent dots // and/or traversal patterns, when needed. // Exts don't need the ^ or / bit, because the root binds that already. -const startNoTraversal = '(?!\\.\\.?(?:$|/))'; +const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))'; const startNoDot = '(?!\\.)'; // characters that indicate a start of pattern needs the "no dots" bit, // because a dot *might* be matched. ( is not in the list, because in @@ -404,7 +404,8 @@ export class AST { // - Since the start for a join is eg /(?!\.) and the start for a part // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root // or start or whatever) and prepend ^ or / at the Regexp construction. - toRegExpSource() { + toRegExpSource(allowDot) { + const dot = allowDot ?? !!this.#options.dot; if (this.#root === this) this.#fillNegs(); if (!this.type) { @@ -413,7 +414,7 @@ export class AST { .map(p => { const [re, _, hasMagic, uflag] = typeof p === 'string' ? AST.#parseGlob(p, this.#hasMagic, noEmpty) - : p.toRegExpSource(); + : p.toRegExpSource(allowDot); this.#hasMagic = this.#hasMagic || hasMagic; this.#uflag = this.#uflag || uflag; return re; @@ -433,14 +434,14 @@ export class AST { // and prevent that. const needNoTrav = // dots are allowed, and the pattern starts with [ or . - (this.#options.dot && aps.has(src.charAt(0))) || + (dot && aps.has(src.charAt(0))) || // the pattern starts with \., and then [ or . (src.startsWith('\\.') && aps.has(src.charAt(2))) || // the pattern starts with \.\., and then [ or . (src.startsWith('\\.\\.') && aps.has(src.charAt(4))); // no need to prevent dots if it can't match a dot, or if a // sub-pattern will be preventing it anyway. - const needNoDot = !this.#options.dot && aps.has(src.charAt(0)); + const needNoDot = !dot && !allowDot && aps.has(src.charAt(0)); start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ''; } } @@ -460,23 +461,13 @@ export class AST { this.#uflag, ]; } + // We need to calculate the body *twice* if it's a repeat pattern + // at the start, once in nodot mode, then again in dot mode, so a + // pattern like *(?) can match 'x.y' + const repeated = this.type === '*' || this.type === '+'; // some kind of extglob const start = this.type === '!' ? '(?:(?!(?:' : '(?:'; - const body = this.#parts - .map(p => { - // extglob ASTs should only contain parent ASTs - /* c8 ignore start */ - if (typeof p === 'string') { - throw new Error('string type in extglob ast??'); - } - /* c8 ignore stop */ - // can ignore hasMagic, because extglobs are already always magic - const [re, _, _hasMagic, uflag] = p.toRegExpSource(); - this.#uflag = this.#uflag || uflag; - return re; - }) - .filter(p => !(this.isStart() && this.isEnd()) || !!p) - .join('|'); + let body = this.#partsToRegExp(dot); if (this.isStart() && this.isEnd() && !body && this.type !== '!') { // invalid extglob, has to at least be *something* present, if it's // the entire path portion. @@ -486,22 +477,37 @@ export class AST { this.#hasMagic = undefined; return [s, unescape(this.toString()), false, false]; } + // XXX abstract out this map method + let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot + ? '' + : this.#partsToRegExp(true); + if (bodyDotAllowed === body) { + bodyDotAllowed = ''; + } + if (bodyDotAllowed) { + body = `(?:${body})(?:${bodyDotAllowed})*?`; + } // an empty !() is exactly equivalent to a starNoEmpty let final = ''; if (this.type === '!' && this.#emptyExt) { - final = - (this.isStart() && !this.#options.dot ? startNoDot : '') + starNoEmpty; + final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty; } else { const close = this.type === '!' ? // !() must match something,but !(x) can match '' '))' + - (this.isStart() && !this.#options.dot ? startNoDot : '') + + (this.isStart() && !dot && !allowDot ? startNoDot : '') + star + ')' : this.type === '@' ? ')' - : `)${this.type}`; + : this.type === '?' + ? ')?' + : this.type === '+' && bodyDotAllowed + ? ')' + : this.type === '*' && bodyDotAllowed + ? `)?` + : `)${this.type}`; final = start + body + close; } return [ @@ -511,6 +517,23 @@ export class AST { this.#uflag, ]; } + #partsToRegExp(dot) { + return this.#parts + .map(p => { + // extglob ASTs should only contain parent ASTs + /* c8 ignore start */ + if (typeof p === 'string') { + throw new Error('string type in extglob ast??'); + } + /* c8 ignore stop */ + // can ignore hasMagic, because extglobs are already always magic + const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot); + this.#uflag = this.#uflag || uflag; + return re; + }) + .filter(p => !(this.isStart() && this.isEnd()) || !!p) + .join('|'); + } static #parseGlob(glob, hasMagic, noEmpty = false) { let escaping = false; let re = ''; diff --git a/deps/npm/node_modules/minimatch/package.json b/deps/npm/node_modules/minimatch/package.json index d5ee74e334d6a4..061c3b9f343306 100644 --- a/deps/npm/node_modules/minimatch/package.json +++ b/deps/npm/node_modules/minimatch/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter (http://blog.izs.me)", "name": "minimatch", "description": "a glob matcher in javascript", - "version": "9.0.1", + "version": "9.0.3", "repository": { "type": "git", "url": "git://github.com/isaacs/minimatch.git" @@ -60,12 +60,12 @@ "devDependencies": { "@types/brace-expansion": "^1.1.0", "@types/node": "^18.15.11", - "@types/tap": "^15.0.7", + "@types/tap": "^15.0.8", "c8": "^7.12.0", "eslint-config-prettier": "^8.6.0", "mkdirp": "1", "prettier": "^2.8.2", - "tap": "^16.3.3", + "tap": "^16.3.7", "ts-node": "^10.9.1", "typedoc": "^0.23.21", "typescript": "^4.9.3" diff --git a/deps/npm/node_modules/semver/README.md b/deps/npm/node_modules/semver/README.md index 33c762cb225dcb..043bdaed6b5fc3 100644 --- a/deps/npm/node_modules/semver/README.md +++ b/deps/npm/node_modules/semver/README.md @@ -159,7 +159,9 @@ of primitive `operators` is: For example, the comparator `>=1.2.7` would match the versions `1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6` -or `1.1.0`. +or `1.1.0`. The comparator `>1` is equivalent to `>=2.0.0` and +would match the versions `2.0.0` and `3.1.0`, but not the versions +`1.0.1` or `1.1.0`. Comparators can be joined by whitespace to form a `comparator set`, which is satisfied by the **intersection** of all of the comparators diff --git a/deps/npm/node_modules/semver/classes/range.js b/deps/npm/node_modules/semver/classes/range.js index 53c2540fd012ef..7e7c41410cbfdd 100644 --- a/deps/npm/node_modules/semver/classes/range.js +++ b/deps/npm/node_modules/semver/classes/range.js @@ -38,7 +38,7 @@ class Range { this.set = this.raw .split('||') // map the range to a 2d array of comparators - .map(r => this.parseRange(r)) + .map(r => this.parseRange(r.trim())) // throw out any comparator lists that are empty // this generally means that it was not a valid range, which is allowed // in loose mode, but will still throw if the WHOLE range is invalid. @@ -98,15 +98,18 @@ class Range { const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) debug('hyphen replace', range) + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) debug('comparator trim', range) // `~ 1.2.3` => `~1.2.3` range = range.replace(re[t.TILDETRIM], tildeTrimReplace) + debug('tilde trim', range) // `^ 1.2.3` => `^1.2.3` range = range.replace(re[t.CARETTRIM], caretTrimReplace) + debug('caret trim', range) // At this point, the range is completely trimmed and // ready to be split into comparators. diff --git a/deps/npm/node_modules/semver/internal/constants.js b/deps/npm/node_modules/semver/internal/constants.js index 25fab1ea01233b..94be1c570277a5 100644 --- a/deps/npm/node_modules/semver/internal/constants.js +++ b/deps/npm/node_modules/semver/internal/constants.js @@ -9,6 +9,10 @@ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || // Max safe segment length for coercion. const MAX_SAFE_COMPONENT_LENGTH = 16 +// Max safe length for a build identifier. The max length minus 6 characters for +// the shortest version with a build 0.0.0+BUILD. +const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 + const RELEASE_TYPES = [ 'major', 'premajor', @@ -22,6 +26,7 @@ const RELEASE_TYPES = [ module.exports = { MAX_LENGTH, MAX_SAFE_COMPONENT_LENGTH, + MAX_SAFE_BUILD_LENGTH, MAX_SAFE_INTEGER, RELEASE_TYPES, SEMVER_SPEC_VERSION, diff --git a/deps/npm/node_modules/semver/internal/re.js b/deps/npm/node_modules/semver/internal/re.js index f73ef1aa06263a..21150b3ec53b7d 100644 --- a/deps/npm/node_modules/semver/internal/re.js +++ b/deps/npm/node_modules/semver/internal/re.js @@ -1,4 +1,8 @@ -const { MAX_SAFE_COMPONENT_LENGTH } = require('./constants') +const { + MAX_SAFE_COMPONENT_LENGTH, + MAX_SAFE_BUILD_LENGTH, + MAX_LENGTH, +} = require('./constants') const debug = require('./debug') exports = module.exports = {} @@ -9,16 +13,31 @@ const src = exports.src = [] const t = exports.t = {} let R = 0 +const LETTERDASHNUMBER = '[a-zA-Z0-9-]' + +// Replace some greedy regex tokens to prevent regex dos issues. These regex are +// used internally via the safeRe object since all inputs in this library get +// normalized first to trim and collapse all extra whitespace. The original +// regexes are exported for userland consumption and lower level usage. A +// future breaking change could export the safer regex only with a note that +// all input should have extra whitespace removed. +const safeRegexReplacements = [ + ['\\s', 1], + ['\\d', MAX_LENGTH], + [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], +] + +const makeSafeRegex = (value) => { + for (const [token, max] of safeRegexReplacements) { + value = value + .split(`${token}*`).join(`${token}{0,${max}}`) + .split(`${token}+`).join(`${token}{1,${max}}`) + } + return value +} + const createToken = (name, value, isGlobal) => { - // Replace all greedy whitespace to prevent regex dos issues. These regex are - // used internally via the safeRe object since all inputs in this library get - // normalized first to trim and collapse all extra whitespace. The original - // regexes are exported for userland consumption and lower level usage. A - // future breaking change could export the safer regex only with a note that - // all input should have extra whitespace removed. - const safe = value - .split('\\s*').join('\\s{0,1}') - .split('\\s+').join('\\s') + const safe = makeSafeRegex(value) const index = R++ debug(name, index, value) t[name] = index @@ -34,13 +53,13 @@ const createToken = (name, value, isGlobal) => { // A single `0`, or a non-zero digit followed by zero or more digits. createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') -createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+') +createToken('NUMERICIDENTIFIERLOOSE', '\\d+') // ## Non-numeric Identifier // Zero or more digits, followed by a letter or hyphen, and then zero or // more letters, digits, or hyphens. -createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*') +createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) // ## Main Version // Three dot-separated numeric identifiers. @@ -75,7 +94,7 @@ createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] // ## Build Metadata Identifier // Any combination of digits, letters, or hyphens. -createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+') +createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) // ## Build Metadata // Plus sign, followed by one or more period-separated build metadata diff --git a/deps/npm/node_modules/semver/package.json b/deps/npm/node_modules/semver/package.json index 7d0aff3c03c270..c145eca2f6d125 100644 --- a/deps/npm/node_modules/semver/package.json +++ b/deps/npm/node_modules/semver/package.json @@ -1,6 +1,6 @@ { "name": "semver", - "version": "7.5.2", + "version": "7.5.4", "description": "The semantic version parser used by npm.", "main": "index.js", "scripts": { @@ -14,7 +14,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.15.1", + "@npmcli/template-oss": "4.17.0", "tap": "^16.0.0" }, "license": "ISC", @@ -53,7 +53,7 @@ "author": "GitHub Inc.", "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.15.1", + "version": "4.17.0", "engines": ">=10", "ciVersions": [ "10.0.0", diff --git a/deps/npm/node_modules/supports-color/index.js b/deps/npm/node_modules/supports-color/index.js index ca95e9f2202a6f..4ce0a2da8d2242 100644 --- a/deps/npm/node_modules/supports-color/index.js +++ b/deps/npm/node_modules/supports-color/index.js @@ -112,7 +112,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { } if ('CI' in env) { - if ('GITHUB_ACTIONS' in env) { + if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) { return 3; } diff --git a/deps/npm/node_modules/supports-color/package.json b/deps/npm/node_modules/supports-color/package.json index eb6011c6bcdc64..738684722643c9 100644 --- a/deps/npm/node_modules/supports-color/package.json +++ b/deps/npm/node_modules/supports-color/package.json @@ -1,6 +1,6 @@ { "name": "supports-color", - "version": "9.3.1", + "version": "9.4.0", "description": "Detect whether a terminal supports color", "license": "MIT", "repository": "chalk/supports-color", @@ -20,7 +20,7 @@ }, "scripts": { "//test": "xo && ava && tsd", - "test": "xo && tsd" + "test": "tsd" }, "files": [ "index.js", @@ -51,11 +51,10 @@ "16m" ], "devDependencies": { - "@types/node": "^16.11.7", - "ava": "^3.15.0", + "@types/node": "^20.3.2", + "ava": "^5.3.1", "import-fresh": "^3.3.0", "tsd": "^0.18.0", - "typescript": "^4.4.3", - "xo": "^0.49.0" + "xo": "^0.54.2" } } diff --git a/deps/npm/package.json b/deps/npm/package.json index c6ab8029946fd1..6e719a073893b0 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "9.8.0", + "version": "9.8.1", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -54,13 +54,15 @@ "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^6.3.0", "@npmcli/config": "^6.2.1", + "@npmcli/fs": "^3.1.0", "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^4.0.0", + "@npmcli/package-json": "^4.0.1", + "@npmcli/promise-spawn": "^6.0.2", "@npmcli/run-script": "^6.0.2", "abbrev": "^2.0.0", "archy": "~1.0.0", "cacache": "^17.1.3", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "ci-info": "^3.8.0", "cli-columns": "^4.0.0", "cli-table3": "^0.6.3", @@ -76,7 +78,7 @@ "json-parse-even-better-errors": "^3.0.0", "libnpmaccess": "^7.0.2", "libnpmdiff": "^5.0.19", - "libnpmexec": "^6.0.2", + "libnpmexec": "^6.0.3", "libnpmfund": "^4.0.19", "libnpmhook": "^9.0.3", "libnpmorg": "^5.0.4", @@ -86,7 +88,7 @@ "libnpmteam": "^5.0.3", "libnpmversion": "^4.0.2", "make-fetch-happen": "^11.1.1", - "minimatch": "^9.0.0", + "minimatch": "^9.0.3", "minipass": "^5.0.0", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", @@ -106,10 +108,10 @@ "proc-log": "^3.0.0", "qrcode-terminal": "^0.12.0", "read": "^2.1.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "sigstore": "^1.7.0", "ssri": "^10.0.4", - "supports-color": "^9.3.1", + "supports-color": "^9.4.0", "tar": "^6.1.15", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", @@ -122,8 +124,10 @@ "@isaacs/string-locale-compare", "@npmcli/arborist", "@npmcli/config", + "@npmcli/fs", "@npmcli/map-workspaces", "@npmcli/package-json", + "@npmcli/promise-spawn", "@npmcli/run-script", "abbrev", "archy", @@ -188,13 +192,11 @@ ], "devDependencies": { "@npmcli/docs": "^1.0.0", - "@npmcli/eslint-config": "^4.0.0", - "@npmcli/fs": "^3.1.0", + "@npmcli/eslint-config": "^4.0.2", "@npmcli/git": "^4.1.0", "@npmcli/mock-globals": "^1.0.0", "@npmcli/mock-registry": "^1.0.0", - "@npmcli/promise-spawn": "^6.0.2", - "@npmcli/template-oss": "4.14.1", + "@npmcli/template-oss": "4.18.0", "@tufjs/repo-mock": "^1.3.1", "diff": "^5.1.0", "licensee": "^10.0.0", @@ -247,7 +249,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.14.1", + "version": "4.18.0", "content": "./scripts/template-oss/root.js" }, "license": "Artistic-2.0", diff --git a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs index d88244d7a15460..7a5c2ddcc3882b 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs @@ -245,6 +245,17 @@ exports[`test/lib/commands/publish.js TAP no auth dry-run > must match snapshot exports[`test/lib/commands/publish.js TAP no auth dry-run > warns about auth being needed 1`] = ` Array [ + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + ), + ], Array [ "", "This command requires you to be logged in to https://registry.npmjs.org/ (dry-run)", @@ -416,6 +427,53 @@ exports[`test/lib/commands/publish.js TAP workspaces all workspaces - color > al exports[`test/lib/commands/publish.js TAP workspaces all workspaces - color > warns about skipped private workspace in color 1`] = ` Array [ + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + "repository" was changed from a string to an object + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + "repository" was changed from a string to an object + "repository.url" was normalized to "git+https://github.com/npm/workspace-b.git" + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + ), + ], Array [ "publish", "Skipping workspace \\u001b[32mworkspace-p\\u001b[39m, marked as \\u001b[1mprivate\\u001b[22m", @@ -431,6 +489,53 @@ exports[`test/lib/commands/publish.js TAP workspaces all workspaces - no color > exports[`test/lib/commands/publish.js TAP workspaces all workspaces - no color > warns about skipped private workspace 1`] = ` Array [ + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + "repository" was changed from a string to an object + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + "repository" was changed from a string to an object + "repository.url" was normalized to "git+https://github.com/npm/workspace-b.git" + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + ), + ], + Array [ + "publish", + "npm auto-corrected some errors in your package.json when publishing. Please run \\"npm pkg fix\\" to address these errors.", + ], + Array [ + "publish", + String( + errors corrected: + Removed invalid "scripts" + ), + ], Array [ "publish", "Skipping workspace workspace-p, marked as private", diff --git a/deps/npm/test/lib/commands/exec.js b/deps/npm/test/lib/commands/exec.js index 2fd11f40379f1f..07a3e6ebd8ed95 100644 --- a/deps/npm/test/lib/commands/exec.js +++ b/deps/npm/test/lib/commands/exec.js @@ -129,3 +129,38 @@ t.test('workspaces', async t => { const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success')) t.ok(exists.isFile(), 'bin ran, creating file inside workspace') }) + +t.test('npx --no-install @npmcli/npx-test', async t => { + const registry = new MockRegistry({ + tap: t, + registry: 'https://registry.npmjs.org/', + }) + + const manifest = registry.manifest({ name: '@npmcli/npx-test' }) + manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' } + + const { npm } = await loadMockNpm(t, { + config: { + audit: false, + yes: false, + }, + prefixDir: { + 'npm-exec-test': { + 'package.json': JSON.stringify(manifest), + 'index.js': `#!/usr/bin/env node + require('fs').writeFileSync('npm-exec-test-success', '')`, + }, + }, + }) + + try { + await npm.exec('exec', ['@npmcli/npx-test']) + t.fail('Expected error was not thrown') + } catch (error) { + t.match( + error.message, + 'npx canceled due to missing packages and no YES option: ', + 'Expected error message thrown' + ) + } +}) From 44b8a061dffbe293fd1d195fed29b30f0b1f27b0 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Sun, 23 Jul 2023 13:22:35 +0300 Subject: [PATCH 17/20] test_runner: fix async callback in describe not awaited PR-URL: https://github.com/nodejs/node/pull/48856 Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow --- lib/internal/test_runner/test.js | 21 ++++++---- .../test-runner/output/describe_it.js | 19 +++++++++ .../test-runner/output/describe_it.snapshot | 39 ++++++++++++++++--- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 9a333e1457fbc0..becb4c07d7cf83 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -13,6 +13,7 @@ const { ObjectSeal, PromisePrototypeThen, PromiseResolve, + SafePromisePrototypeFinally, ReflectApply, RegExpPrototypeExec, SafeMap, @@ -789,17 +790,23 @@ class Suite extends Test { const { ctx, args } = this.getRunArgs(); const runArgs = [this.fn, ctx]; ArrayPrototypePushApply(runArgs, args); - this.buildSuite = PromisePrototypeThen( - PromiseResolve(ReflectApply(this.runInAsyncScope, this, runArgs)), - undefined, - (err) => { - this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); - }); + this.buildSuite = SafePromisePrototypeFinally( + PromisePrototypeThen( + PromiseResolve(ReflectApply(this.runInAsyncScope, this, runArgs)), + undefined, + (err) => { + this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); + }), + () => { + this.buildPhaseFinished = true; + }, + ); } catch (err) { this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); + + this.buildPhaseFinished = true; } this.fn = () => {}; - this.buildPhaseFinished = true; } getRunArgs() { diff --git a/test/fixtures/test-runner/output/describe_it.js b/test/fixtures/test-runner/output/describe_it.js index c6d3f9c1b72fb3..0b89e1a11112b1 100644 --- a/test/fixtures/test-runner/output/describe_it.js +++ b/test/fixtures/test-runner/output/describe_it.js @@ -375,3 +375,22 @@ describe('rejected thenable', () => { }, }; }); + +describe("async describe function", async () => { + await null; + + await it("it inside describe 1", async () => { + await null + }); + await it("it inside describe 2", async () => { + await null; + }); + + describe("inner describe", async () => { + await null; + + it("it inside inner describe", async () => { + await null; + }); + }); +}); diff --git a/test/fixtures/test-runner/output/describe_it.snapshot b/test/fixtures/test-runner/output/describe_it.snapshot index e085ff9535ec70..c5b4610d522e43 100644 --- a/test/fixtures/test-runner/output/describe_it.snapshot +++ b/test/fixtures/test-runner/output/describe_it.snapshot @@ -635,8 +635,37 @@ not ok 59 - rejected thenable stack: |- * ... +# Subtest: async describe function + # Subtest: it inside describe 1 + ok 1 - it inside describe 1 + --- + duration_ms: * + ... + # Subtest: it inside describe 2 + ok 2 - it inside describe 2 + --- + duration_ms: * + ... + # Subtest: inner describe + # Subtest: it inside inner describe + ok 1 - it inside inner describe + --- + duration_ms: * + ... + 1..1 + ok 3 - inner describe + --- + duration_ms: * + type: 'suite' + ... + 1..3 +ok 60 - async describe function + --- + duration_ms: * + type: 'suite' + ... # Subtest: invalid subtest fail -not ok 60 - invalid subtest fail +not ok 61 - invalid subtest fail --- duration_ms: * failureType: 'parentAlreadyFinished' @@ -645,16 +674,16 @@ not ok 60 - invalid subtest fail stack: |- * ... -1..60 +1..61 # Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event. # Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event. # Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event. -# tests 67 -# suites 9 -# pass 29 +# tests 70 +# suites 11 +# pass 32 # fail 19 # cancelled 4 # skipped 10 From 88ab2e7f214de5cd6d4f5035190726d9773cf1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sun, 23 Jul 2023 21:35:38 +0200 Subject: [PATCH 18/20] permission: move PrintTree into unnamed namespace This function is not declared outside of fs_permission.cc and thus should not be visible outside the file during the linking stage. PR-URL: https://github.com/nodejs/node/pull/48874 Reviewed-By: Rafael Gonzaga Reviewed-By: Debadree Chatterjee --- src/permission/fs_permission.cc | 57 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/permission/fs_permission.cc b/src/permission/fs_permission.cc index a02f95ea55f3a2..91c63dff6582a8 100644 --- a/src/permission/fs_permission.cc +++ b/src/permission/fs_permission.cc @@ -67,52 +67,53 @@ bool is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree, return granted_tree->Lookup(param, true); } -} // namespace - -namespace node { - -namespace permission { - -void PrintTree(const FSPermission::RadixTree::Node* node, size_t spaces = 0) { +void PrintTree(const node::permission::FSPermission::RadixTree::Node* node, + size_t spaces = 0) { std::string whitespace(spaces, ' '); if (node == nullptr) { return; } if (node->wildcard_child != nullptr) { - per_process::Debug(DebugCategory::PERMISSION_MODEL, - "%s Wildcard: %s\n", - whitespace, - node->prefix); + node::per_process::Debug(node::DebugCategory::PERMISSION_MODEL, + "%s Wildcard: %s\n", + whitespace, + node->prefix); } else { - per_process::Debug(DebugCategory::PERMISSION_MODEL, - "%s Prefix: %s\n", - whitespace, - node->prefix); + node::per_process::Debug(node::DebugCategory::PERMISSION_MODEL, + "%s Prefix: %s\n", + whitespace, + node->prefix); if (node->children.size()) { size_t child = 0; for (const auto& pair : node->children) { ++child; - per_process::Debug(DebugCategory::PERMISSION_MODEL, - "%s Child(%s): %s\n", - whitespace, - child, - std::string(1, pair.first)); + node::per_process::Debug(node::DebugCategory::PERMISSION_MODEL, + "%s Child(%s): %s\n", + whitespace, + child, + std::string(1, pair.first)); PrintTree(pair.second, spaces + 2); } - per_process::Debug(DebugCategory::PERMISSION_MODEL, - "%s End of tree - child(%s)\n", - whitespace, - child); + node::per_process::Debug(node::DebugCategory::PERMISSION_MODEL, + "%s End of tree - child(%s)\n", + whitespace, + child); } else { - per_process::Debug(DebugCategory::PERMISSION_MODEL, - "%s End of tree: %s\n", - whitespace, - node->prefix); + node::per_process::Debug(node::DebugCategory::PERMISSION_MODEL, + "%s End of tree: %s\n", + whitespace, + node->prefix); } } } +} // namespace + +namespace node { + +namespace permission { + // allow = '*' // allow = '/tmp/,/home/example.js' void FSPermission::Apply(const std::string& allow, PermissionScope scope) { From 1ceb8c113d5ecc1b3fa3bec280d050bc10d09f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sun, 23 Jul 2023 21:51:38 +0200 Subject: [PATCH 19/20] node-api: avoid macro redefinition Even though the redefinition complies with the C standard because the second definition is "effectively the same" as the first definition, it's best to avoid any redefinition. Refs: https://github.com/nodejs/node/pull/28237 Refs: https://github.com/nodejs/node/pull/30006 PR-URL: https://github.com/nodejs/node/pull/48879 Reviewed-By: Michael Dawson Reviewed-By: Vladimir Morozov Reviewed-By: Luigi Pinca --- src/js_native_api_v8.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index f7646778311bfd..4f0fb8b3d152f7 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -268,14 +268,6 @@ inline napi_status napi_set_last_error(napi_env env, } \ } while (0) -#define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status) \ - do { \ - if (!(condition)) { \ - return napi_set_last_error( \ - (env), try_catch.HasCaught() ? napi_pending_exception : (status)); \ - } \ - } while (0) - #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status) \ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status)) From ad0603edc6516e98b4692914a6e8a3c36da4f223 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 24 Jul 2023 09:47:47 +0200 Subject: [PATCH 20/20] url: ensure getter access do not mutate observable symbols PR-URL: https://github.com/nodejs/node/pull/48897 Refs: https://github.com/nodejs/node/pull/48891 Refs: https://github.com/nodejs/node/issues/48886 Reviewed-By: Yagiz Nizipli Reviewed-By: Moshe Atlow --- test/parallel/test-whatwg-url-custom-searchparams.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/parallel/test-whatwg-url-custom-searchparams.js b/test/parallel/test-whatwg-url-custom-searchparams.js index 0b2087ac246313..75fa1779bdeb45 100644 --- a/test/parallel/test-whatwg-url-custom-searchparams.js +++ b/test/parallel/test-whatwg-url-custom-searchparams.js @@ -16,7 +16,9 @@ const normalizedValues = ['a', '1', 'true', 'undefined', 'null', '\uFFFD', '[object Object]']; const m = new URL('http://example.org'); +const ownSymbolsBeforeGetterAccess = Object.getOwnPropertySymbols(m); const sp = m.searchParams; +assert.deepStrictEqual(Object.getOwnPropertySymbols(m), ownSymbolsBeforeGetterAccess); assert(sp); assert.strictEqual(sp.toString(), '');