From 44886e55e1a2d773078f4933e165cb6af042833b Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Sat, 5 Nov 2022 05:03:53 -0700 Subject: [PATCH 001/117] diagnostics_channel: mark as stable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/45290 Reviewed-By: Gerhard Stöbich Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Santiago Gimeno Reviewed-By: Rafael Gonzaga Reviewed-By: Yagiz Nizipli Reviewed-By: Chengzhong Wu Reviewed-By: Gireesh Punathil --- doc/api/diagnostics_channel.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index cbcf06e716bd42..019fe9a6614b9d 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1,8 +1,16 @@ # Diagnostics Channel + + -> Stability: 1 - Experimental +> Stability: 2 - Stable From f79dd6533303c173a4f02da325effa7058c3d06e Mon Sep 17 00:00:00 2001 From: Darshan Sen Date: Thu, 10 Nov 2022 18:38:30 +0530 Subject: [PATCH 002/117] test: add a test to ensure the correctness of timezone upgrades Currently, there's no way to know if a timezone upgrade PR is correct without building and testing the change locally. This change provides a solution for that. Tested in https://github.com/RaisinTen/node/pull/4. Signed-off-by: Darshan Sen PR-URL: https://github.com/nodejs/node/pull/45299 Reviewed-By: Antoine du Hamel --- .github/workflows/timezone-update.yml | 3 +++ test/fixtures/tz-version.txt | 1 + test/parallel/test-tz-version.js | 28 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 test/fixtures/tz-version.txt create mode 100644 test/parallel/test-tz-version.js diff --git a/.github/workflows/timezone-update.yml b/.github/workflows/timezone-update.yml index cebf43223e1046..27cbfd2946a1d2 100644 --- a/.github/workflows/timezone-update.yml +++ b/.github/workflows/timezone-update.yml @@ -36,6 +36,9 @@ jobs: - run: ./tools/update-timezone.mjs + - name: Update the expected timezone version in test + run: echo "${{ env.new_version }}" > test/fixtures/tz-version.txt + - name: Open Pull Request uses: gr2m/create-or-update-pull-request-action@dc1726cbf4dd3ce766af4ec29cfb660e0125e8ee # Create a PR or update the Action's existing PR env: diff --git a/test/fixtures/tz-version.txt b/test/fixtures/tz-version.txt new file mode 100644 index 00000000000000..13ad873c89cf2e --- /dev/null +++ b/test/fixtures/tz-version.txt @@ -0,0 +1 @@ +2022e diff --git a/test/parallel/test-tz-version.js b/test/parallel/test-tz-version.js new file mode 100644 index 00000000000000..6e4b14e1ac1880 --- /dev/null +++ b/test/parallel/test-tz-version.js @@ -0,0 +1,28 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasIntl) { + common.skip('missing Intl'); +} + +// Refs: https://github.com/nodejs/node/blob/1af63a90ca3a59ca05b3a12ad7dbea04008db7d9/configure.py#L1694-L1711 +if (process.config.variables.icu_path !== 'deps/icu-small') { + // If Node.js is configured to use its built-in ICU, it uses a strict subset + // of ICU formed using `tools/icu/shrink-icu-src.py`, which is present in + // `deps/icu-small`. It is not the same as configuring the build with + // `./configure --with-intl=small-icu`. The latter only uses a subset of the + // locales, i.e., it uses the English locale, `root,en`, by default and other + // locales can also be specified using the `--with-icu-locales` option. + common.skip('not using the icu data file present in deps/icu-small/source/data/in/icudt##l.dat.bz2'); +} + +const fixtures = require('../common/fixtures'); + +// This test ensures the correctness of the automated timezone upgrade PRs. + +const { strictEqual } = require('assert'); +const { readFileSync } = require('fs'); + +const expectedVersion = readFileSync(fixtures.path('tz-version.txt'), 'utf8').trim(); +strictEqual(process.versions.tz, expectedVersion); From 6704e7814f012f73ee9bc5529486db6db613672a Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 10 Nov 2022 09:03:50 -0800 Subject: [PATCH 003/117] meta: be more proactive about removing from teams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/45352 Reviewed-By: Antoine du Hamel Reviewed-By: Chengzhong Wu Reviewed-By: Gireesh Punathil Reviewed-By: Geoffrey Booth Reviewed-By: Darshan Sen Reviewed-By: Michaël Zasso Reviewed-By: Matteo Collina Reviewed-By: Beth Griggs Reviewed-By: Colin Ihrig Reviewed-By: Michael Dawson --- doc/contributing/offboarding.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/contributing/offboarding.md b/doc/contributing/offboarding.md index 87f21103540bef..f30688a33d0d87 100644 --- a/doc/contributing/offboarding.md +++ b/doc/contributing/offboarding.md @@ -12,6 +12,9 @@ emeritus or leaves the project. a team listing. For example, if someone is removed from @nodejs/build, they should also be removed from the Build WG README.md file in the repository. + * When in doubt, especially if you are unable to get in contact with the + collaborator, remove them from all teams. It is easy enough to add them + back later, so we err on the side of privacy and security. * Open an issue in the [build](https://github.com/nodejs/build) repository titled `Remove Collaborator from Coverity` asking that the collaborator be removed from the Node.js coverity project if they had access. From 7c6281a7d24e6fc71e2ecd9c0a3826885a9488d6 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 10 Nov 2022 09:04:00 -0800 Subject: [PATCH 004/117] tools: dynamically determine parallelism on GitHub Actions macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: https://github.com/nodejs/node/pull/45340#discussion_r1014859250 PR-URL: https://github.com/nodejs/node/pull/45350 Reviewed-By: Luigi Pinca Reviewed-By: Michaël Zasso --- .github/workflows/test-macos.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 092de3885ab336..c09004cbe422c7 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -58,6 +58,6 @@ jobs: - name: tools/doc/node_modules workaround run: make tools/doc/node_modules - name: Build - run: make build-ci -j3 V=1 CONFIG_FLAGS="--error-on-warn" + run: make build-ci -j$(getconf _NPROCESSORS_ONLN) V=1 CONFIG_FLAGS="--error-on-warn" - name: Test - run: make run-ci -j3 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9" + run: make run-ci -j$(getconf _NPROCESSORS_ONLN) V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9" From 6c56c9722b25829a452b6e5217e31eaba16ae72a Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Thu, 10 Nov 2022 15:52:45 -0500 Subject: [PATCH 005/117] buffer: introduce File PR-URL: https://github.com/nodejs/node/pull/45139 Fixes: https://github.com/nodejs/node/issues/39015 Reviewed-By: Yagiz Nizipli Reviewed-By: Minwoo Jung Reviewed-By: Antoine du Hamel --- benchmark/blob/file.js | 34 ++++ doc/api/buffer.md | 51 ++++++ lib/buffer.js | 5 + lib/internal/file.js | 113 +++++++++++++ .../wpt/FileAPI/file/File-constructor.any.js | 155 ++++++++++++++++++ .../file/send-file-formdata-controls.any.js | 69 ++++++++ .../send-file-formdata-punctuation.any.js | 144 ++++++++++++++++ .../file/send-file-formdata-utf-8.any.js | 33 ++++ .../FileAPI/file/send-file-formdata.any.js | 8 + test/fixtures/wpt/README.md | 1 + test/fixtures/wpt/versions.json | 4 + test/parallel/test-bootstrap-modules.js | 1 + test/parallel/test-file.js | 155 ++++++++++++++++++ test/wpt/status/FileAPI/file.json | 17 ++ test/wpt/test-file.js | 13 ++ tools/doc/type-parser.mjs | 1 + 16 files changed, 804 insertions(+) create mode 100644 benchmark/blob/file.js create mode 100644 lib/internal/file.js create mode 100644 test/fixtures/wpt/FileAPI/file/File-constructor.any.js create mode 100644 test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js create mode 100644 test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js create mode 100644 test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js create mode 100644 test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js create mode 100644 test/parallel/test-file.js create mode 100644 test/wpt/status/FileAPI/file.json create mode 100644 test/wpt/test-file.js diff --git a/benchmark/blob/file.js b/benchmark/blob/file.js new file mode 100644 index 00000000000000..42f866b1ad8ce8 --- /dev/null +++ b/benchmark/blob/file.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common.js'); +const { File } = require('buffer'); + +const bench = common.createBenchmark(main, { + bytes: [128, 1024, 1024 ** 2], + n: [1e6], + operation: ['text', 'arrayBuffer'] +}); + +const options = { + lastModified: Date.now() - 1e6, +}; + +async function run(n, bytes, operation) { + const buff = Buffer.allocUnsafe(bytes); + const source = new File(buff, 'dummy.txt', options); + bench.start(); + for (let i = 0; i < n; i++) { + switch (operation) { + case 'text': + await source.text(); + break; + case 'arrayBuffer': + await source.arrayBuffer(); + break; + } + } + bench.end(n); +} + +function main(conf) { + run(conf.n, conf.bytes, conf.operation).catch(console.log); +} diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 8b46553ec4941a..b0522f688f6ad7 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -5013,6 +5013,56 @@ changes: See [`Buffer.from(string[, encoding])`][`Buffer.from(string)`]. +## Class: `File` + + + +> Stability: 1 - Experimental + +* Extends: {Blob} + +A [`File`][] provides information about files. + +### `new buffer.File(sources, fileName[, options])` + + + +* `sources` {string\[]|ArrayBuffer\[]|TypedArray\[]|DataView\[]|Blob\[]|File\[]} + An array of string, {ArrayBuffer}, {TypedArray}, {DataView}, {File}, or {Blob} + objects, or any mix of such objects, that will be stored within the `File`. +* `fileName` {string} The name of the file. +* `options` {Object} + * `endings` {string} One of either `'transparent'` or `'native'`. When set + to `'native'`, line endings in string source parts will be converted to + the platform native line-ending as specified by `require('node:os').EOL`. + * `type` {string} The File content-type. + * `lastModified` {number} The last modified date of the file. + **Default:** `Date.now()`. + +### `file.name` + + + +* Type: {string} + +The name of the `File`. + +### `file.lastModified` + + + +* Type: {number} + +The last modified date of the `File`. + ## `node:buffer` module APIs While, the `Buffer` object is available as a global, there are additional @@ -5359,6 +5409,7 @@ introducing security vulnerabilities into an application. [`ERR_INVALID_ARG_VALUE`]: errors.md#err_invalid_arg_value [`ERR_INVALID_BUFFER_SIZE`]: errors.md#err_invalid_buffer_size [`ERR_OUT_OF_RANGE`]: errors.md#err_out_of_range +[`File`]: https://developer.mozilla.org/en-US/docs/Web/API/File [`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify [`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer [`String.prototype.indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf diff --git a/lib/buffer.js b/lib/buffer.js index 7c0bbbc81c6398..898bc5032e5f8d 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -126,6 +126,10 @@ const { resolveObjectURL, } = require('internal/blob'); +const { + File, +} = require('internal/file'); + FastBuffer.prototype.constructor = Buffer; Buffer.prototype = FastBuffer.prototype; addBufferPrototypeMethods(Buffer.prototype); @@ -1320,6 +1324,7 @@ function atob(input) { module.exports = { Blob, + File, resolveObjectURL, Buffer, SlowBuffer, diff --git a/lib/internal/file.js b/lib/internal/file.js new file mode 100644 index 00000000000000..df9966532cdc24 --- /dev/null +++ b/lib/internal/file.js @@ -0,0 +1,113 @@ +'use strict'; + +const { + DateNow, + NumberIsNaN, + ObjectDefineProperties, + SymbolToStringTag, +} = primordials; + +const { + Blob, +} = require('internal/blob'); + +const { + customInspectSymbol: kInspect, + emitExperimentalWarning, + kEnumerableProperty, + kEmptyObject, + toUSVString, +} = require('internal/util'); + +const { + codes: { + ERR_INVALID_THIS, + ERR_MISSING_ARGS, + }, +} = require('internal/errors'); + +const { + inspect, +} = require('internal/util/inspect'); + +class File extends Blob { + /** @type {string} */ + #name; + + /** @type {number} */ + #lastModified; + + constructor(fileBits, fileName, options = kEmptyObject) { + emitExperimentalWarning('buffer.File'); + + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS('fileBits', 'fileName'); + } + + super(fileBits, options); + + let { lastModified } = options ?? kEmptyObject; + + if (lastModified !== undefined) { + // Using Number(...) will not throw an error for bigints. + lastModified = +lastModified; + + if (NumberIsNaN(lastModified)) { + lastModified = 0; + } + } else { + lastModified = DateNow(); + } + + this.#name = toUSVString(fileName); + this.#lastModified = lastModified; + } + + get name() { + if (!this || !(#name in this)) { + throw new ERR_INVALID_THIS('File'); + } + + return this.#name; + } + + get lastModified() { + if (!this || !(#name in this)) { + throw new ERR_INVALID_THIS('File'); + } + + return this.#lastModified; + } + + [kInspect](depth, options) { + if (depth < 0) { + return this; + } + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1, + }; + + return `File ${inspect({ + size: this.size, + type: this.type, + name: this.#name, + lastModified: this.#lastModified, + }, opts)}`; + } +} + +ObjectDefineProperties(File.prototype, { + name: kEnumerableProperty, + lastModified: kEnumerableProperty, + [SymbolToStringTag]: { + __proto__: null, + configurable: true, + value: 'File', + } +}); + +module.exports = { + File, +}; diff --git a/test/fixtures/wpt/FileAPI/file/File-constructor.any.js b/test/fixtures/wpt/FileAPI/file/File-constructor.any.js new file mode 100644 index 00000000000000..0b0185c40bf83c --- /dev/null +++ b/test/fixtures/wpt/FileAPI/file/File-constructor.any.js @@ -0,0 +1,155 @@ +// META: title=File constructor + +const to_string_obj = { toString: () => 'a string' }; +const to_string_throws = { toString: () => { throw new Error('expected'); } }; + +test(function() { + assert_true("File" in globalThis, "globalThis should have a File property."); +}, "File interface object exists"); + +test(t => { + assert_throws_js(TypeError, () => new File(), + 'Bits argument is required'); + assert_throws_js(TypeError, () => new File([]), + 'Name argument is required'); +}, 'Required arguments'); + +function test_first_argument(arg1, expectedSize, testName) { + test(function() { + var file = new File(arg1, "dummy"); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); + assert_equals(file.size, expectedSize); + assert_equals(file.type, ""); + // assert_false(file.isClosed); XXX: File.isClosed doesn't seem to be implemented + assert_not_equals(file.lastModified, ""); + }, testName); +} + +test_first_argument([], 0, "empty fileBits"); +test_first_argument(["bits"], 4, "DOMString fileBits"); +test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits"); +test_first_argument([new String('string object')], 13, "String object fileBits"); +test_first_argument([new Blob()], 0, "Empty Blob fileBits"); +test_first_argument([new Blob(["bits"])], 4, "Blob fileBits"); +test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits"); +test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits"); +test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits"); +test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits"); +test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]), + new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits"); +test_first_argument([12], 2, "Number in fileBits"); +test_first_argument([[1,2,3]], 5, "Array in fileBits"); +test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]" +if (globalThis.document !== undefined) { + test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]" +} +test_first_argument([to_string_obj], 8, "Object with toString in fileBits"); +test_first_argument({[Symbol.iterator]() { + let i = 0; + return {next: () => [ + {done:false, value:'ab'}, + {done:false, value:'cde'}, + {done:true} + ][i++]}; +}}, 5, 'Custom @@iterator'); + +[ + 'hello', + 0, + null +].forEach(arg => { + test(t => { + assert_throws_js(TypeError, () => new File(arg, 'world.html'), + 'Constructor should throw for invalid bits argument'); + }, `Invalid bits argument: ${JSON.stringify(arg)}`); +}); + +test(t => { + assert_throws_js(Error, () => new File([to_string_throws], 'name.txt'), + 'Constructor should propagate exceptions'); +}, 'Bits argument: object that throws'); + + +function test_second_argument(arg2, expectedFileName, testName) { + test(function() { + var file = new File(["bits"], arg2); + assert_true(file instanceof File); + assert_equals(file.name, expectedFileName); + }, testName); +} + +test_second_argument("dummy", "dummy", "Using fileName"); +test_second_argument("dummy/foo", "dummy/foo", + "No replacement when using special character in fileName"); +test_second_argument(null, "null", "Using null fileName"); +test_second_argument(1, "1", "Using number fileName"); +test_second_argument('', '', "Using empty string fileName"); +if (globalThis.document !== undefined) { + test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName"); +} + +// testing the third argument +[ + {type: 'text/plain', expected: 'text/plain'}, + {type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'}, + {type: 'TEXT/PLAIN', expected: 'text/plain'}, + {type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''}, + {type: 'ascii/nonprintable\u001F', expected: ''}, + {type: 'ascii/nonprintable\u007F', expected: ''}, + {type: 'nonascii\u00EE', expected: ''}, + {type: 'nonascii\u1234', expected: ''}, + {type: 'nonparsable', expected: 'nonparsable'} +].forEach(testCase => { + test(t => { + var file = new File(["bits"], "dummy", { type: testCase.type}); + assert_true(file instanceof File); + assert_equals(file.type, testCase.expected); + }, `Using type in File constructor: ${testCase.type}`); +}); +test(function() { + var file = new File(["bits"], "dummy", { lastModified: 42 }); + assert_true(file instanceof File); + assert_equals(file.lastModified, 42); +}, "Using lastModified"); +test(function() { + var file = new File(["bits"], "dummy", { name: "foo" }); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); +}, "Misusing name"); +test(function() { + var file = new File(["bits"], "dummy", { unknownKey: "value" }); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); +}, "Unknown properties are ignored"); + +[ + 123, + 123.4, + true, + 'abc' +].forEach(arg => { + test(t => { + assert_throws_js(TypeError, () => new File(['bits'], 'name.txt', arg), + 'Constructor should throw for invalid property bag type'); + }, `Invalid property bag: ${JSON.stringify(arg)}`); +}); + +[ + null, + undefined, + [1,2,3], + /regex/, + function() {} +].forEach(arg => { + test(t => { + assert_equals(new File(['bits'], 'name.txt', arg).size, 4, + 'Constructor should accept object-ish property bag type'); + }, `Unusual but valid property bag: ${arg}`); +}); + +test(t => { + assert_throws_js(Error, + () => new File(['bits'], 'name.txt', {type: to_string_throws}), + 'Constructor should propagate exceptions'); +}, 'Property bag propagates exceptions'); diff --git a/test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js b/test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js new file mode 100644 index 00000000000000..e95d3aada4421f --- /dev/null +++ b/test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js @@ -0,0 +1,69 @@ +// META: title=FormData: FormData: Upload files named using controls +// META: script=../support/send-file-formdata-helper.js + "use strict"; + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-NUL-[\0].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-BS-[\b].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-VT-[\v].txt", + }); + + // These have characters that undergo processing in name=, + // filename=, and/or value; formDataPostFileUploadTest postprocesses + // expectedEncodedBaseName for these internally. + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-LF-[\n].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-CR-[\r].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-HT-[\t].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-FF-[\f].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-DEL-[\x7F].txt", + }); + + // The rest should be passed through unmodified: + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-ESC-[\x1B].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-SPACE-[ ].txt", + }); diff --git a/test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js b/test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js new file mode 100644 index 00000000000000..987dba39aff3a1 --- /dev/null +++ b/test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js @@ -0,0 +1,144 @@ +// META: title=FormData: FormData: Upload files named using punctuation +// META: script=../support/send-file-formdata-helper.js + "use strict"; + + // These have characters that undergo processing in name=, + // filename=, and/or value; formDataPostFileUploadTest postprocesses + // expectedEncodedBaseName for these internally. + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: '"file-for-upload-in-form-double-quoted.txt"', + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt", + }); + + // The rest should be passed through unmodified: + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-ASTERISK-[*].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-COMMA-[,].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-COLON-[:].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form-TILDE-[~].txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "'file-for-upload-in-form-single-quoted.txt'", + }); diff --git a/test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js b/test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js new file mode 100644 index 00000000000000..b8bd74c717a1b3 --- /dev/null +++ b/test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js @@ -0,0 +1,33 @@ +// META: title=FormData: FormData: Upload files in UTF-8 fetch() +// META: script=../support/send-file-formdata-helper.js + "use strict"; + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form.txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "x-user-defined", + fileBaseName: "file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "windows-1252", + fileBaseName: "file-for-upload-in-form-☺😂.txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "JIS X 0201 and JIS X 0208", + fileBaseName: "file-for-upload-in-form-★星★.txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "Unicode", + fileBaseName: "file-for-upload-in-form-☺😂.txt", + }); + + formDataPostFileUploadTest({ + fileNameSource: "Unicode", + fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`, + }); diff --git a/test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js b/test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js new file mode 100644 index 00000000000000..e13a34828a0ebe --- /dev/null +++ b/test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js @@ -0,0 +1,8 @@ +// META: title=FormData: Upload ASCII-named file in UTF-8 form +// META: script=../support/send-file-formdata-helper.js + "use strict"; + + formDataPostFileUploadTest({ + fileNameSource: "ASCII", + fileBaseName: "file-for-upload-in-form.txt", + }); diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index b4b0e9c151f1a3..6e2ce815a454fd 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -17,6 +17,7 @@ Last update: - encoding: https://github.com/web-platform-tests/wpt/tree/c1b24fce6e/encoding - fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources - FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI +- FileAPI/file: https://github.com/web-platform-tests/wpt/tree/c01f637cca/FileAPI/file - hr-time: https://github.com/web-platform-tests/wpt/tree/34cafd797e/hr-time - html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 374fce9d0dca89..2b6ae3401d3645 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -27,6 +27,10 @@ "commit": "3b279420d40afea32506e823f9ac005448f4f3d8", "path": "FileAPI" }, + "FileAPI/file": { + "commit": "c01f637cca43f0e08ce8e4269121dcd89ccbdd82", + "path": "FileAPI/file" + }, "hr-time": { "commit": "34cafd797e58dad280d20040eee012d49ccfa91f", "path": "hr-time" diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index c0698fe1a96b5c..53da8b1af1492d 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -165,6 +165,7 @@ const expectedModules = new Set([ 'NativeModule internal/worker/js_transferable', 'Internal Binding blob', 'NativeModule internal/blob', + 'NativeModule internal/file', 'NativeModule async_hooks', 'NativeModule net', 'NativeModule path', diff --git a/test/parallel/test-file.js b/test/parallel/test-file.js new file mode 100644 index 00000000000000..64a83f77ef919d --- /dev/null +++ b/test/parallel/test-file.js @@ -0,0 +1,155 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Blob, File } = require('buffer'); +const { inspect } = require('util'); + +{ + // ensure File extends Blob + assert.deepStrictEqual(Object.getPrototypeOf(File.prototype), Blob.prototype); +} + +{ + assert.throws(() => new File(), TypeError); + assert.throws(() => new File([]), TypeError); +} + +{ + const properties = ['name', 'lastModified']; + + for (const prop of properties) { + const desc = Object.getOwnPropertyDescriptor(File.prototype, prop); + assert.notStrictEqual(desc, undefined); + // Ensure these properties are getters. + assert.strictEqual(desc.get?.name, `get ${prop}`); + assert.strictEqual(desc.set, undefined); + assert.strictEqual(desc.enumerable, true); + assert.strictEqual(desc.configurable, true); + } +} + +{ + const file = new File([], ''); + assert.strictEqual(file[Symbol.toStringTag], 'File'); + assert.strictEqual(File.prototype[Symbol.toStringTag], 'File'); +} + +{ + assert.throws(() => File.prototype.name, TypeError); + assert.throws(() => File.prototype.lastModified, TypeError); +} + +{ + const keys = Object.keys(File.prototype).sort(); + assert.deepStrictEqual(keys, ['lastModified', 'name']); +} + +{ + const file = new File([], 'dummy.txt.exe'); + assert.strictEqual(file.name, 'dummy.txt.exe'); + assert.strictEqual(file.size, 0); + assert.strictEqual(typeof file.lastModified, 'number'); + assert(file.lastModified <= Date.now()); +} + +{ + const emptyFile = new File([], 'empty.txt'); + const blob = new Blob(['hello world']); + + emptyFile.text.call(blob).then(common.mustCall((text) => { + assert.strictEqual(text, 'hello world'); + })); +} + +{ + const toPrimitive = { + [Symbol.toPrimitive]() { + return 'NaN'; + } + }; + + const invalidLastModified = [ + null, + 'string', + false, + toPrimitive, + ]; + + for (const lastModified of invalidLastModified) { + const file = new File([], '', { lastModified }); + assert.strictEqual(file.lastModified, 0); + } +} + +{ + const file = new File([], '', { lastModified: undefined }); + assert.notStrictEqual(file.lastModified, 0); +} + +{ + const toPrimitive = { + [Symbol.toPrimitive]() { + throw new TypeError('boom'); + } + }; + + const throwValues = [ + BigInt(3n), + toPrimitive, + ]; + + for (const lastModified of throwValues) { + assert.throws(() => new File([], '', { lastModified }), TypeError); + } +} + +{ + const valid = [ + { + [Symbol.toPrimitive]() { + return 10; + } + }, + new Number(10), + 10, + ]; + + for (const lastModified of valid) { + assert.strictEqual(new File([], '', { lastModified }).lastModified, 10); + } +} + +{ + const file = new File([], ''); + assert(inspect(file).startsWith('File { size: 0, type: \'\', name: \'\', lastModified:')); +} + +{ + function MyClass() {} + MyClass.prototype.lastModified = 10; + + const file = new File([], '', new MyClass()); + assert.strictEqual(file.lastModified, 10); +} + +{ + let counter = 0; + new File([], '', { + get lastModified() { + counter++; + return 10; + } + }); + assert.strictEqual(counter, 1); +} + +{ + const getter = Object.getOwnPropertyDescriptor(File.prototype, 'name').get; + assert.throws( + () => getter.call(undefined), // eslint-disable-line no-useless-call + { + code: 'ERR_INVALID_THIS', + } + ); +} diff --git a/test/wpt/status/FileAPI/file.json b/test/wpt/status/FileAPI/file.json new file mode 100644 index 00000000000000..6b50bcec1539e3 --- /dev/null +++ b/test/wpt/status/FileAPI/file.json @@ -0,0 +1,17 @@ +{ + "Worker-read-file-constructor.worker.js": { + "skip": true + }, + "send-file-formdata-punctuation.any.js": { + "skip": true + }, + "send-file-formdata-utf-8.any.js": { + "skip": true + }, + "send-file-formdata.any.js": { + "skip": true + }, + "send-file-formdata-controls.any.js": { + "skip": true + } +} diff --git a/test/wpt/test-file.js b/test/wpt/test-file.js new file mode 100644 index 00000000000000..71e8c179de95e2 --- /dev/null +++ b/test/wpt/test-file.js @@ -0,0 +1,13 @@ +'use strict'; + +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('FileAPI/file'); + +runner.setInitScript(` + const { File } = require('buffer'); + globalThis.File = File; +`); + +runner.runJsTests(); diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 0053b62b31f33a..64d499d182f484 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -43,6 +43,7 @@ const customTypesMap = { `${jsDocPrefix}Reference/Global_Objects/WebAssembly/Instance`, 'Blob': 'buffer.html#class-blob', + 'File': 'buffer.html#class-file', 'BroadcastChannel': 'worker_threads.html#class-broadcastchannel-' + From 17e6031bf0d4014111f388592f3013695580f0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 11 Nov 2022 08:20:17 +0100 Subject: [PATCH 006/117] deps: V8: cherry-pick 031b98b25cba MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [runtime] Clear array join stack when throwing uncatchable ... exception. Array#join depends array_join_stack to avoid infinite loop and ensures symmetric pushes/pops through catch blocks to correctly maintain the elements in the join stack. However, the stack does not pop the elements and leaves in an invalid state when throwing the uncatchable termination exception. And the invalid join stack state will affect subsequent Array#join calls. Because all the terminate exception will be handled by Isolate::UnwindAndFindHandler, we could clear the array join stack when unwinding the terminate exception. Bug: v8:13259 Change-Id: I23823e823c5fe0b089528c5cf654864cea78ebeb Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3878451 Reviewed-by: Jakob Linke Commit-Queue: 王澳 Cr-Commit-Position: refs/heads/main@{#83465} Refs: https://github.com/v8/v8/commit/031b98b25cbaaa4c62d8544f5f667d33ea4076c4 Closes: https://github.com/nodejs/node/issues/44417 PR-URL: https://github.com/nodejs/node/pull/45375 Fixes: https://github.com/nodejs/node/issues/44417 Reviewed-By: Jiawen Geng Reviewed-By: Rich Trott Reviewed-By: Kohei Ueno --- deps/v8/src/execution/isolate.cc | 9 +++ ...ode-side-effecting-array-join-expected.txt | 48 +++++++++++++ ...ate-repl-mode-side-effecting-array-join.js | 32 +++++++++ .../execution/thread-termination-unittest.cc | 70 +++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join-expected.txt create mode 100644 deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join.js diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index bbd6855bb3be48..2e4f402c66e31c 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -1949,6 +1949,15 @@ Object Isolate::UnwindAndFindHandler() { // Special handling of termination exceptions, uncatchable by JavaScript and // Wasm code, we unwind the handlers until the top ENTRY handler is found. bool catchable_by_js = is_catchable_by_javascript(exception); + if (!catchable_by_js && !context().is_null()) { + // Because the array join stack will not pop the elements when throwing the + // uncatchable terminate exception, we need to clear the array join stack to + // avoid leaving the stack in an invalid state. + // See also CycleProtectedArrayJoin. + raw_native_context().set_array_join_stack( + ReadOnlyRoots(this).undefined_value()); + } + int visited_frames = 0; #if V8_ENABLE_WEBASSEMBLY diff --git a/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join-expected.txt b/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join-expected.txt new file mode 100644 index 00000000000000..19ad7f863eeba9 --- /dev/null +++ b/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join-expected.txt @@ -0,0 +1,48 @@ +Tests that Runtime.evaluate with REPL mode correctly handles Array.prototype.join. +{ + id : + result : { + result : { + className : Array + description : Array(1) + objectId : + subtype : array + type : object + } + } +} +{ + id : + result : { + exceptionDetails : { + columnNumber : -1 + exception : { + className : EvalError + description : EvalError: Possible side-effect in debug-evaluate + objectId : + subtype : error + type : object + } + exceptionId : + lineNumber : -1 + scriptId : + text : Uncaught + } + result : { + className : EvalError + description : EvalError: Possible side-effect in debug-evaluate + objectId : + subtype : error + type : object + } + } +} +{ + id : + result : { + result : { + type : string + value : /a/ + } + } +} diff --git a/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join.js b/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join.js new file mode 100644 index 00000000000000..05259ff24f4d95 --- /dev/null +++ b/deps/v8/test/inspector/runtime/evaluate-repl-mode-side-effecting-array-join.js @@ -0,0 +1,32 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let {Protocol} = InspectorTest.start( + 'Tests that Runtime.evaluate with REPL mode correctly handles \ +Array.prototype.join.'); + +Protocol.Runtime.enable(); +(async function () { + await evaluateReplWithSideEffects('a=[/a/]') + await evaluateRepl('a.toString()'); + await evaluateReplWithSideEffects('a.toString()'); + + InspectorTest.completeTest(); +})(); + +async function evaluateRepl(expression) { + InspectorTest.logMessage(await Protocol.Runtime.evaluate({ + expression: expression, + replMode: true, + throwOnSideEffect: true + })); +} + +async function evaluateReplWithSideEffects(expression) { + InspectorTest.logMessage(await Protocol.Runtime.evaluate({ + expression: expression, + replMode: true, + throwOnSideEffect: false + })); +} diff --git a/deps/v8/test/unittests/execution/thread-termination-unittest.cc b/deps/v8/test/unittests/execution/thread-termination-unittest.cc index ef23af37fdce7b..f9634b4a53d7e3 100644 --- a/deps/v8/test/unittests/execution/thread-termination-unittest.cc +++ b/deps/v8/test/unittests/execution/thread-termination-unittest.cc @@ -33,6 +33,7 @@ #include "src/init/v8.h" #include "src/objects/objects-inl.h" #include "test/unittests/test-utils.h" +#include "testing/gmock-support.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { @@ -889,6 +890,75 @@ TEST_F(ThreadTerminationTest, TerminateConsole) { CHECK(isolate()->IsExecutionTerminating()); } +TEST_F(ThreadTerminationTest, TerminationClearArrayJoinStack) { + internal::v8_flags.allow_natives_syntax = true; + HandleScope scope(isolate()); + Local global_template = + CreateGlobalTemplate(isolate(), TerminateCurrentThread, DoLoopNoCall); + { + Local context = Context::New(isolate(), nullptr, global_template); + Context::Scope context_scope(context); + { + TryCatch try_catch(isolate()); + TryRunJS( + "var error = false;" + "var a = [{toString(){if(error)loop()}}];" + "function Join(){ return a.join();}; " + "%PrepareFunctionForOptimization(Join);" + "Join();" + "%OptimizeFunctionOnNextCall(Join);" + "error = true;" + "Join();"); + CHECK(try_catch.HasTerminated()); + CHECK(isolate()->IsExecutionTerminating()); + } + EXPECT_THAT(RunJS("a[0] = 1; Join();"), testing::IsString("1")); + } + { + Local context = Context::New(isolate(), nullptr, global_template); + Context::Scope context_scope(context); + { + TryCatch try_catch(isolate()); + TryRunJS( + "var a = [{toString(){loop()}}];" + "function Join(){ return a.join();}; " + "Join();"); + CHECK(try_catch.HasTerminated()); + CHECK(isolate()->IsExecutionTerminating()); + } + EXPECT_THAT(RunJS("a[0] = 1; Join();"), testing::IsString("1")); + } + { + ConsoleImpl console; + debug::SetConsoleDelegate(isolate(), &console); + HandleScope scope(isolate()); + Local context = Context::New(isolate(), nullptr, global_template); + Context::Scope context_scope(context); + { + // setup console global. + HandleScope scope(isolate()); + Local name = String::NewFromUtf8Literal( + isolate(), "console", NewStringType::kInternalized); + Local console = context->GetExtrasBindingObject() + ->Get(context, name) + .ToLocalChecked(); + context->Global()->Set(context, name, console).FromJust(); + } + CHECK(!isolate()->IsExecutionTerminating()); + { + TryCatch try_catch(isolate()); + CHECK(!isolate()->IsExecutionTerminating()); + CHECK(TryRunJS("var a = [{toString(){terminate();console.log();fail()}}];" + "function Join() {return a.join();}" + "Join();") + .IsEmpty()); + CHECK(try_catch.HasCaught()); + CHECK(isolate()->IsExecutionTerminating()); + } + EXPECT_THAT(RunJS("a[0] = 1; Join();"), testing::IsString("1")); + } +} + class TerminatorSleeperThread : public base::Thread { public: explicit TerminatorSleeperThread(Isolate* isolate, int sleep_ms) From f86f90f839d4f9e4beea976e3dea37cc2e139e55 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 9 Nov 2022 09:18:42 -0500 Subject: [PATCH 007/117] util: improve text decoder performance PR-URL: https://github.com/nodejs/node/pull/45388 Reviewed-By: Anna Henningsen Reviewed-By: Santiago Gimeno Reviewed-By: Rich Trott --- lib/internal/encoding.js | 12 +----------- src/node_errors.h | 1 + src/node_i18n.cc | 24 ++++++++++++++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/internal/encoding.js b/lib/internal/encoding.js index 40c87a0a8719ff..66dc0ce9c8609a 100644 --- a/lib/internal/encoding.js +++ b/lib/internal/encoding.js @@ -18,7 +18,6 @@ const { } = primordials; const { - ERR_ENCODING_INVALID_ENCODED_DATA, ERR_ENCODING_NOT_SUPPORTED, ERR_INVALID_ARG_TYPE, ERR_INVALID_THIS, @@ -411,11 +410,6 @@ function makeTextDecoderICU() { decode(input = empty, options = kEmptyObject) { validateDecoder(this); - if (!isAnyArrayBuffer(input) && !isArrayBufferView(input)) { - throw new ERR_INVALID_ARG_TYPE('input', - ['ArrayBuffer', 'ArrayBufferView'], - input); - } validateObject(options, 'options', { nullable: true, allowArray: true, @@ -426,11 +420,7 @@ function makeTextDecoderICU() { if (options !== null) flags |= options.stream ? 0 : CONVERTER_FLAGS_FLUSH; - const ret = _decode(this[kHandle], input, flags); - if (typeof ret === 'number') { - throw new ERR_ENCODING_INVALID_ENCODED_DATA(this.encoding, ret); - } - return ret; + return _decode(this[kHandle], input, flags, this.encoding); } } diff --git a/src/node_errors.h b/src/node_errors.h index 68a95835812e50..706464acc87b5c 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -60,6 +60,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_CRYPTO_JOB_INIT_FAILED, Error) \ V(ERR_DLOPEN_DISABLED, Error) \ V(ERR_DLOPEN_FAILED, Error) \ + V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \ V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \ V(ERR_INVALID_ADDRESS, Error) \ V(ERR_INVALID_ARG_VALUE, TypeError) \ diff --git a/src/node_i18n.cc b/src/node_i18n.cc index ed7b72c31f975e..441c2b32763a96 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -436,13 +436,25 @@ void ConverterObject::Create(const FunctionCallbackInfo& args) { void ConverterObject::Decode(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - CHECK_GE(args.Length(), 3); // Converter, Buffer, Flags + CHECK_GE(args.Length(), 4); // Converter, Buffer, Flags, Encoding ConverterObject* converter; ASSIGN_OR_RETURN_UNWRAP(&converter, args[0].As()); + + if (!(args[1]->IsArrayBuffer() || args[1]->IsSharedArrayBuffer() || + args[1]->IsArrayBufferView())) { + return node::THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"input\" argument must be an instance of SharedArrayBuffer, " + "ArrayBuffer or ArrayBufferView."); + } + ArrayBufferViewContents input(args[1]); int flags = args[2]->Uint32Value(env->context()).ToChecked(); + CHECK(args[3]->IsString()); + Local from_encoding = args[3].As(); + UErrorCode status = U_ZERO_ERROR; MaybeStackBuffer result; @@ -524,14 +536,14 @@ void ConverterObject::Decode(const FunctionCallbackInfo& args) { Local ret; if (encoded.ToLocal(&ret)) { args.GetReturnValue().Set(ret); - } else { - args.GetReturnValue().Set(error); + return; } - - return; } - args.GetReturnValue().Set(status); + node::THROW_ERR_ENCODING_INVALID_ENCODED_DATA( + env->isolate(), + "The encoded data was not valid for encoding %s", + *node::Utf8Value(env->isolate(), from_encoding)); } ConverterObject::ConverterObject( From b72b2bab72e9953956d442ad28c14348f5014428 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 7 Nov 2022 19:38:35 -0800 Subject: [PATCH 008/117] http: add JSDoc property descriptions PR-URL: https://github.com/nodejs/node/pull/45370 Reviewed-By: Akhil Marsonya Reviewed-By: Yagiz Nizipli Reviewed-By: Jacob Smith --- lib/http.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/http.js b/lib/http.js index d7acafe4f317b8..6835789836d66b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -62,28 +62,28 @@ function createServer(opts, requestListener) { /** * @typedef {object} HTTPRequestOptions - * @property {httpAgent.Agent | boolean} [agent] - * @property {string} [auth] - * @property {Function} [createConnection] - * @property {number} [defaultPort] - * @property {number} [family] - * @property {object} [headers] - * @property {number} [hints] - * @property {string} [host] - * @property {string} [hostname] - * @property {boolean} [insecureHTTPParser] - * @property {string} [localAddress] - * @property {number} [localPort] - * @property {Function} [lookup] - * @property {number} [maxHeaderSize] - * @property {string} [method] - * @property {string} [path] - * @property {number} [port] - * @property {string} [protocol] - * @property {boolean} [setHost] - * @property {string} [socketPath] - * @property {number} [timeout] - * @property {AbortSignal} [signal] + * @property {httpAgent.Agent | boolean} [agent] Controls Agent behavior. + * @property {string} [auth] Basic authentication ('user:password') to compute an Authorization header. + * @property {Function} [createConnection] Produces a socket/stream to use when the agent option is not used. + * @property {number} [defaultPort] Default port for the protocol. + * @property {number} [family] IP address family to use when resolving host or hostname. + * @property {object} [headers] An object containing request headers. + * @property {number} [hints] Optional dns.lookup() hints. + * @property {string} [host] A domain name or IP address of the server to issue the request to. + * @property {string} [hostname] Alias for host. + * @property {boolean} [insecureHTTPParser] Use an insecure HTTP parser that accepts invalid HTTP headers when true. + * @property {string} [localAddress] Local interface to bind for network connections. + * @property {number} [localPort] Local port to connect from. + * @property {Function} [lookup] Custom lookup function. Default: dns.lookup(). + * @property {number} [maxHeaderSize] Overrides the --max-http-header-size value for responses received from the server. + * @property {string} [method] A string specifying the HTTP request method. + * @property {string} [path] Request path. + * @property {number} [port] Port of remote server. + * @property {string} [protocol] Protocol to use. + * @property {boolean} [setHost] Specifies whether or not to automatically add the Host header. + * @property {AbortSignal} [signal] An AbortSignal that may be used to abort an ongoing request. + * @property {string} [socketPath] Unix domain socket. + * @property {number} [timeout] A number specifying the socket timeout in milliseconds. */ /** From 4e5ad9df501d3078a7bbf12b273f2e354644fdf6 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 7 Nov 2022 19:39:29 -0800 Subject: [PATCH 009/117] esm: add JSDoc property descriptions for fetch PR-URL: https://github.com/nodejs/node/pull/45370 Reviewed-By: Akhil Marsonya Reviewed-By: Yagiz Nizipli Reviewed-By: Jacob Smith --- lib/internal/modules/esm/fetch_module.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/modules/esm/fetch_module.js b/lib/internal/modules/esm/fetch_module.js index 7638f94b3fe525..073ec13562097c 100644 --- a/lib/internal/modules/esm/fetch_module.js +++ b/lib/internal/modules/esm/fetch_module.js @@ -21,9 +21,9 @@ const { once } = require('events'); const { compose } = require('stream'); /** * @typedef CacheEntry - * @property {Promise | string} resolvedHREF - * @property {Record} headers - * @property {Promise | Buffer} body + * @property {Promise | string} resolvedHREF Parsed HREF of the request. + * @property {Record} headers HTTP headers of the response. + * @property {Promise | Buffer} body Response body. */ /** From 8906a4e58e06aa87cf955a6321f66de19c84509f Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 7 Nov 2022 19:39:44 -0800 Subject: [PATCH 010/117] esm: add JSDoc property descriptions for loader PR-URL: https://github.com/nodejs/node/pull/45370 Reviewed-By: Akhil Marsonya Reviewed-By: Yagiz Nizipli Reviewed-By: Jacob Smith --- lib/internal/modules/esm/loader.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 0f23d48084abe8..55c17d97ec5695 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -58,9 +58,9 @@ const { getOptionValue } = require('internal/options'); /** * @typedef {object} ExportedHooks - * @property {Function} globalPreload - * @property {Function} resolve - * @property {Function} load + * @property {Function} globalPreload Global preload hook. + * @property {Function} resolve Resolve hook. + * @property {Function} load Load hook. */ /** @@ -69,14 +69,14 @@ const { getOptionValue } = require('internal/options'); /** * @typedef {object} KeyedExports - * @property {ModuleExports} exports - * @property {URL['href']} url + * @property {ModuleExports} exports The contents of the module. + * @property {URL['href']} url The URL of the module. */ /** * @typedef {object} KeyedHook - * @property {Function} fn - * @property {URL['href']} url + * @property {Function} fn The hook function. + * @property {URL['href']} url The URL of the module. */ /** From 36bf87fabff1018fc38e520843a51a1ecbcadf80 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 7 Nov 2022 19:41:12 -0800 Subject: [PATCH 011/117] tools: enable jsdoc/require-property-description rule PR-URL: https://github.com/nodejs/node/pull/45370 Reviewed-By: Akhil Marsonya Reviewed-By: Yagiz Nizipli Reviewed-By: Jacob Smith --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2a10875dd34ebd..64d35736b9ce77 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -311,7 +311,6 @@ module.exports = { 'jsdoc/require-param': 'off', 'jsdoc/check-tag-names': 'off', 'jsdoc/require-returns': 'off', - 'jsdoc/require-property-description': 'off', // Custom rules from eslint-plugin-node-core 'node-core/no-unescaped-regexp-dot': 'error', From b7f8a44c648e477c7e402294f825b685363a8674 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 12 Nov 2022 06:48:52 -0800 Subject: [PATCH 012/117] tools: simplify regex in ESLint config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/45399 Reviewed-By: Michaël Zasso Reviewed-By: Antoine du Hamel Reviewed-By: Yagiz Nizipli Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- .eslintrc.js | 3 +-- test/parallel/test-fs-readv-promises.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 64d35736b9ce77..0f90cb076ae45c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -126,8 +126,7 @@ module.exports = { line: { // Ignore all lines that have less characters than 20 and all lines that // start with something that looks like a variable name or code. - // eslint-disable-next-line max-len - ignorePattern: '.{0,20}$|[a-z]+ ?[0-9A-Z_.(/=:[#-]|std|http|ssh|ftp|(let|var|const) [a-z_A-Z0-9]+ =|[b-z] |[a-z]*[0-9].* ', + ignorePattern: '.{0,20}$|[a-z]+ ?[0-9A-Z_.(/=:[#-]|std|http|ssh|ftp', ignoreInlineComments: true, ignoreConsecutiveComments: true, }, diff --git a/test/parallel/test-fs-readv-promises.js b/test/parallel/test-fs-readv-promises.js index ae3c92926ea221..3d698f2536a7f2 100644 --- a/test/parallel/test-fs-readv-promises.js +++ b/test/parallel/test-fs-readv-promises.js @@ -29,7 +29,6 @@ const allocateEmptyBuffers = (combinedLength) => { const filename = getFileName(); await fs.writeFile(filename, exptectedBuff); const handle = await fs.open(filename, 'r'); - // const buffer = Buffer.from(expected); const bufferArr = allocateEmptyBuffers(exptectedBuff.length); const expectedLength = exptectedBuff.length; @@ -49,7 +48,6 @@ const allocateEmptyBuffers = (combinedLength) => { const filename = getFileName(); await fs.writeFile(filename, exptectedBuff); const handle = await fs.open(filename, 'r'); - // const buffer = Buffer.from(expected); const bufferArr = allocateEmptyBuffers(exptectedBuff.length); const expectedLength = exptectedBuff.length; From 93bc2ba509f4dd4a252f10214447ae41e2734cbe Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 12 Nov 2022 07:13:05 -0800 Subject: [PATCH 013/117] tools: simplify .eslintrc.js Remove explicit setting of configuration options in rules when those options are the defaults. PR-URL: https://github.com/nodejs/node/pull/45397 Reviewed-By: Joyee Cheung Reviewed-By: Luigi Pinca --- .eslintrc.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0f90cb076ae45c..b676485c5909ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -117,8 +117,8 @@ module.exports = { // https://eslint.org/docs/rules/ 'accessor-pairs': 'error', 'array-callback-return': 'error', - 'arrow-parens': ['error', 'always'], - 'arrow-spacing': ['error', { before: true, after: true }], + 'arrow-parens': 'error', + 'arrow-spacing': 'error', 'block-scoped-var': 'error', 'block-spacing': 'error', 'brace-style': ['error', '1tbs', { allowSingleLine: true }], @@ -161,9 +161,9 @@ module.exports = { ObjectExpression: 'first', SwitchCase: 1, }], - 'key-spacing': ['error', { mode: 'strict' }], + 'key-spacing': 'error', 'keyword-spacing': 'error', - 'linebreak-style': ['error', 'unix'], + 'linebreak-style': 'error', 'max-len': ['error', { code: 120, ignorePattern: '^// Flags:', @@ -177,7 +177,7 @@ module.exports = { 'no-constant-condition': ['error', { checkLoops: false }], 'no-constructor-return': 'error', 'no-duplicate-imports': 'error', - 'no-else-return': ['error', { allowElseIf: true }], + 'no-else-return': 'error', 'no-extra-parens': ['error', 'functions'], 'no-lonely-if': 'error', 'no-mixed-requires': 'error', @@ -284,7 +284,7 @@ module.exports = { named: 'never', asyncArrow: 'always', }], - 'space-in-parens': ['error', 'never'], + 'space-in-parens': 'error', 'space-infix-ops': 'error', 'space-unary-ops': 'error', 'spaced-comment': ['error', 'always', { From bb36acff42c0465f7582465937facfff76e90a1e Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sat, 12 Nov 2022 18:22:02 +0200 Subject: [PATCH 014/117] tools: do not run CQ on non-fast-tracked PRs open for less than 2 days MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/45407 Fixes: https://github.com/nodejs/node/issues/45405 Reviewed-By: Yagiz Nizipli Reviewed-By: Filip Skokan Reviewed-By: Antoine du Hamel Reviewed-By: Michaël Zasso --- .github/workflows/commit-queue.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commit-queue.yml b/.github/workflows/commit-queue.yml index 18a26666dee871..59ab3d2ce64263 100644 --- a/.github/workflows/commit-queue.yml +++ b/.github/workflows/commit-queue.yml @@ -32,14 +32,24 @@ jobs: steps: - name: Get Pull Requests id: get_mergeable_prs - run: > - numbers=$(gh pr list \ + run: | + prs=$(gh pr list \ + --repo ${{ github.repository }} \ + --base ${{ github.ref_name }} \ + --label 'commit-queue' \ + --json 'number' \ + --search "created:<=$(date --date="2 days ago" +"%Y-%m-%dT%H:%M:%S%z")" \ + -t '{{ range . }}{{ .number }} {{ end }}' \ + --limit 100) + fast_track_prs=$(gh pr list \ --repo ${{ github.repository }} \ --base ${{ github.ref_name }} \ --label 'commit-queue' \ + --label 'fast-track' \ --json 'number' \ -t '{{ range . }}{{ .number }} {{ end }}' \ --limit 100) + numbers=$(echo $prs' '$fast_track_prs | jq -r -s 'unique | join(" ")') echo "numbers=$numbers" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a6fe707b62e32ffa5cfff28b8ce8352bccfdeb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 12 Nov 2022 18:22:05 +0100 Subject: [PATCH 015/117] doc: fix typo in maintaining-dependencies.md PR-URL: https://github.com/nodejs/node/pull/45428 Reviewed-By: Colin Ihrig Reviewed-By: Gireesh Punathil Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott --- doc/contributing/maintaining-dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/maintaining-dependencies.md b/doc/contributing/maintaining-dependencies.md index 2c057320b4819b..b43f1d36a40fc6 100644 --- a/doc/contributing/maintaining-dependencies.md +++ b/doc/contributing/maintaining-dependencies.md @@ -67,7 +67,7 @@ shared library is available can added by: If there are additional libraries that are required it is possible to list more than one with the `pkgname` option. * in `node.gypi` guard the build for the dependency - with `node_shared_depname` so that is is only built if + with `node_shared_depname` so that it is only built if the dependency is being bundled into Node.js itself. For example: ```text From 7c79ba7b27df407062dc35fc2d7f6726d53d9ca6 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sat, 12 Nov 2022 16:31:44 -0500 Subject: [PATCH 016/117] util: add fast path for utf8 encoding Co-authored-by: Anna Henningsen PR-URL: https://github.com/nodejs/node/pull/45412 Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott Reviewed-By: Santiago Gimeno --- lib/internal/encoding.js | 35 ++++++++++++--- src/node_buffer.cc | 45 +++++++++++++++++++ ...test-whatwg-encoding-custom-textdecoder.js | 2 +- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/lib/internal/encoding.js b/lib/internal/encoding.js index 66dc0ce9c8609a..0e3c44d2e84fbb 100644 --- a/lib/internal/encoding.js +++ b/lib/internal/encoding.js @@ -4,6 +4,7 @@ // https://encoding.spec.whatwg.org const { + Boolean, ObjectCreate, ObjectDefineProperties, ObjectGetOwnPropertyDescriptors, @@ -28,6 +29,8 @@ const kFlags = Symbol('flags'); const kEncoding = Symbol('encoding'); const kDecoder = Symbol('decoder'); const kEncoder = Symbol('encoder'); +const kUTF8FastPath = Symbol('kUTF8FastPath'); +const kIgnoreBOM = Symbol('kIgnoreBOM'); const { getConstructorOf, @@ -49,7 +52,8 @@ const { const { encodeInto, - encodeUtf8String + encodeUtf8String, + decodeUTF8, } = internalBinding('buffer'); let Buffer; @@ -397,19 +401,40 @@ function makeTextDecoderICU() { flags |= options.ignoreBOM ? CONVERTER_FLAGS_IGNORE_BOM : 0; } - const handle = getConverter(enc, flags); - if (handle === undefined) - throw new ERR_ENCODING_NOT_SUPPORTED(encoding); + // Only support fast path for UTF-8 without FATAL flag + const fastPathAvailable = enc === 'utf-8' && !(options?.fatal); this[kDecoder] = true; - this[kHandle] = handle; this[kFlags] = flags; this[kEncoding] = enc; + this[kIgnoreBOM] = Boolean(options?.ignoreBOM); + this[kUTF8FastPath] = fastPathAvailable; + this[kHandle] = undefined; + + if (!fastPathAvailable) { + this.#prepareConverter(); + } } + #prepareConverter() { + if (this[kHandle] !== undefined) return; + const handle = getConverter(this[kEncoding], this[kFlags]); + if (handle === undefined) + throw new ERR_ENCODING_NOT_SUPPORTED(this[kEncoding]); + this[kHandle] = handle; + } decode(input = empty, options = kEmptyObject) { validateDecoder(this); + + this[kUTF8FastPath] &&= !(options?.stream); + + if (this[kUTF8FastPath]) { + return decodeUTF8(input, this[kIgnoreBOM]); + } + + this.#prepareConverter(); + validateObject(options, 'options', { nullable: true, allowArray: true, diff --git a/src/node_buffer.cc b/src/node_buffer.cc index eb8e541c68635d..acec3c420ce1d2 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -24,6 +24,7 @@ #include "node_blob.h" #include "node_errors.h" #include "node_external_reference.h" +#include "node_i18n.h" #include "node_internals.h" #include "env-inl.h" @@ -565,6 +566,48 @@ void StringSlice(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ret); } +// Convert the input into an encoded string +void DecodeUTF8(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); // list, flags + + if (!(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer() || + args[0]->IsArrayBufferView())) { + return node::THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"list\" argument must be an instance of SharedArrayBuffer, " + "ArrayBuffer or ArrayBufferView."); + } + + ArrayBufferViewContents buffer(args[0]); + + CHECK(args[1]->IsBoolean()); + bool ignore_bom = args[1]->IsTrue(); + + const char* data = buffer.data(); + size_t length = buffer.length(); + + if (!ignore_bom && length >= 3) { + if (memcmp(data, "\xEF\xBB\xBF", 3) == 0) { + data += 3; + length -= 3; + } + } + + if (length == 0) return args.GetReturnValue().SetEmptyString(); + + Local error; + MaybeLocal maybe_ret = + StringBytes::Encode(env->isolate(), data, length, UTF8, &error); + Local ret; + + if (!maybe_ret.ToLocal(&ret)) { + CHECK(!error.IsEmpty()); + env->isolate()->ThrowException(error); + return; + } + + args.GetReturnValue().Set(ret); +} // bytesCopied = copy(buffer, target[, targetStart][, sourceStart][, sourceEnd]) void Copy(const FunctionCallbackInfo &args) { @@ -1282,6 +1325,7 @@ void Initialize(Local target, SetMethod(context, target, "setBufferPrototype", SetBufferPrototype); SetMethodNoSideEffect(context, target, "createFromString", CreateFromString); + SetMethodNoSideEffect(context, target, "decodeUTF8", DecodeUTF8); SetMethodNoSideEffect(context, target, "byteLengthUtf8", ByteLengthUtf8); SetMethod(context, target, "copy", Copy); @@ -1339,6 +1383,7 @@ void Initialize(Local target, void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(SetBufferPrototype); registry->Register(CreateFromString); + registry->Register(DecodeUTF8); registry->Register(ByteLengthUtf8); registry->Register(Copy); diff --git a/test/parallel/test-whatwg-encoding-custom-textdecoder.js b/test/parallel/test-whatwg-encoding-custom-textdecoder.js index 75a2a4735cd6ef..a48d0993fc7a92 100644 --- a/test/parallel/test-whatwg-encoding-custom-textdecoder.js +++ b/test/parallel/test-whatwg-encoding-custom-textdecoder.js @@ -113,7 +113,7 @@ if (common.hasIntl) { ' fatal: false,\n' + ' ignoreBOM: true,\n' + ' [Symbol(flags)]: 4,\n' + - ' [Symbol(handle)]: Converter {}\n' + + ' [Symbol(handle)]: undefined\n' + '}' ); } else { From 4fe5c4e167a3019fde6d16387884643796057bee Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 12 Nov 2022 18:56:08 -0800 Subject: [PATCH 017/117] test: fix flaky test-repl-sigint-nested-eval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a race condition where process.kill can be sent before the target is ready to receive the signal. Fixes: https://github.com/nodejs/node/issues/41123 PR-URL: https://github.com/nodejs/node/pull/45354 Reviewed-By: Luigi Pinca Reviewed-By: Antoine du Hamel Reviewed-By: Tobias Nießen Reviewed-By: Benjamin Gruenbaum --- test/parallel/parallel.status | 2 -- test/parallel/test-repl-sigint-nested-eval.js | 13 +++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index b749eb8497743f..ac0ac19f14401c 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -5,8 +5,6 @@ prefix parallel # sample-test : PASS,FLAKY [true] # This section applies to all platforms -# https://github.com/nodejs/node/issues/41123 -test-repl-sigint-nested-eval: PASS, FLAKY # https://github.com/nodejs/node/issues/43084 test-worker-http2-stream-terminate: PASS, FLAKY diff --git a/test/parallel/test-repl-sigint-nested-eval.js b/test/parallel/test-repl-sigint-nested-eval.js index 28e4d44b235cde..62eb46e0af6759 100644 --- a/test/parallel/test-repl-sigint-nested-eval.js +++ b/test/parallel/test-repl-sigint-nested-eval.js @@ -10,9 +10,8 @@ if (!common.isMainThread) const assert = require('assert'); const spawn = require('child_process').spawn; -process.env.REPL_TEST_PPID = process.pid; const child = spawn(process.execPath, [ '-i' ], { - stdio: [null, null, 2] + stdio: [null, null, 2, 'ipc'] }); let stdout = ''; @@ -22,7 +21,8 @@ child.stdout.on('data', function(c) { }); child.stdout.once('data', common.mustCall(() => { - process.on('SIGUSR2', common.mustCall(() => { + child.on('message', common.mustCall((msg) => { + assert.strictEqual(msg, 'repl is busy'); process.kill(child.pid, 'SIGINT'); child.stdout.once('data', common.mustCall(() => { // Make sure REPL still works. @@ -30,9 +30,10 @@ child.stdout.once('data', common.mustCall(() => { })); })); - child.stdin.write('process.kill(+process.env.REPL_TEST_PPID, "SIGUSR2");' + - 'vm.runInThisContext("while(true){}", ' + - '{ breakOnSigint: true });\n'); + child.stdin.write( + 'vm.runInThisContext("process.send(\'repl is busy\'); while(true){}", ' + + '{ breakOnSigint: true });\n' + ); })); child.on('close', function(code) { From a483d1291e1f1cba7e02b49a13cb255cfe40caa8 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 12 Nov 2022 20:11:13 -0800 Subject: [PATCH 018/117] src: condense experimental warning message PR-URL: https://github.com/nodejs/node/pull/45424 Reviewed-By: Yagiz Nizipli Reviewed-By: Moshe Atlow --- lib/internal/util.js | 3 +-- src/node_process_events.cc | 3 +-- test/common/measure-memory.js | 7 ++++--- test/parallel/test-vfs.js | 3 ++- test/wasi/test-wasi-symlinks.js | 3 +-- test/wasi/test-wasi.js | 3 +-- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/internal/util.js b/lib/internal/util.js index 4f74f912936611..6e3b60d77ba44a 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -224,8 +224,7 @@ function slowCases(enc) { function emitExperimentalWarning(feature) { if (experimentalWarnings.has(feature)) return; - const msg = `${feature} is an experimental feature. This feature could ` + - 'change at any time'; + const msg = `${feature} is an experimental feature and might change at any time`; experimentalWarnings.add(feature); process.emitWarning(msg, 'ExperimentalWarning'); } diff --git a/src/node_process_events.cc b/src/node_process_events.cc index 944b0605b148b7..f4c39d7dd9eb68 100644 --- a/src/node_process_events.cc +++ b/src/node_process_events.cc @@ -92,8 +92,7 @@ Maybe ProcessEmitExperimentalWarning(Environment* env, experimental_warnings.insert(warning); std::string message(warning); - message.append( - " is an experimental feature. This feature could change at any time"); + message.append(" is an experimental feature and might change at any time"); return ProcessEmitWarningGeneric(env, message.c_str(), "ExperimentalWarning"); } diff --git a/test/common/measure-memory.js b/test/common/measure-memory.js index a42f6344268d12..82b3ca61d14f05 100644 --- a/test/common/measure-memory.js +++ b/test/common/measure-memory.js @@ -43,9 +43,10 @@ function assertSingleDetailedShape(result) { } function expectExperimentalWarning() { - common.expectWarning('ExperimentalWarning', - 'vm.measureMemory is an experimental feature. ' + - 'This feature could change at any time'); + common.expectWarning( + 'ExperimentalWarning', + 'vm.measureMemory is an experimental feature and might change at any time' + ); } module.exports = { diff --git a/test/parallel/test-vfs.js b/test/parallel/test-vfs.js index c4842be3a839d0..84e77f4bd58cd8 100644 --- a/test/parallel/test-vfs.js +++ b/test/parallel/test-vfs.js @@ -34,7 +34,8 @@ throws(() => require(vfsFile), { code: 'MODULE_NOT_FOUND' }); common.expectWarning( 'ExperimentalWarning', - 'Module._stat is an experimental feature. This feature could change at any time'); + 'Module._stat is an experimental feature and might change at any time' +); process.on('warning', common.mustCall()); diff --git a/test/wasi/test-wasi-symlinks.js b/test/wasi/test-wasi-symlinks.js index 1389707671adcb..c2f29e539a5ed8 100644 --- a/test/wasi/test-wasi-symlinks.js +++ b/test/wasi/test-wasi-symlinks.js @@ -5,8 +5,7 @@ const path = require('path'); if (process.argv[2] === 'wasi-child') { common.expectWarning('ExperimentalWarning', - 'WASI is an experimental feature. This feature could ' + - 'change at any time'); + 'WASI is an experimental feature and might change at any time'); const { WASI } = require('wasi'); const wasmDir = path.join(__dirname, 'wasm'); diff --git a/test/wasi/test-wasi.js b/test/wasi/test-wasi.js index 6c00b5a7b72503..b514de731adf85 100644 --- a/test/wasi/test-wasi.js +++ b/test/wasi/test-wasi.js @@ -8,8 +8,7 @@ if (process.argv[2] === 'wasi-child') { const path = require('path'); common.expectWarning('ExperimentalWarning', - 'WASI is an experimental feature. This feature could ' + - 'change at any time'); + 'WASI is an experimental feature and might change at any time'); const { WASI } = require('wasi'); tmpdir.refresh(); From 8a34ef4897fb3d68edb02f85652abc09c423d6e3 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sat, 12 Nov 2022 23:45:57 -0500 Subject: [PATCH 019/117] tools: update lint-md-dependencies to rollup@3.3.0 PR-URL: https://github.com/nodejs/node/pull/45442 Reviewed-By: Rich Trott Reviewed-By: Yagiz Nizipli --- tools/lint-md/package-lock.json | 14 +++++++------- tools/lint-md/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/lint-md/package-lock.json b/tools/lint-md/package-lock.json index 565c5c9ef053ee..5150c335acb23c 100644 --- a/tools/lint-md/package-lock.json +++ b/tools/lint-md/package-lock.json @@ -18,7 +18,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.2.5", + "rollup": "^3.3.0", "rollup-plugin-cleanup": "^3.2.1" } }, @@ -2207,9 +2207,9 @@ } }, "node_modules/rollup": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.5.tgz", - "integrity": "sha512-/Ha7HhVVofduy+RKWOQJrxe4Qb3xyZo+chcpYiD8SoQa4AG7llhupUtyfKSSrdBM2mWJjhM8wZwmbY23NmlIYw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.3.0.tgz", + "integrity": "sha512-wqOV/vUJCYEbWsXvwCkgGWvgaEnsbn4jxBQWKpN816CqsmCimDmCNJI83c6if7QVD4v/zlyRzxN7U2yDT5rfoA==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -4178,9 +4178,9 @@ } }, "rollup": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.5.tgz", - "integrity": "sha512-/Ha7HhVVofduy+RKWOQJrxe4Qb3xyZo+chcpYiD8SoQa4AG7llhupUtyfKSSrdBM2mWJjhM8wZwmbY23NmlIYw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.3.0.tgz", + "integrity": "sha512-wqOV/vUJCYEbWsXvwCkgGWvgaEnsbn4jxBQWKpN816CqsmCimDmCNJI83c6if7QVD4v/zlyRzxN7U2yDT5rfoA==", "dev": true, "requires": { "fsevents": "~2.3.2" diff --git a/tools/lint-md/package.json b/tools/lint-md/package.json index 9ec41cdf029725..ef6cd10cdef9ec 100644 --- a/tools/lint-md/package.json +++ b/tools/lint-md/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.2.5", + "rollup": "^3.3.0", "rollup-plugin-cleanup": "^3.2.1" } } From 3263ceb21a9cf382e22cda7f5fd59a5834845897 Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sun, 13 Nov 2022 17:13:26 +0200 Subject: [PATCH 020/117] watch: watch for missing dependencies PR-URL: https://github.com/nodejs/node/pull/45348 Reviewed-By: Ruy Adorno Reviewed-By: Benjamin Gruenbaum Reviewed-By: Nitzan Uziely --- lib/internal/modules/cjs/loader.js | 10 +++- lib/internal/modules/esm/loader.js | 2 +- lib/internal/modules/esm/resolve.js | 3 + lib/internal/watch_mode/files_watcher.js | 11 ++-- test/fixtures/watch-mode/ipc.js | 8 +-- test/sequential/test-watch-mode.mjs | 72 ++++++++++++++++++++---- 6 files changed, 84 insertions(+), 22 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 02a07222746dfd..340df162a4464f 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -28,6 +28,7 @@ const { ArrayPrototypeIncludes, ArrayPrototypeIndexOf, ArrayPrototypeJoin, + ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeSlice, ArrayPrototypeSplice, @@ -191,7 +192,13 @@ function updateChildren(parent, child, scan) { function reportModuleToWatchMode(filename) { if (shouldReportRequiredModules && process.send) { - process.send({ 'watch:require': filename }); + process.send({ 'watch:require': [filename] }); + } +} + +function reportModuleNotFoundToWatchMode(basePath, extensions) { + if (shouldReportRequiredModules && process.send) { + process.send({ 'watch:require': ArrayPrototypeMap(extensions, (ext) => path.resolve(`${basePath}${ext}`)) }); } } @@ -648,6 +655,7 @@ Module._findPath = function(request, paths, isMain) { Module._pathCache[cacheKey] = filename; return filename; } + reportModuleNotFoundToWatchMode(basePath, ArrayPrototypeConcat([''], exts)); } return false; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 55c17d97ec5695..e4d320ebc39cce 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -463,7 +463,7 @@ class ESMLoader { ); if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { - process.send({ 'watch:import': url }); + process.send({ 'watch:import': [url] }); } const job = new ModuleJob( diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 561bbbe32e626e..e4ad685a6938e7 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -254,6 +254,9 @@ function finalizeResolution(resolved, base, preserveSymlinks) { err.url = String(resolved); throw err; } else if (!stats.isFile()) { + if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { + process.send({ 'watch:require': [path || resolved.pathname] }); + } throw new ERR_MODULE_NOT_FOUND( path || resolved.pathname, base && fileURLToPath(base), 'module'); } diff --git a/lib/internal/watch_mode/files_watcher.js b/lib/internal/watch_mode/files_watcher.js index 6c6c0f27fd8f8d..f2141051fceaf4 100644 --- a/lib/internal/watch_mode/files_watcher.js +++ b/lib/internal/watch_mode/files_watcher.js @@ -1,6 +1,8 @@ 'use strict'; const { + ArrayIsArray, + ArrayPrototypeForEach, SafeMap, SafeSet, StringPrototypeStartsWith, @@ -94,6 +96,7 @@ class FilesWatcher extends EventEmitter { } filterFile(file) { + if (!file) return; if (supportsRecursiveWatching) { this.watchPath(dirname(file)); } else { @@ -109,11 +112,11 @@ class FilesWatcher extends EventEmitter { } child.on('message', (message) => { try { - if (message['watch:require']) { - this.filterFile(message['watch:require']); + if (ArrayIsArray(message['watch:require'])) { + ArrayPrototypeForEach(message['watch:require'], (file) => this.filterFile(file)); } - if (message['watch:import']) { - this.filterFile(fileURLToPath(message['watch:import'])); + if (ArrayIsArray(message['watch:import'])) { + ArrayPrototypeForEach(message['watch:import'], (file) => this.filterFile(fileURLToPath(file))); } } catch { // Failed watching file. ignore diff --git a/test/fixtures/watch-mode/ipc.js b/test/fixtures/watch-mode/ipc.js index 021df9973511d0..5881299387e5b4 100644 --- a/test/fixtures/watch-mode/ipc.js +++ b/test/fixtures/watch-mode/ipc.js @@ -6,7 +6,7 @@ const tmpdir = require('../../common/tmpdir'); const tmpfile = path.join(tmpdir.path, 'file'); fs.writeFileSync(tmpfile, ''); -process.send({ 'watch:require': path.resolve(__filename) }); -process.send({ 'watch:import': url.pathToFileURL(path.resolve(__filename)).toString() }); -process.send({ 'watch:import': url.pathToFileURL(tmpfile).toString() }); -process.send({ 'watch:import': new URL('http://invalid.com').toString() }); +process.send({ 'watch:require': [path.resolve(__filename)] }); +process.send({ 'watch:import': [url.pathToFileURL(path.resolve(__filename)).toString()] }); +process.send({ 'watch:import': [url.pathToFileURL(tmpfile).toString()] }); +process.send({ 'watch:import': [new URL('http://invalid.com').toString()] }); diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index e24fb97ad234a8..9b894922621e3b 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -9,10 +9,13 @@ import { spawn } from 'node:child_process'; import { writeFileSync, readFileSync } from 'node:fs'; import { inspect } from 'node:util'; import { once } from 'node:events'; +import { createInterface } from 'node:readline/promises'; if (common.isIBMi) common.skip('IBMi does not support `fs.watch()`'); +const supportsRecursive = common.isOSX || common.isWindows; + function restart(file) { // To avoid flakiness, we save the file repeatedly until test is done writeFileSync(file, readFileSync(file)); @@ -59,8 +62,8 @@ async function spawnWithRestarts({ } let tmpFiles = 0; -function createTmpFile(content = 'console.log("running");') { - const file = path.join(tmpdir.path, `${tmpFiles++}.js`); +function createTmpFile(content = 'console.log("running");', ext = '.js') { + const file = path.join(tmpdir.path, `${tmpFiles++}${ext}`); writeFileSync(file, content); return file; } @@ -74,11 +77,29 @@ function assertRestartedCorrectly({ stdout, messages: { inner, completed, restar assert.deepStrictEqual(lines.slice(-end.length), end); } +async function failWriteSucceed({ file, watchedFile }) { + const child = spawn(execPath, ['--watch', '--no-warnings', file], { encoding: 'utf8' }); + + try { + // Break the chunks into lines + for await (const data of createInterface({ input: child.stdout })) { + if (data.startsWith('Completed running')) { + break; + } + if (data.startsWith('Failed running')) { + writeFileSync(watchedFile, 'console.log("test has ran");'); + } + } + } finally { + child.kill(); + } +} + tmpdir.refresh(); // Warning: this suite can run safely with concurrency: true // only if tests do not watch/depend on the same files -describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { +describe('watch mode', { concurrency: true, timeout: 60_000 }, () => { it('should watch changes to a file - event loop ended', async () => { const file = createTmpFile(); const { stderr, stdout } = await spawnWithRestarts({ file }); @@ -104,16 +125,8 @@ describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { }); }); - it('should not watch when running an non-existing file', async () => { - const file = fixtures.path('watch-mode/non-existing.js'); - const { stderr, stdout } = await spawnWithRestarts({ file, restarts: 0 }); - - assert.match(stderr, /code: 'MODULE_NOT_FOUND'/); - assert.strictEqual(stdout, `Failed running ${inspect(file)}\n`); - }); - it('should watch when running an non-existing file - when specified under --watch-path', { - skip: !common.isOSX && !common.isWindows + skip: !supportsRecursive }, async () => { const file = fixtures.path('watch-mode/subdir/non-existing.js'); const watchedFile = fixtures.path('watch-mode/subdir/file.js'); @@ -220,4 +233,39 @@ describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { messages: { restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` }, }); }); + + // TODO: Remove skip after https://github.com/nodejs/node/pull/45271 lands + it('should not watch when running an missing file', { + skip: !supportsRecursive + }, async () => { + const nonExistingfile = path.join(tmpdir.path, `${tmpFiles++}.js`); + await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile }); + }); + + it('should not watch when running an missing mjs file', { + skip: !supportsRecursive + }, async () => { + const nonExistingfile = path.join(tmpdir.path, `${tmpFiles++}.mjs`); + await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile }); + }); + + it('should watch changes to previously missing dependency', { + skip: !supportsRecursive + }, async () => { + const dependency = path.join(tmpdir.path, `${tmpFiles++}.js`); + const relativeDependencyPath = `./${path.basename(dependency)}`; + const dependant = createTmpFile(`console.log(require('${relativeDependencyPath}'))`); + + await failWriteSucceed({ file: dependant, watchedFile: dependency }); + }); + + it('should watch changes to previously missing ESM dependency', { + skip: !supportsRecursive + }, async () => { + const dependency = path.join(tmpdir.path, `${tmpFiles++}.mjs`); + const relativeDependencyPath = `./${path.basename(dependency)}`; + const dependant = createTmpFile(`import '${relativeDependencyPath}'`, '.mjs'); + + await failWriteSucceed({ file: dependant, watchedFile: dependency }); + }); }); From 94a6a97ec6b7c13ede7a3046f80d9a77b4cb8c0b Mon Sep 17 00:00:00 2001 From: Konv <82451257+kovsu@users.noreply.github.com> Date: Mon, 14 Nov 2022 01:01:41 +0800 Subject: [PATCH 021/117] doc: adjust wording to eliminate awkward typography PR-URL: https://github.com/nodejs/node/pull/45398 Reviewed-By: Rich Trott Reviewed-By: Jacob Smith --- doc/api/modules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/modules.md b/doc/api/modules.md index 6f663d473c3602..2f0c8aff496b30 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -90,8 +90,8 @@ By default, Node.js will treat the following as CommonJS modules: * Files with an extension that is not `.mjs`, `.cjs`, `.json`, `.node`, or `.js` (when the nearest parent `package.json` file contains a top-level field [`"type"`][] with a value of `"module"`, those files will be recognized as - CommonJS modules only if they are being `require`d, not when used as the - command-line entry point of the program). + CommonJS modules only if they are being included via `require()`, not when + used as the command-line entry point of the program). See [Determining module system][] for more details. From d13ea68ef653751e5f31d1d9007dd533f5e4015f Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 13 Nov 2022 15:31:22 -0500 Subject: [PATCH 022/117] meta: update AUTHORS PR-URL: https://github.com/nodejs/node/pull/45443 Reviewed-By: Rich Trott Reviewed-By: Luigi Pinca Reviewed-By: Moshe Atlow --- .mailmap | 1 + AUTHORS | 1 + 2 files changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index abc56b9b2208b0..ba4fa95048b7b6 100644 --- a/.mailmap +++ b/.mailmap @@ -103,6 +103,7 @@ Christian Clauss Christophe Naud-Dulude Christopher Lenz Claudio Rodriguez +Claudio Wunder Clemens Backes Colin Ihrig Corey Martin diff --git a/AUTHORS b/AUTHORS index 531280545ece61..35d26ccb675f9c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3567,5 +3567,6 @@ Tim Shilov Obiwac Yu Gu andreysoktoev +Pavel Horal # Generated by tools/update-authors.mjs From e9760b4ae82246833003e5fad07f4b5e6c5901d8 Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sun, 13 Nov 2022 23:40:26 +0200 Subject: [PATCH 023/117] test_runner: support watch mode PR-URL: https://github.com/nodejs/node/pull/45214 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel --- doc/api/cli.md | 15 ++++- doc/api/test.md | 19 +++++++ lib/internal/main/test_runner.js | 3 +- lib/internal/test_runner/runner.js | 72 +++++++++++++++++++++--- lib/internal/watch_mode/files_watcher.js | 34 +++++++++-- src/node_options.cc | 6 +- test/fixtures/test-runner/dependency.js | 1 + test/fixtures/test-runner/dependency.mjs | 1 + test/fixtures/test-runner/dependent.js | 3 + test/parallel/test-runner-watch-mode.mjs | 46 +++++++++++++++ 10 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/test-runner/dependency.js create mode 100644 test/fixtures/test-runner/dependency.mjs create mode 100644 test/fixtures/test-runner/dependent.js create mode 100644 test/parallel/test-runner-watch-mode.mjs diff --git a/doc/api/cli.md b/doc/api/cli.md index aeebb55933d622..9fa3463eda1d36 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1208,11 +1208,16 @@ status code 1. added: - v18.1.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/45214 + description: Test runner now supports running in watch mode. --> Starts the Node.js command line test runner. This flag cannot be combined with -`--check`, `--eval`, `--interactive`, or the inspector. See the documentation -on [running tests from the command line][] for more details. +`--watch-path`, `--check`, `--eval`, `--interactive`, or the inspector. +See the documentation on [running tests from the command line][] +for more details. ### `--test-name-pattern` @@ -1574,6 +1579,10 @@ will be chosen. > Stability: 1 - Experimental @@ -1607,7 +1616,7 @@ This will turn off watching of required or imported modules, even when used in combination with `--watch`. This flag cannot be combined with -`--check`, `--eval`, `--interactive`, or the REPL. +`--check`, `--eval`, `--interactive`, `--test`, or the REPL. ```console $ node --watch-path=./src --watch-path=./tests index.js diff --git a/doc/api/test.md b/doc/api/test.md index cc55eb6943028b..3e9d3775f4cabd 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -291,6 +291,25 @@ test('a test that creates asynchronous activity', (t) => { }); ``` +## Watch mode + + + +> Stability: 1 - Experimental + +The Node.js test runner supports running in watch mode by passing the `--watch` flag: + +```bash +node --test --watch +``` + +In watch mode, the test runner will watch for changes to test files and +their dependencies. When a change is detected, the test runner will +rerun the tests affected by the change. +The test runner will continue to run until the process is terminated. + ## Running tests from the command line The Node.js test runner can be invoked from the command line by passing the diff --git a/lib/internal/main/test_runner.js b/lib/internal/main/test_runner.js index 12753f3bbbf6dc..f7165a0288cf9e 100644 --- a/lib/internal/main/test_runner.js +++ b/lib/internal/main/test_runner.js @@ -3,6 +3,7 @@ const { prepareMainThreadExecution, markBootstrapComplete } = require('internal/process/pre_execution'); +const { getOptionValue } = require('internal/options'); const { isUsingInspector } = require('internal/util/inspector'); const { run } = require('internal/test_runner/runner'); const { exitCodes: { kGenericUserError } } = internalBinding('errors'); @@ -20,7 +21,7 @@ if (isUsingInspector()) { inspectPort = process.debugPort; } -const tapStream = run({ concurrency, inspectPort }); +const tapStream = run({ concurrency, inspectPort, watch: getOptionValue('--watch') }); tapStream.pipe(process.stdout); tapStream.once('test:fail', () => { process.exitCode = kGenericUserError; diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index c82799a30ac6af..f1e536c493f200 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -10,6 +10,9 @@ const { ObjectAssign, PromisePrototypeThen, SafePromiseAll, + SafePromiseAllReturnVoid, + SafePromiseAllSettledReturnVoid, + SafeMap, SafeSet, } = primordials; @@ -17,13 +20,14 @@ const { spawn } = require('child_process'); const { readdirSync, statSync } = require('fs'); // TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern. const { createInterface } = require('readline'); +const { FilesWatcher } = require('internal/watch_mode/files_watcher'); const console = require('internal/console/global'); const { codes: { ERR_TEST_FAILURE, }, } = require('internal/errors'); -const { validateArray } = require('internal/validators'); +const { validateArray, validateBoolean } = require('internal/validators'); const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector'); const { kEmptyObject } = require('internal/util'); const { createTestTree } = require('internal/test_runner/harness'); @@ -34,9 +38,12 @@ const { } = require('internal/test_runner/utils'); const { basename, join, resolve } = require('path'); const { once } = require('events'); -const { exitCodes: { kGenericUserError } } = internalBinding('errors'); +const { + triggerUncaughtException, + exitCodes: { kGenericUserError }, +} = internalBinding('errors'); -const kFilterArgs = ['--test']; +const kFilterArgs = ['--test', '--watch']; // TODO(cjihrig): Replace this with recursive readdir once it lands. function processPath(path, testFiles, options) { @@ -113,17 +120,28 @@ function getRunArgs({ path, inspectPort }) { return argv; } +const runningProcesses = new SafeMap(); +const runningSubtests = new SafeMap(); -function runTestFile(path, root, inspectPort) { +function runTestFile(path, root, inspectPort, filesWatcher) { const subtest = root.createSubtest(Test, path, async (t) => { const args = getRunArgs({ path, inspectPort }); + const stdio = ['pipe', 'pipe', 'pipe']; + const env = { ...process.env }; + if (filesWatcher) { + stdio.push('ipc'); + env.WATCH_REPORT_DEPENDENCIES = '1'; + } - const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8' }); + const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8', env, stdio }); + runningProcesses.set(path, child); // TODO(cjihrig): Implement a TAP parser to read the child's stdout // instead of just displaying it all if the child fails. let err; let stderr = ''; + filesWatcher?.watchChildProcessModules(child, path); + child.on('error', (error) => { err = error; }); @@ -146,6 +164,8 @@ function runTestFile(path, root, inspectPort) { child.stdout.toArray({ signal: t.signal }), ]); + runningProcesses.delete(path); + runningSubtests.delete(path); if (code !== 0 || signal !== null) { if (!err) { err = ObjectAssign(new ERR_TEST_FAILURE('test failed', kSubtestsFailed), { @@ -166,21 +186,57 @@ function runTestFile(path, root, inspectPort) { return subtest.start(); } +function watchFiles(testFiles, root, inspectPort) { + const filesWatcher = new FilesWatcher({ throttle: 500, mode: 'filter' }); + filesWatcher.on('changed', ({ owners }) => { + filesWatcher.unfilterFilesOwnedBy(owners); + PromisePrototypeThen(SafePromiseAllReturnVoid(testFiles, async (file) => { + if (!owners.has(file)) { + return; + } + const runningProcess = runningProcesses.get(file); + if (runningProcess) { + runningProcess.kill(); + await once(runningProcess, 'exit'); + } + await runningSubtests.get(file); + runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher)); + }, undefined, (error) => { + triggerUncaughtException(error, true /* fromPromise */); + })); + }); + return filesWatcher; +} + function run(options) { if (options === null || typeof options !== 'object') { options = kEmptyObject; } - const { concurrency, timeout, signal, files, inspectPort } = options; + const { concurrency, timeout, signal, files, inspectPort, watch } = options; if (files != null) { validateArray(files, 'options.files'); } + if (watch != null) { + validateBoolean(watch, 'options.watch'); + } const root = createTestTree({ concurrency, timeout, signal }); const testFiles = files ?? createTestFileList(); - PromisePrototypeThen(SafePromiseAll(testFiles, (path) => runTestFile(path, root, inspectPort)), - () => root.postRun()); + let postRun = () => root.postRun(); + let filesWatcher; + if (watch) { + filesWatcher = watchFiles(testFiles, root, inspectPort); + postRun = undefined; + } + + PromisePrototypeThen(SafePromiseAllSettledReturnVoid(testFiles, (path) => { + const subtest = runTestFile(path, root, inspectPort, filesWatcher); + runningSubtests.set(path, subtest); + return subtest; + }), postRun); + return root.reporter; } diff --git a/lib/internal/watch_mode/files_watcher.js b/lib/internal/watch_mode/files_watcher.js index f2141051fceaf4..3c756c4b5d77c9 100644 --- a/lib/internal/watch_mode/files_watcher.js +++ b/lib/internal/watch_mode/files_watcher.js @@ -26,6 +26,8 @@ class FilesWatcher extends EventEmitter { #watchers = new SafeMap(); #filteredFiles = new SafeSet(); #throttling = new SafeSet(); + #depencencyOwners = new SafeMap(); + #ownerDependencies = new SafeMap(); #throttle; #mode; @@ -74,7 +76,8 @@ class FilesWatcher extends EventEmitter { return; } this.#throttling.add(trigger); - this.emit('changed'); + const owners = this.#depencencyOwners.get(trigger); + this.emit('changed', { owners }); setTimeout(() => this.#throttling.delete(trigger), this.#throttle).unref(); } @@ -95,7 +98,7 @@ class FilesWatcher extends EventEmitter { } } - filterFile(file) { + filterFile(file, owner) { if (!file) return; if (supportsRecursiveWatching) { this.watchPath(dirname(file)); @@ -105,24 +108,43 @@ class FilesWatcher extends EventEmitter { this.watchPath(file, false); } this.#filteredFiles.add(file); + if (owner) { + const owners = this.#depencencyOwners.get(file) ?? new SafeSet(); + const dependencies = this.#ownerDependencies.get(file) ?? new SafeSet(); + owners.add(owner); + dependencies.add(file); + this.#depencencyOwners.set(file, owners); + this.#ownerDependencies.set(owner, dependencies); + } } - watchChildProcessModules(child) { + watchChildProcessModules(child, key = null) { if (this.#mode !== 'filter') { return; } child.on('message', (message) => { try { if (ArrayIsArray(message['watch:require'])) { - ArrayPrototypeForEach(message['watch:require'], (file) => this.filterFile(file)); + ArrayPrototypeForEach(message['watch:require'], (file) => this.filterFile(file, key)); } if (ArrayIsArray(message['watch:import'])) { - ArrayPrototypeForEach(message['watch:import'], (file) => this.filterFile(fileURLToPath(file))); + ArrayPrototypeForEach(message['watch:import'], (file) => this.filterFile(fileURLToPath(file), key)); } } catch { // Failed watching file. ignore } }); } + unfilterFilesOwnedBy(owners) { + owners.forEach((owner) => { + this.#ownerDependencies.get(owner)?.forEach((dependency) => { + this.#filteredFiles.delete(dependency); + this.#depencencyOwners.delete(dependency); + }); + this.#filteredFiles.delete(owner); + this.#depencencyOwners.delete(owner); + this.#ownerDependencies.delete(owner); + }); + } clearFileFilters() { this.#filteredFiles.clear(); } @@ -130,6 +152,8 @@ class FilesWatcher extends EventEmitter { this.#watchers.forEach(this.#unwatch); this.#watchers.clear(); this.#filteredFiles.clear(); + this.#depencencyOwners.clear(); + this.#ownerDependencies.clear(); } } diff --git a/src/node_options.cc b/src/node_options.cc index ce353bbcf5cbbf..d3c493a36d5f89 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -152,9 +152,9 @@ void EnvironmentOptions::CheckOptions(std::vector* errors, errors->push_back("either --test or --interactive can be used, not both"); } - if (watch_mode) { - // TODO(MoLow): Support (incremental?) watch mode within test runner - errors->push_back("either --test or --watch can be used, not both"); + if (watch_mode_paths.size() > 0) { + errors->push_back( + "--watch-path cannot be used in combination with --test"); } #ifndef ALLOW_ATTACHING_DEBUGGER_IN_TEST_RUNNER diff --git a/test/fixtures/test-runner/dependency.js b/test/fixtures/test-runner/dependency.js new file mode 100644 index 00000000000000..f053ebf7976e37 --- /dev/null +++ b/test/fixtures/test-runner/dependency.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/test/fixtures/test-runner/dependency.mjs b/test/fixtures/test-runner/dependency.mjs new file mode 100644 index 00000000000000..cc798ff50da947 --- /dev/null +++ b/test/fixtures/test-runner/dependency.mjs @@ -0,0 +1 @@ +export const a = 1; diff --git a/test/fixtures/test-runner/dependent.js b/test/fixtures/test-runner/dependent.js new file mode 100644 index 00000000000000..c382b0f989e47b --- /dev/null +++ b/test/fixtures/test-runner/dependent.js @@ -0,0 +1,3 @@ +require('./dependency.js'); +import('./dependency.mjs'); +import('data:text/javascript,'); diff --git a/test/parallel/test-runner-watch-mode.mjs b/test/parallel/test-runner-watch-mode.mjs new file mode 100644 index 00000000000000..6803ac4e349138 --- /dev/null +++ b/test/parallel/test-runner-watch-mode.mjs @@ -0,0 +1,46 @@ +// Flags: --expose-internals +import '../common/index.mjs'; +import { describe, it } from 'node:test'; +import { spawn } from 'node:child_process'; +import { writeFileSync, readFileSync } from 'node:fs'; +import util from 'internal/util'; +import * as fixtures from '../common/fixtures.mjs'; + +async function testWatch({ files, fileToUpdate }) { + const ran1 = util.createDeferredPromise(); + const ran2 = util.createDeferredPromise(); + const child = spawn(process.execPath, ['--watch', '--test', '--no-warnings', ...files], { encoding: 'utf8' }); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data.toString(); + if (/ok 2/.test(stdout)) ran1.resolve(); + if (/ok 3/.test(stdout)) ran2.resolve(); + }); + + await ran1.promise; + writeFileSync(fileToUpdate, readFileSync(fileToUpdate, 'utf8')); + await ran2.promise; + child.kill(); +} + +describe('test runner watch mode', () => { + it('should run tests repeatedly', async () => { + const file1 = fixtures.path('test-runner/index.test.js'); + const file2 = fixtures.path('test-runner/subdir/subdir_test.js'); + await testWatch({ files: [file1, file2], fileToUpdate: file2 }); + }); + + it('should run tests with dependency repeatedly', async () => { + const file1 = fixtures.path('test-runner/index.test.js'); + const dependent = fixtures.path('test-runner/dependent.js'); + const dependency = fixtures.path('test-runner/dependency.js'); + await testWatch({ files: [file1, dependent], fileToUpdate: dependency }); + }); + + it('should run tests with ESM dependency', async () => { + const file1 = fixtures.path('test-runner/index.test.js'); + const dependent = fixtures.path('test-runner/dependent.js'); + const dependency = fixtures.path('test-runner/dependency.mjs'); + await testWatch({ files: [file1, dependent], fileToUpdate: dependency }); + }); +}); From 1ce2f56cf6ef7bf901807bff6d0d6fceb43f6097 Mon Sep 17 00:00:00 2001 From: Jiawen Geng Date: Mon, 14 Nov 2022 16:36:22 +0800 Subject: [PATCH 024/117] build: make scripts in gyp run with right python PR-URL: https://github.com/nodejs/node/pull/45435 Reviewed-By: Luigi Pinca Reviewed-By: Ujjwal Sharma --- node.gyp | 2 +- tools/v8_gypfiles/v8.gyp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node.gyp b/node.gyp index a22af3b479aadf..3a0d83ec03cff7 100644 --- a/node.gyp +++ b/node.gyp @@ -429,7 +429,7 @@ 'inputs': [ '<(opensslconfig)', ], 'outputs': [ '<(opensslconfig_internal)', ], 'action': [ - 'python', 'tools/copyfile.py', + '<(python)', 'tools/copyfile.py', '<(opensslconfig)', '<(opensslconfig_internal)', ], diff --git a/tools/v8_gypfiles/v8.gyp b/tools/v8_gypfiles/v8.gyp index fdbac3bd1af8aa..3ccfe07df70bac 100644 --- a/tools/v8_gypfiles/v8.gyp +++ b/tools/v8_gypfiles/v8.gyp @@ -1772,7 +1772,7 @@ }], ], 'action': [ - 'python', '<(V8_ROOT)/tools/testrunner/utils/dump_build_config_gyp.py', + '<(python)', '<(V8_ROOT)/tools/testrunner/utils/dump_build_config_gyp.py', '<@(v8_dump_build_config_args)', ], }, @@ -1866,7 +1866,7 @@ '<(SHARED_INTERMEDIATE_DIR)/debug-support.cc', ], 'action': [ - 'python', + '<(python)', '<(V8_ROOT)/tools/gen-postmortem-metadata.py', '<@(_outputs)', '<@(heapobject_files)' From f208db70a00930175f70cedfd0c7fb6201fdd2b4 Mon Sep 17 00:00:00 2001 From: Aidan Temple <15520814+aidant@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:19:37 +0100 Subject: [PATCH 025/117] http: add debug log for ERR_UNESCAPED_CHARACTERS When encountering ERR_UNESCAPED_CHARACTERS on large applications it can be unclear which request has caused this error. Even when setting NODE_DEBUG=http there is no information about this error since it's thrown before any debug logs. This patch adds a debug log that contains the invalid path. PR-URL: https://github.com/nodejs/node/pull/45420 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca --- lib/_http_client.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/_http_client.js b/lib/_http_client.js index ef58f66f6a97e5..79ed2c8bc9b2d3 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -173,8 +173,10 @@ function ClientRequest(input, options, cb) { if (options.path) { const path = String(options.path); - if (RegExpPrototypeExec(INVALID_PATH_REGEX, path) !== null) + if (RegExpPrototypeExec(INVALID_PATH_REGEX, path) !== null) { + debug('Path contains unescaped characters: "%s"', path); throw new ERR_UNESCAPED_CHARACTERS('Request path'); + } } if (protocol !== expectedProtocol) { From 00a3b5f7d502b4b12cddf0561c6243820b977502 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 14 Nov 2022 09:19:48 -0800 Subject: [PATCH 026/117] test: remove flaky designation for test-worker-http2-stream-terminate Closes: https://github.com/nodejs/node/issues/43084 PR-URL: https://github.com/nodejs/node/pull/45438 Fixes: https://github.com/nodejs/node/issues/43084 Reviewed-By: Rafael Gonzaga Reviewed-By: Luigi Pinca --- test/parallel/parallel.status | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index ac0ac19f14401c..2f3d912a9b7ab1 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -5,8 +5,6 @@ prefix parallel # sample-test : PASS,FLAKY [true] # This section applies to all platforms -# https://github.com/nodejs/node/issues/43084 -test-worker-http2-stream-terminate: PASS, FLAKY [$system==win32] # https://github.com/nodejs/node/issues/24497 From 39e873139b760ad8221548f5b70affe758d23ffb Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 14 Nov 2022 17:58:58 -0500 Subject: [PATCH 027/117] tools: include current release in the list of released versions PR-URL: https://github.com/nodejs/node/pull/45463 Reviewed-By: Rafael Gonzaga Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Rich Trott --- .github/workflows/linters.yml | 2 +- tools/lint-md/list-released-versions-from-changelogs.mjs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 43724f9413d83a..c9b8f08f99e4a4 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -107,7 +107,7 @@ jobs: - name: Get release version numbers if: ${{ github.event.pull_request && github.event.pull_request.base.ref == github.event.pull_request.base.repo.default_branch }} id: get-released-versions - run: ./tools/lint-md/list-released-versions-from-changelogs.mjs + run: ./tools/lint-md/list-released-versions-from-changelogs.mjs >> $GITHUB_OUTPUT - name: Lint markdown files run: | echo "::add-matcher::.github/workflows/remark-lint-problem-matcher.json" diff --git a/tools/lint-md/list-released-versions-from-changelogs.mjs b/tools/lint-md/list-released-versions-from-changelogs.mjs index 52eb50d673e3a7..81b7d17197e7f9 100755 --- a/tools/lint-md/list-released-versions-from-changelogs.mjs +++ b/tools/lint-md/list-released-versions-from-changelogs.mjs @@ -20,6 +20,8 @@ async function getVersionsFromFile(file) { return; } else if (toc && line.startsWith('') + 1, -'
'.length)); + } else if (toc && line.startsWith('', 3) + 1, -'
'.length)); } } } @@ -30,11 +32,11 @@ const dir = await fs.promises.opendir(dataFolder); for await (const dirent of dir) { if (dirent.isFile()) { filesToCheck.push( - getVersionsFromFile(new URL(`./${dirent.name}`, dataFolder)) + getVersionsFromFile(new URL(dirent.name, dataFolder)) ); } } await Promise.all(filesToCheck); -console.log(`::set-output name=NODE_RELEASED_VERSIONS::${result.join(',')}`); +console.log(`NODE_RELEASED_VERSIONS=${result.join(',')}`); From ad8da86b3f43674bea0046f82795f0df45965e8d Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 14 Nov 2022 19:29:26 -0500 Subject: [PATCH 028/117] deps: update acorn to 8.8.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/45441 Reviewed-By: Mohammed Keyvanzadeh Reviewed-By: Rich Trott Reviewed-By: Luigi Pinca Reviewed-By: Yagiz Nizipli Reviewed-By: Tobias Nießen --- deps/acorn/acorn/CHANGELOG.md | 6 ++++++ deps/acorn/acorn/dist/acorn.d.ts | 2 +- deps/acorn/acorn/dist/acorn.js | 2 +- deps/acorn/acorn/dist/acorn.mjs | 2 +- deps/acorn/acorn/package.json | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/deps/acorn/acorn/CHANGELOG.md b/deps/acorn/acorn/CHANGELOG.md index ea8d6c04dbad5b..cf40d7cfd13ce2 100644 --- a/deps/acorn/acorn/CHANGELOG.md +++ b/deps/acorn/acorn/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.1 (2022-10-24) + +### Bug fixes + +Make type for `Comment` compatible with estree types. + ## 8.8.0 (2022-07-21) ### Bug fixes diff --git a/deps/acorn/acorn/dist/acorn.d.ts b/deps/acorn/acorn/dist/acorn.d.ts index 140f6ed12855be..d6d5b824c3e5c8 100644 --- a/deps/acorn/acorn/dist/acorn.d.ts +++ b/deps/acorn/acorn/dist/acorn.d.ts @@ -224,7 +224,7 @@ declare namespace acorn { } interface Comment extends AbstractToken { - type: string + type: 'Line' | 'Block' value: string start: number end: number diff --git a/deps/acorn/acorn/dist/acorn.js b/deps/acorn/acorn/dist/acorn.js index 8e8b225b0b3c8e..5a291db1552038 100644 --- a/deps/acorn/acorn/dist/acorn.js +++ b/deps/acorn/acorn/dist/acorn.js @@ -5527,7 +5527,7 @@ // Acorn is a tiny, fast JavaScript parser written in JavaScript. - var version = "8.8.0"; + var version = "8.8.1"; Parser.acorn = { Parser: Parser, diff --git a/deps/acorn/acorn/dist/acorn.mjs b/deps/acorn/acorn/dist/acorn.mjs index 5ae045a7f2a7de..7ddf96b2a8ebed 100644 --- a/deps/acorn/acorn/dist/acorn.mjs +++ b/deps/acorn/acorn/dist/acorn.mjs @@ -5521,7 +5521,7 @@ pp.readWord = function() { // Acorn is a tiny, fast JavaScript parser written in JavaScript. -var version = "8.8.0"; +var version = "8.8.1"; Parser.acorn = { Parser: Parser, diff --git a/deps/acorn/acorn/package.json b/deps/acorn/acorn/package.json index 896061c412f549..579d89f5fff463 100644 --- a/deps/acorn/acorn/package.json +++ b/deps/acorn/acorn/package.json @@ -16,7 +16,7 @@ ], "./package.json": "./package.json" }, - "version": "8.8.0", + "version": "8.8.1", "engines": { "node": ">=0.4.0" }, From 4128c27f663760887484c0b98a6a3664ead96016 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Tue, 15 Nov 2022 07:50:42 -0300 Subject: [PATCH 029/117] doc: include v19.1.0 in `CHANGELOG.md` It was missed in the last release. Refs: https://github.com/nodejs/node/commit/3770d3a450fb3b65808d312cc33bdda3de92f6d4 PR-URL: https://github.com/nodejs/node/pull/45462 Reviewed-By: Richard Lau Reviewed-By: Ruy Adorno Reviewed-By: Beth Griggs Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott Reviewed-By: Harshitha K P Reviewed-By: Antoine du Hamel --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b52617e2e91e4..ee441c1091bc25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,8 @@ release. -19.0.1
+19.1.0
+19.0.1
19.0.0
From 7cff1e14bab190bf7632f740e3985b4afd57f1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerhard=20St=C3=B6bich?= Date: Tue, 15 Nov 2022 13:27:03 +0100 Subject: [PATCH 030/117] async_hooks: add hook to stop propagation Add hook to AsyncLocalStorage to allow user to stop propagation. This is needed to avoid leaking a store if e.g. the store indicates that its operations are finished or it reached its time to live. PR-URL: https://github.com/nodejs/node/pull/45386 Reviewed-By: Stephen Belanger Reviewed-By: Minwoo Jung Reviewed-By: Chengzhong Wu --- doc/api/async_context.md | 21 +++++++- lib/async_hooks.js | 23 +++++++-- ...st-async-local-storage-stop-propagation.js | 50 +++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 test/async-hooks/test-async-local-storage-stop-propagation.js diff --git a/doc/api/async_context.md b/doc/api/async_context.md index a41b73ce67697e..2d76d41392a525 100644 --- a/doc/api/async_context.md +++ b/doc/api/async_context.md @@ -116,17 +116,35 @@ Each instance of `AsyncLocalStorage` maintains an independent storage context. Multiple instances can safely exist simultaneously without risk of interfering with each other's data. -### `new AsyncLocalStorage()` +### `new AsyncLocalStorage([options])` +> Stability: 1 - `options.onPropagate` is experimental. + +* `options` {Object} + * `onPropagate` {Function} Optional callback invoked before a store is + propagated to a new async resource. Returning `true` allows propagation, + returning `false` avoids it. Default is to propagate always. + Creates a new instance of `AsyncLocalStorage`. Store is only provided within a `run()` call or after an `enterWith()` call. +The `onPropagate` is called during creation of an async resource. Throwing at +this time will print the stack trace and exit. See +[`async_hooks` Error handling][] for details. + +Creating an async resource within the `onPropagate` callback will result in +a recursive call to `onPropagate`. + ### `asyncLocalStorage.disable()` u8rM|RaJczuN=n(-e-OVW-2_Kt3PQ+sr@p`JDrTqfg8am`JzxGVxI`fm zmDK-Vfkj_@^deWA6tq~OU5(vV#Q&R&7xOVf0GIM%c&%6Tq^)1Yd-jU>gmi=dZPCaR zgUmjf`M&r2qpr@?lv}FfAeUE>b z6edgfk5gzpW1olH=Kp!PlDepnM*V+nD5sMl7TcY5Cy(j56h(DxtYn`#oe$ca-V4h* z*g^31vzWn|uGZ@OPi@-bQCL?j1Tc)&I-*WeVJxW1W_3YS5Mrc2rnnHG=|4{ZJX=?1BlHa5UtsH#V%T zNcQQ#DrahAM;z>?tj0r{Qt-3@c*U1k=f@PBx`Xttrln&jxNU5^BQPh za>PnOrKEWB1)omRmZp)V{q!Z*KZ`fn;K19PwPRt-z75dvju#m=_sj`q%~_R)0B!pJ zE&abI{ck--JrDN(_wRcz>G+>>bMt;*^f~uF%U^Bt9cBN5zwhN=^uo_)22QD$E{y(zNE~iGtM2I+qo*@ zZex$h*!nqXr{`Y23<)q8zVb2EY@+e|Lw{*MBj?=+TtFlEQ^<*v_>RP2^!TU#n4nWn z%!*nkIyXuOG+H^X0*LC|rxIPxe3R;OOZVljk`r1q2ca z9klaDO5gd*n-!U_F>2C)KxB|3Ajk)}D*B|>X>`Z7c$gaR>UTFmA&^_;{H?DpY_43u zM&)i#K4qwR$Dy1-MKrUgiZ%L0p3~;~b0NR6xAtoL1bpShdyj6|d){y>b141&ULL@X zIszPMeIKBl95kDtcFS3^0F*F+emJRj0vKF99r?OM)-wZpE91$FPU{Q>!|Ui5ciNqVS;=oMFN zR?r|QlgJjp>j&w8&lTqWbZ!#BQE@k_#Zve}G~K1n;r>PzLVXiR54~*^gOov-cW+<3 z>L}R#rxR+kOx#u}D`>?{SW^qhyvANhO%h@<|* zT~p+dTQb$gx5$3lJOxYMe@T%Xkj5K%${*cU^Qr0sW69s6&WDNpSod5VyxC^hdMVld z-N1SdE_HXbUjA4L%R0h?{N*{Azw4|dtwvE_@0T?I|AWrC9ARFTx%UXqi|bp-+S#!W)P!HIz*ByrAUk~V?do2#Ft`mZph{W9*go z!2jVd-pTn|58p=G^;xqB+An%2avE{*u=<_6p6IeDk;EV{?tzHL#iS7d58Tz3jSg+q z%L40&LOKIvJbqO;hywm=6-ZQ1?W)?MI3v?(=7_53Xm(@u3gIS~+mID?MgPA_iJC?< zR$lAWEkhhK<86acWwc1xCdzt$o8>-&sC79S&C0ffH3~0a#QH2jd0)}>7hK5|6no8f zRz_ZaY|`TVDY0t7!KIh@?8OoNE~)K^2>2uc=ZS;5AtDd`2p8876 zAO52FiXxh;4{ZRsD?7M4Vc96z^Zeh6Px}fb`C`yW;T+a%nJi8y>JUJuWAT+(MOs|M zTd{1a`wn^Z=WA2n<}_fq!b2D+2l9webdlvMAs-eJA)1Eo^5&>@-?~kE7|e072pOn4 zph(TzWOSX8SW>{v8<9w){u8N)a2T$nw4xLom0%pn6bO>22*3(Lqz;W+94PwbF0va(yKXnpLb`UTo*uq9p9mC7

gQIg zNO>Nj^Zt!0;J|EIvg1ySzunlG^Lc#UYaQ?5t*P+$KL4jj5n#s`y}Q?}2C3ZTOH#OJ zIsQXw$k8gQDZjjvMm7mzY@RSvUzWcKP^AI@dMfPQFKcD#woz<@{mgJcg1bktIaaxj_+`<81Bax#7zMg=o~x` zWFS4 zBHx4zL-8=aK%v}a566kmd-D;t6dXuY~J%A-|pmmA7L-z|9J+CTlXcyMSYn7Im(_r>eMEPzAiY|?f-2q zrY*nzTjzy_DQW+HCVc~5lS1RFDX1i=n?caJa>;0(O4=Py>a2}?Qcb6r5mklM^=cRB zbt~+PVD{Wr@B95`qn+p0mivi&{^g`M6DC{c{kFUm?5ek1d4Uk3!?fo61jdQIx6@Bs zmWcondk?a&zD6X0Tt(ubN)z^mIPtlROZqp$w)v)JH(07Rq zU4ut%|Gs*xQ!@|LOZq74do5Km8HxRvl4+zqAL{cvuTR3&F$eJa(-=qMh!Tk4pTS}X zrc-s|bsaGkF?!K1p&9w_y0rsI3 zArCM%(}ECyP=jnyEu2NF*b)Z2H;7HAND>($vC5IXcSJOU_pa1Mp=gydWBc=eD-cQj zp5ppf#G7_0r&ZQkoN<)8asY?1a$Mc+zQ57`O>LQZQ~#@>PfOMF@CO9vAv6?#1aL9fuq18CJ*NP;vn_7 zd?Wzs{|i44^pEhqADQ;PN5%6JexB3X-`C`Q+wa5b|NHxWU%bop?fJj+bt23Q%+Fk2 z7oCDD!i4-H=KMGT`8{$z2ca{4AH)0{?qDyPV@=+DqL1ztR?jhxvWSv?V~U~isQNAX zO%J3h`eXRsLXqzKbo(DfUrcqbXvjmnncclEH%ZqN4gRP96fz#i8#TnNdamC zQUroTpVeO5QA<6HzV{4wZM>w=|F{3o3;y_{C*v4)dWR)4`5iaTH~Wk^vjkw6?*{1S zh*O*7{Uzt;5?b>W3~zWxzoFtfNrXUa5Ft#IY*X%T*iU(UzX^>852PxLA8+@kImwd< zRE+RJCQ8aANfpfzPx_zX^QP}P9^k)wN2*8E{Q`W?+sZYNPCIU|UihiUfEK1b=so|?*4zhj+ zsNp^<9dFFPg!xXzSc>JRVhyk`+vE)oKkqC9fS-kN+CZ#{%4x)j_})h-0cxP`cb2EXlK^>Pght5rm6oyx(V-0{}v@75B6Mo)5KjW z@txon67KcI-HGBNV)ON`(R#rNa=QetSRn7!gW^g$8Kt^30^XHR?_`~g@eaqG_EI<&$}1D#^gO>!l9GNSUXnoY<|n%eIE(` z^mKejtJD9tcT{0Rzd?=lcZ0H@H)(CsI0BI%YY&HhXd#OR2#A2|kpNKbS#OrJIYnV6)*ZoP?EOs8J;so3kuz4LFgZ{ zZi;>Yv#L;E&kZ9(Q&)TQ;$aa z&6ASb>q7Qzo1mS(P=cc_uZi0yws*JJeDW@_t6`?cp}u=#(y+VjOwi)Ob(x$Kc@o*V z8s7ZFsZn8Jp1y)iC{BW6WD(9%yiAYUb`<0*S$~6Ur>({;P^HU5GXn!*Qiaqqe)f@ouT=L%CtaZj0=>=cmP1!>4|y@i*1BZ2%x1Fg>t{fZ#zx|30Tqa5xzo z-f69CAN9Lh;J}ouO-A9vyj1Hh#JALszvO6qLvX$;KZ<_uKo3*-hSxYAPP_T;Vy5!} zdqap)fg$+EH~9P*O{a$Pun2!_KhW015ChZNDK%P98^7U2UdQnl^DX|dmgBmDnErT_ zHxLmYu+~z6pj_XYy!J!J@wHxwd)Ao~(> zm>>9R`bXYSdo|Ik07t@tCeDYf`L`ze!M(DdP_K`#UyEu)0IhH~eMm-L$61f+Usq+c z<6Lk~QpUZM9TA1K;G1P#z` zbPQpbbBMTfN)e1hquP5;14H&OYw}8*zoqUu+mis81UX7)`)*zd*g8RMzZT4hFoKm1 zuk4T43DbYNcW+3OZJy?Y2zA|>cjdKp-I$OJZ zn{Q5q@@mZ^C~5L$Ph@F=P)1v!!{hItr4U*LjsmF|^}&-60&|6xk%;v0h)4`A^G5nZ z_%*+9Qdy|=vc&|I>)QIe)ME7n3dD=QgZVDSshrTU!9$ zPacicmH+Y#=0AHtpJu{@ zJ0+2q1GFd<4%!dFRbdB9pHwn$YOoXu!3alw=%8p_Ci!Y&)#Rb@r;S}+pDAF9G2>$x zlEk0`BsvHOR2#TnK})k<)VDLGU*wai&>Ol3fi!L4cjdl^^=;tEDzenyVa=54#|hwg zNid-0CZ+@2<%MJ~|MR|&yJ>T-usikMd1ipXfyE15F59%_>SRX?IDhy54Up*YW$7=B}%ycdHvkt3CVy8%iOsG@Vg>vAtH<<4HiAPj^C0JE34P z6TjVfLT?Ao%vEkc+o9|O#r>7ke zmj`zOWv0#KY4erPyj;_A90>!(=Xj1-0{$N%eFBc>kJLxO7?B7(h~yM_%8t}SnPObLq4RhakjDy{cc9Jue@ z3O&NN;j3_~lr)1wU=4T~qpw^aX7t9ce{QPLKr=w#5ZcWffj(Es~BGB)y5O7`2 z0baA3lwL9GhI$sil;eyiKNyfF;F1r^U3vl!jT}%D$sg|wa=@M#UpDWV3Icx-pI?B{ z;uK;OBb%c6a!IpqtHSNAx&DVWkcn+P?FvLXCq_=*vby1;!~A|enuM$-i*8wuHF ztN(AwP3OzEt6u9@wwIa5$K)Z1K6WisfA5K(!q@YBx2?p*6*a6^<;}UF&hgyV&#TMg zc4hqVcr58DDnu-idCPmtuby}zSE~VeQy=Qh9`tT$8Xvl9UdxDnT-4x$dzHA0HpQg;xpQN%#p7*7nA`eq$QCiBFQS4kIjh=L{>sGR~Bwa@U7ibK?4 zG?c5@W&0EaQP>TeIcODA=uYowR9V?yLNw1diOibG#w&|YskOgf#?kkFQ2>3A2p|MK zX1`(@@yksTPCj_j|B3Sqqx)G(KoR``2j~;k)s{QArTT`5<@Xr4P;y!Pv~iS_!uF=4 ziby*dlDg;(MDi0isa5}!H~k~w?8Hncria{<(Ur)7eUByt*WI2LiOtgMG66zi{zyI! z_uTGdt=cy)aI^6ajscp>6qb0ff(S4teuffRKjnOVhEs2dXhEX+K)$_Hvm4?GgSBCF zzWy=X;jzH6kz@kH8BvS<9W`fhd~eyY8tEyfwPW)aK4A`x<(tz`zms*zP^LUH5Om`} zdg0e`-;~ylb8bo)h??g%(Nic+VdzQyjskq>Q--H!)XgV9*)+^xpDI`(^a%W7{2K5x zIg>}1llNIF2~iU&aYz5uQUcI4gaWncp-iZ@QbLMu^h{(c9S@;93+l{OPEH$xy!|Eq z4mI9`ED9&D0g%12t#oME@JMkq4yOA0zpM7nQ`;@)%jZ&Vn8eOq#6*oQ`#a*y`vjrNKCeSBYarL!k9?=v`1#9=9 z`JN1|=w4BcH*x7{Ne&hb@nVUK(5Xvv+mA>SXphc*SQN0MRiwwljGGf8s`If`|C7Vv ztUu@eAoP3ffKYDn_5Ns{;e_51v?(PQi=_qudF6wFD@{TXTxRy*RYz6;7<+ zQg-qz6^N~h#-X9-`mj9Xz`L-Y)hmmi{QsfQ((J4Mby2=JW_^kFTdBhdNu8&W3FvS* zlS6XyA8N!9PxxM8^JbZR2)*ABI3|dSnJE2Coavoj-n*M-v+45kss~(RmCy;dBQFE0 zQxN7_RnKp__sr!!yXR!XhMMa+*2tc~g76y{X6f`J1W{gMvSp$*c0$I{QvGGkcr15$3eY_y4h8^^|(P@eG&a<3G8SI0jb|`GS8Q_Qu<|N#$h@ z5wc`@^K5_DomQxihQdTDwriRthPO1a&G{!PdLG4NfrVLvJY%yf1VR*x9u$j$2%5eV zt5+rpuSC@Qf73OkLvq8g@dlQDVAsEHxL}V|WFpy;ZhR7eLgWz&^|*LYpIW4Y5Q4i; zB0jj>O!dy;>K3P1w2`@)!O_X5-st-7g_{*h}Z@)&uiMdR4_{xaC`& zzl7YWv+pb)kow9jVK;N}dXxEGa+t;zft8`in8}lu%4xX8@S5BQ>4g6)RfffpA-2S( zY#l)`ZK;0X&QhMb4H5AP#a<$qhTZ`&2||wSydUp#_e0_X5 zgbVBZ9+uuQ*4P*A|fifQbIy1Cm?8wh^+%nNF!t5kOZR7 zl%D3&3XS6;9$+;bHh#S4eh!H()dN6K9s&GcsktDZlTYCbxdz)E0!x6{`~ zA5vATd8(Q58l@ZGo46nuZ`1H_we(-t^QpQcnj%>h$a-1F^}zAUAl!|j1a12jIr{n5 zY6udM)a%2KC;qs~PXS^c8Tm#oX<2VF%r|x^_%(LG;LlI9lCx(Xa9A6%N<~A}#P7D- zFU;h|#kSK=k$aYp_4bSTTGNe{h-QiC@F#|AxnT#=So}Ql}o9LD=#Az6Tl& z~Wy9b#= zCjg-?)-Xnjs!~!>5adVxD6@*dyq|!5*&4v5E>_PM2i*~wmu>aK;QD^g!T!Eq=n$VQ z$UZJkCu80r_&&+Uw}sJ4zoqT^h3@;7KKZx7J^#&?0f)@|8DkCKdE$s*iFldKHTb+Q z;uKR~VH@y4`%wy-Kycv@S~eNo@GysONzxla$EUhs>AKS0t!Cu?p zM+RcFy^YV!m*i?o^K5J%@n!O54sng>-tR9}VLbqx@>=sdkHdDeOIw6<2u@dj!237} zKkJcwEAa##q?L)3oFGvR!-A)n{y#_R{huX&`8w$bprUgZQhsb1!1$`p7#CQ5%_e_* z{5wn04Ax8aOWR5eNe~XoG&E@X8s!Y?y>C^m`|T;eP4kFb_CT}r5DVE5ZngDbTW5;5 zQj_2xuc!nFB+l3V3Y)#*fVIG55YWr|+zx}Xczt~60d<;VUe_4vr^~5&4OWKL zDD=u>y)pAsD3<;dkqzJGvP`Hy`!uW35WkO$E3{L-LtDSUZ6H10uJu|yHdjtkD-)db zKgdu&+QFoJl64qy;sPm0TXlU?h%>cUJ zYI5;4rVYR%|3BhnfC>mW6`-(oT+Y$ZqMxbmiTcj<6ov&f<3E?+P_0f2YlbwN#`L(!6gl} zm=k#}%RUQ@pOdMTE zu)3hC(4OU~e4OJQQ!B90SS{X2i{ePAs=vvb(rUk7=Ig)1rq|!NH z>-(CFf1c!snr18TVHMQQdE#BMaovQ957x=Dydf`@4U`@xHh4{6F*j|JUw) z-~9eR)zRp`2fl&yewXXyd@tAfzkl)jU+d|$|5-n(|DT_M$NHX!wD3On@$cyy{Uv?g ze*@co3BTX49UiE?`cB-aP>qR$*?x&IVTSGuA0$^6NuVtclkpT7 zKLHb;3{UdfrDvYDpWFNo3*8_vc+b4_SY96t`m}=vK>?HS3fHlx>~tX9*uN@RXrMV> z3fWnEy_`zaseh^Ym0&)D#8o|J_u8bxr8A%wEX}6PG!4@GVRH#-xv4Fb{`b)@n4~x+ z{S7DU{i{qS6xLhq#rcQ+MF)TdOafumUN4^5;0(48u4IqIb!V_isYEysOh4|=4Cf!v z^F0|q69?dXFUy@5&>hHmR1M;LzMtXu-9D$P-7{5ou%UY=wyA$kOeO!supTLb&Wd5L1KmUz$ z0)AO!3oXh}%xwg|EFa3zumul?6yLN0#A6yNBM1osj`_=B_8rJ`B$v-1yNiB!4S$*+VvEhWrC9s1=0%B1w5c5(Jovg8HD>0VU1-HpFIy$iCz+%DA zl$!QHI5dhc_$&cN@)AIrT8TnAA@ix#tmHzAoH^@>ggyWjxrVu5Cqx_wT>f?rq`Q8_ z=RkXS=ghn3GL91z@-21n0HV3CU*oukF@##nyEMt94TIsb(PAwhqPyZ`g?wSRY zGJaFm9WU|$vmmYGg7gA5))MD?a!7NlV+)nYzcZIzf30qd;JR&-=xaLMph0rW7a*f7 zGzVgNYpoh3)#%@^1P<#TrlrAov2nwUn?o?>p2qXks&XsatAh1#5mZQCTjF!o(s+GA z8}HDRf5SLkuVkS0CobpT_vIqw6m|W|fu)4Iv+OL9Wj>3-cx5gDX@UXF0&{`{Ho*AX zgZ(#7NX^&T6bBck`Wp}kJB+J*GBf)5t_m-dNoB`Rs>JOy__azk@Lw|3g$GE~8X9WI zL|Q!BWVS`8%`s7>Q9rRg3KkI+3*=u*+~oMb(dBeG<|c=Ot<+sz`eg_oYC%T4=T&OC z3#rlaJq@lJr5ZFH1lpHHRDUvCQ^Mon&onEC$7!a@{SWMSURv!x?*BiibwLTQ`5q;| zbxud$XrJNH;P`QZCxDP1fTXxR|Af&gmumTTjj0iTBR=&x^}ku1!O?AvNb($j-z?Hs zAj$ScpfU$=e!KXH4kLyx)tk8R`ov`@93uhL{HCLB9?HFOd}h%SD2C^tT7lwvoBxx_H7- zhd2D5{wn)t|4bU5u(GTe?#n05E+I>M&~_R(p2{>?mgr42Odb3ri4jBpr5X4bFSIfc z91}@zwxLl$AzO@%&0V_$5NPB~mRq%l1S|9-!UEk-w1Jr(w)v-BYry`RdMjPyn?k1Y zyEHEVJCU%E7@PDpKs<1#Bn|{9I1Y#MMvV;5GGR8_q}mN|pMoF=2eWc6c@Dr(i>oC$ zdRV5c@k@?V6WCV6RO2Ln^}Ih9$LM=NR)x{{-QNeP**9yX!2rqC3B=jNN!jE0WN}t6 zsdq82#^e9u^AVBO`u;%x_gy}azJ1pG&%ysbx7%+K_CdKZj(_jr`d?SZ@=$xPxB5Q` zMQRE7zTk)bCz|NP;+5OPus@UVC5T2}u)F?ooAyepihEDx{@gAXVO!5t)JJwdYd+>J#Vgfc^xSc8Hs&>8+DFsQ z7p4CkxIN_m5vD&ivEZ~4!0PRLg%eNaLA{Odh38hagGla>L+ zAMsS<#5C&DjHERCv}yV){xcCA1WX_I;a`8nEZG7M%eO6PeW`%+y#mu?mni5K1LOR; zhSUJv?|6UaIS>tlZ8bodp&SAE!4u`mtG<`0`G39Ru@9jB3|aQ(Hw!wS^1_ zL|eRUmbpUZYXwKNcn) zhInnb)@_GD=5zOXTnuXZyvOs~$KjQb=P*P?rJ;1(%PZVE7LGfBPZc2Zk~*z7+4Pux z&w2hopNwM*$<6nhPdpL`2ml^Ys3Ys`Ni|{kGmmI@{ie zk60rBA>!nU0dUz059#;6zy2=$U*ewgZ{GYL!^3~R&fj_mRQ)Yp+r?m6zu~L{_~Z)2 zK!id9AV(lkKp)~CpwE5uzt7(Me_xN?KlyvcK1uce?_bPI{mTA(erUhf`~Py4g@Bhk z0{{RsW5D)t??^7#KPW=~>3F@~4^R0NU_LHtA)|zFhS(Bsz^DS-0}yc_`(8&Fyf0co zXG7WIneh24kE{p?;`lv0G;kIwRt4Hru_tEHEnA7J6 zT)E@>%hVqCue5mqCn*dACQo9`H_1y6ez*%g*(^|^{ekhr^}fft2hIIGK{NTEPu=W( zZskv^d#lCh;cxi$MEu`ldVoWh8}FoZ3#zp~6@{jj3!``kb7V*g%&pKi1rJdc|m>^t9t8 z`Y-OR$8dcTej9s^)bVZI&by|ML}1EVnp!d+heGV9G4dig>XFn2m*lK*bSfZZUqMHH z`IuBbKl9;HH4FFPJp-CoNEkf~AJ$QYQ=dsL5}mD zT`in{o=_*bQ}{W{>LV`Qs7W=q@E4}%H7(*%fKr8-$Q>M_6i|)^JJ&c4ITZx5tlYm% zu7LmXn8q)(NCc4z0j~>-U~pIQrNF(eY&xC^kDW*IV$EZh5-XLu$0MeGizjjavoosM z)U|!%Qg*}OD|Z3u?t;fO&drKsbT9z&ySM`>-idapdz#(u+kJR)+BPA2)e`8v@eFhr zMH;g)nu$wVQ6Lw*L#*B$;E1Sf5-Xog=R`U8(J+VCmvVw>85TK z7@+T3w#0_lzdZNiByAo(jD|ZJS_$#=!>PmGmU#Jw@3Z%)GtrWJFXj+WAK#VkL96H4 z^li7Z^_Y>U1e3A)UrJ@XZ_#(oCmj9^i2hX?yx*q%!$Rt9e=0#7JRS-bU91!!EC08! z&D;8xM^?iv-YcUa4v)Goj4}cJsF*;*cPF8hmNnF|=7c~1K(f%~9CcTE+`t1ZMRTGw zh7?f8ps#ow3S;GDYjI1ro4yY-hty-^r-=wHA}CVOq6@T= zx#<;GBPY`2Z;S9MSH+XbzRuFR$Lizc?&fzfvdqtN@f}C}(9^3nPU^D9_DWHQ&mi4R z_ZWRs-{Cgg!9POWr~8>#as${yMA$oK|lYj{%8Zs}&k+Y%wc;D<> z-sJkJfXn0lZ*8gzVJ^fM@dkt^2=QTL6}SggmbK zj>Yo?()`(m$N8@KgOMlxBppfDM9a{jGrE7o9sn!IRBCVM8e}Odo@E_xzXd|cz-7y1@rTKXLzGC7D1Z*Ik;h&ZnL`pQg<++EgVM!~p!BxeQJ;BhM6|56WAA8vyHQVW zu=`pcWvDIR1dXQu0^0%!#lC$1eO@Y-RS<%w7VX``@Jneu4{?Vn^hQXus+6QZ0ay_G zu!<)_fv6@{VI3cuUY6!7?w1ZlfaEHuj05Cg;mTH1drk8^XTx)y-XeL(3+<+na&c5j zAS6}9p)>}d?1cTipnqK^G_`7K-D8Q8*c*c=ZoH();y8f@enJZv=9V}skaC7Ft9Dab zr}!AmtxB~ysBW4zS+3oaNG@fQgGeq;{zkxvV;Y+$#8Ey$e4m+h*qV+i^f76cX5I=5 zd9nqErBiX?1#`;QZ7aXoetlts3pq=M?Dm5*Cp)Q_Eh6ljHdHe_!Gn;3A|)frAe`+J z$qQoB5@LX}N1Xb=dBA{xH**0g06@{@HJm0Og9ey~Btex~fP&@w1k8>&0CttU&eqf3Gi2Qi>g$Ix|9WCb|`ffv~eOVcf)4T z&ys12Lx2{gp`Z)O&hu9yTHPvFRk0x|MjEJQC^>pa!GaA|T09{V zc0gOlp!I`A1Cs*`kZ7iJ7MjMcgI;N*ao0_w7;I;5HrhL45MY-#F>`p(<;n~(RNhL@ z`03UXjzHyGh9sL!f$2rP{(+~|j(H?{AyYEZa~jIWnbj!cxFW*iQ*rr`Eff96u+4pR zmr_dhTL`8|S4UDw?=DQ~VeV1+1z9lw=)hFP7)zoX8`WBSn!8KFD36tEsTx!4JVpHw zQG7B0fV7L1pl^R=`><6Ff_-htX4` zk8kZ&%&JOg)y(JhI$TWUO=NW58S%|N7|B-Nu-3R za_diM?HC~u0y@A)(Ky5!YS4bk8aV!z_S7(>2db4VwXrf#USt$4_WY+0I@>2Wik943 z1PHu4pUG(da0UG@L*dKYG53&1%?{}g@Js&102qJ-FZEu>`pA8_`l1D!5&b@`a6Bt_ zeP7mt)*8wlC64^SZ~@06q10QY46Yb850>Q2_C-9fo4jOTP940dEEFDzU@yG?nKG0! zAG6~e^E{D1;77=nJa&J&Li&^|06_RG1JD*XucYl9y zeV=@W|N0~gu7l|BR8Y@(`>FojXCINzTS^2HIiRseMO**%+O7}^69SpC- zYWDW_x7}f^X;{}?WM2`Vcb~9st~gdB>={icl&HUTI(rz_uGQ-f|I=W)kwrvJ+ehs- zcwYWlH1aJ{FTv+*S=Gv#RD(i-mr9Q1hD?+TlL(ub69gwIBY#0|)vvafqP6PN{@T*M zhWek_%H;UlSov;i?e{jfEhu<0LO~#cJ&47yH~nXf7)5T2ZTSou*~L7EvB89&J)zv6MXcXjkvm(v#;ejeT3*t9D_1*c z5iio*SW%Max=#Aowxoy(M1*H?0ftRn{U=S=aC+WP!SI?8mq*`mQ@r1QLqIAb3d93C z=23XXk<136i+le%m)%JG^9I)_8^$2DtTlI2E!3NU^Dw|P^;j)sJ^CI)7FM?NL3Pz? zq(#-;YV8Z9ux+Y{b!6^?!fBFT)?;Z@zK9!7#qM@7%Ms@eaX;1w1e3|Jde(^-uj+ zn)Mt>!L#=_{7+^4YUeBPzx>Hp>b<<(b-?_$D---=Jm2l#ec!IKztZ}fKj-H<{+eQU zTy>KAC`rXjJFO?b?Cm)EZvLp6Idu6Po{g^3(n(rzZH z@r?&g^dz2KD3t!j(00}SFU6w@V=IEm0Hg@oLb+!73O5Q?3mL=iNJHGVFc}L%pQ9ac zhu$XIX}lH2OrHWCRb{W`SxFp<)Zqe3D9QNd3x;(6jJ2bJpJ<-2s04MTAYlSYJd9P^ zy^}Wnj}I?hBKhNl3JpF1mwUQ5P=CEeJD$a(>Q39M^|0v`BQ`~C?Wi~hR43Wf~!1| zZsnrV_<)HsBj34&8nNX{y?83*qb&{(%tD50JY_^Fbtd@)xnL?O;jce(~gZqUe7IukF z#IX?<^9gM?#Ix}#vM*zh2m@l?&6^*|fNUxrw<{|(X{HIH%yudZhY+Gw%&w1zKb{pftuNu}YfPHy^45F_TG z_usRzr*8yVK@ZO)#v6BQF_(G&#-H=4;ZU|b+OAJay67~JDkgSv$D{S7gEkf`Ph~kS zFoO5m;NI5`2zE(jPyb+9p>WY81M(jhrI8v98S`2mFm3mYVN4dm4&uN>1;@)?F_XF) zj<7IoQASHtI=|XrXaqt#f-+(zB%LhcdO5C6J{!KS+m_RulzUT#;#gZ^(t9XL77b-k z7GTug$k)vkC$W4qy}6kKCVu?>e*b`*+aQ&l6kOQ54REo9)kXr8D$d3{lbNWE)%7Ic zhU)B4VunMuZOM9CS(S{aJ_vQ#92*X{EZcJp;NOb*J20>eL0;#zDm$U>Ct={&Z)>{_ zNiI1wiq%aPbNt_iNLogEccZ2 zpzaWEr19B%!vO5!zTcFmzFLTd68$ex=(uuBjgb1eE?|7fACIOh`l84{H(QLhO{HeP z>|Lq=e?DjxGOu*-;LFxGZ#Bc^#>-fqw{3Of&sl9s;QT~Li)D3L+A!c$+*Zag31Hl| zM1c&03o$j4YRR6oDkTvymc-a_+B}l0qJ3CcCVRKVIH^5xdk&7LtHeJ@xqZm}KX;hp z{dDWGN*T8i?kR?eJ0k;gMV{Uzqob#`-2iQ}lB_i|TincX2g@%lGQevy)eGvn_R2vt z%XwxyMtiUh)oVz{(a+x!!`3-IbJwX#KWigumBOm8;0#xUu2}~Fkk4XeJdRPNwR(CJD!vLl5 zuI*3BFIdN!NRToZ+y8vHs2IZTUdXde@^ z>oURvr&cJJ3=FGcB}A;=8qS+}ljzG~a?*Q{70++PvGcDi`tZ+gfrY9{p*rhZFbz1- zzd2KjpXgY~nP%S7o@JpM=fP`bLUAzjd+>u(rFwvh%#R)(hmmpW>c}(`d{Q8!k_7-+ zk!2(n4By+g@_diwZM*`~QxcaUP@ zh+2KDv$ey7 zZK`t^L65m>isbGuFEp?+&r*cj@Z6&|A5IZnJ|=TXVkxThBsViwUQ#V8u(3kPcA_8+ zvoH>Y>E}%+kf@u2IYCFt)j{1`50z13PLV_;QX3afozeFyA-yC|%mb!+UlL}gL~7p= zet_og?!t+abdAC@Y~6|igWs)=M+BmmQ99AWuvSD)0NpB)p*9^y*Li&>~dm7NOBnK238kUB#trv-IS#+uWDWl@4Itwtu)uZHQbHiSSr397Tw##_QE6 z$j&)ebia2A2YUhw^AKKUvA3660O6{&n_O0q+A=+jIe_eiCdFCzj{6m4r}*!1mX+Ym zF@YSh)T(*!A*cjtvy{xnU0f!!ve(p>fMSTFSi;DMJp%GggYZ&E+tKSp>g+8}-+3BH z@FD#V<%CN+<1osfLKFFXS&NGVWM`%R9b#R4sfT1Ir2a}TREYaGE8TRZ-7$}~=K0K0s4r?XvPceLsd?c)QC1Q2SM zU#pt6i+_gjO@p)R*WZ#)SI`@{P%AxA4l838QVoN zWE_+QE>YXy((m4GtSln5@MM95J7X*5lb_3d&enq031Jo`2-g^2c(dMAD5`RsIXGI zi3^p~m_ZOE0m^r;wT}C2oV0}3QX2{6o&o$6PI7h;2q0K+6>cd9Ax*fDNJNxUpGy`P zu>==Hz^V)lF}PM`avvjmzUA?3G~wZS7+ti7JMWXo56kEK9}ndF<-fOs+~2W9;UE1m zO5u3hiThor+;FP@p3C*ZDL#Dva!xaXZ{PjX)%s%n)o+@);|0lNi3gy+N3*T`sH63h z_7D}}kF&pvi<}3M`@$}}|6@Gc-aZL&j(<{H^Ua*U?YGWT?J?|`o!{x5hc}+G>9gB$ zfbTgCvp6l=4v{wLVGZmB?M7IT!BT{2F_S#sXZ_^K9yXK@m;Uhe{FzkJ*XO)0xl#yO zKDb!@hLpegGXvUw3mt0RQD3;A8H!^DWL3n-&G$treEP^5FX^N-mx%YQ=76Tj%_M8O zFZP7M74GCNCqH8aAMiR@1#Po0{tIwbJN|r~j*Gf{dG)`DU47yPG>~^Oa+^c}wsB*NA=kA(H?UU7ag4Oh_EO3xt=R93lN8L>P9?mfs~d2Hi=N zNyKT&fdK?<1Y9U~%+oU7Wb+ei%M=%PB_sg>LzG8uKJ%7Pk@F^lKMiI;&C)Q?-jO5C z4x8WRS_F+tNsE-~pr`CtGX``(%Y)elUL^zM710s#@?%F;EIHygqv{`z5vfhCc* z$ejj)SN_!-POi|SKyc`8d6k!UsN&W9?Q48hk)4*uqC6}J^GxzHbFCA-Jyav0`hxPr zAmzePDF>GThLCuF7x52EPIS`&seD>?V1pL}yVv4L$5od+VM|{&yUY=vi-OwmZjQnY zaqBvMLq`NMi z=32x~X%EWY-E2uH|HjX)E}l87nmuwAz-axXE1Y*<(c^IoyxT{vwk?QpDvWG+WVf1rf~20)k)4Q2go*qxwEJO_2a?q+ec+|2(YxYK$pfBW3xYnnH?zNx|@ z(o(BUE}1lz9Tlw5j=mL93R+QV#5lSej>)b17z6}>NCcPy3<&Y5mU!^5{q%{-OD}!d z@?XgP|7y0~qwlYGp0-qnG(V@iY0h6Fi(yYj^ZN>P}XR1ohL#53>UH_lC+1rGo zJ+Ot@Xo!gMrvF5BSJ>fS=^o;_jqe`2-d{q}u3R-fwtOzWf}7~qp#RAG)_j7%4?N|B zf7u#;Q}nX%7AQoJ=jjy(Kqw|D$o{3?$Ni~HB*7ISxfOxit&i|7#|Q9r{~OADtgIcb z_pb8V@Au|U17EOy4-VC3qqLu)V2Y@y(v2w6(vH$k#SkCCK!@=Ty73q5Q@&U+?uOV;*W`Y$>-B#E z&{6X|9YzE3y4?0d2i>su;mZ8JRCPav@j!AkEJ_k>4aKq-@$K^NY8yyS@`ouzQHkE-S70DLF=tsQwsG{UWMeQ2W zTCk$~zd3*XZgKi-e|^E^KgZp0@$#OM8i#Fm_oCuv%uAT;IH_}|{fnWHy z{;~}93q-K~q`@!9V$G>gWdj3(eP|jiC;m9-8!Spb=M2N=OAw>w{Y#&3GDFu?K`-h@ z?j-}*meHUrdM5goJ@NmYh`&2Kpr@75Jf_r|k9kRz@iZ6uZd2ci<8+=%?^FiV znu}uz$!g41Bp!h$+i?m7100>sr~7LP8`LyfejD+v(`SREzmoMBOb8&r z7zluf2*?5VDSYE2TZ3afv)sK~CjD8b895zU02_oLw^NrbeFWDOOfnv0$iL8Hew46Q zommaiEF(Xc`6s^>;nJ#If_ItVV)*?h&jR~zu-{Y2RI}aqm%iU0V-K`5pm9e3&wbq& z-wvq%o#OZ9&<3EEmD@Wn=;I&WmPkR=WK_yo?#s*kiDPJ3^B?{Lbd*dDY}9qUX~&nOoD6q^nQ1F@u}$dR2|J%FkAed`Lxzc`G*y)U$C`^ z=lR!jFOdo>DP3&;?M_ePDz)s{fYWNBnnw*|x2D^G)%6$?Wn@RWGB@w}{GX{N>SdCf zC<^EMDMPm{pQ}^=jad?|oz-y~`Tp{Hrk=o=t|1P&(p%VprcVe&qXTt0b$>JeXDTXi zE~Rs5YniM}V*#m>1T?h^jbKel?~k3J#k4HMb7~89q=5$vTn4C>Api{5d$eWQ1i9Bj_MBKxn$e-K6UP2|C^gzLYgIJBL=#=!T4(C< zHxhGA!%qBvaiiPcUNxVGi4A>@)i?2P;~P7O+?ppF`gXE&D6ETZ3?gKlkVRAt#v%gu zi8m#*c^Z_FJ%gpU-J|v;sc+77E^(kgumwN!bdkrH}=yG)A!Jx-~b$teH9pVY7%oQ4YM z-gYL1HJUa--{hH%B9VQ9?_JB|b{J`OZV}oJPWsk(GyT-8U6BXC$;BWdVuvs|_{b~_ zA$jJS5J3oly{ss=YD5%xqDA7mf(Y)9r$1a+9SDSsJwd0{*+9^Xxqe*-)>z}^8GEhY@9lJ)rjG{`7g3RuOH_?2 zC<{vdd$#y}wdKM&3gW+{8&2av5qGj~SE%!kI6@G5VGKgq5aqthA3>^Q-*A|C+~-Zg z#qCyQE$Da+eI;#TU)LE_Q}1Y9-$Hrvl+gsMAbIb~mhL!ZJV@~-G*h6fVtphg*-IQB zwEU=y+614Bx~z>sSP&$|%pw`d4Xvq*NJX4dlqf06%aIv70 zn+^V>FP$V+O!k`Blqw^eg8VhTovjltK{=X%Jg|=dLPAYm-6oJmS zLd$Hd@zw-w_MNdpU9O{t74{tTq#tK#@ltRb%Cp7av@ckfua7OMg;W(fcs9ruxKPER z1<%FkjtC8J;FxAL;$OV1aejhfY2dEC)>eXcYSW6=M496W2)J_SC|Kcpfrm-cNsN}h zM*I7ZRgcD>Q|vvTjO71vdOv~dS-l6SkR@1;ejjpp!blhBU2&dr1eCpRF)wflI$Hn- zSF_gOoyX;OPHyVbna%&kAObIPyXJwJ%6;(z`2Nit5ncor^q3?{S=@Q)eyaH%q~v48 zA+d z1O9SNU*f|iXTXeI+ZwsOwWMaz)$s^u-iO1xpV|q>OPV&KbP-EAAG< zlb3;)kF=K4_WB+gCB*AY<08nZ3Q!M@PZY+i4?(Z!>R90OdK0(#veT>)ME)++0`F#eafS13Cd;J&_<~GuM=PAz`S{UCb4ghYZNAaY3HhSx;uu7e1|iE*{4RG zT{7^(_?|LTX**IFLW%hN;TTZZL<$ z88S)v6|djR#0HM-?s5UC`sS(x1RJDb+!hBLp4RE71Rp|qW3z-HWXb;FicPTw4)+OR z!Xn`$m>@4%P&9uT0%Z}Ux|e8>=_Hs7o1$8H$g4<7G8MA9mkcMS#uM;`n0Dgbk804n zC$4+7=%)(TDx~6!lz@Q9I543mrW>hS56{ZZD3aCA$N+BWMRx`H9**WM=TQu#3U0!9 zxE%Hz-AvZ)!Zbh(JCLZnupz{W@9phVrV|h8AQLTC_ZPV0!=|9=f+Zxkq}3xF;~}_2 zT(Vwivt^M<8!`l-Lv$W_FrAWJI&_03*c~~Af02(hhMQEtt?2Wcaq>_A$+3lU0mM4x zRl?ZVwy?YmAx+yU@CM|RNb(P&e5maaiW83(8Ex{voxB)=_zAL4=c|lW*}+9~ZB@w~ zAOT^ah~Y)tf2lyVfK^N&6wim#7#LcDp`qbNrU&xl=h@R?K17$j8Gkkg!#N; z#&aa)BK`G%9h9ty#@_iiY;5beu!nc007fgw1e_XH9I3#902ahJErz-gxfJkaToSb8qY~ZF2S2X-i#?iRsdo+G3SsVX)sE9I4V9 ze~WFWdqqCersV5U9JMP56I8{8DOsVFpKTBU>=dGr45*;8QSmTcS=SV~Cgx3G5J5L` zUwvbmMm(eiAQX(ZWgLa-(t5@faJ;_4b|Gn0Y`#j9M2OP_Osz3qBo%)Ui%B`pdJ+&U zC-nW98D*R-ziFY{cdFC7lcjI_#}I_JB`Zb~1j$OU)!&>i?3%SLc(cfW$->&+R2B(XC7evirND-)*NBwA=yI_YWRX&D^ z;C{%hYw~4Hl;J*M&^+Wh#3qe{37(81S+p@X8E7{O+`$Zn0^p|U z#;Rqbk=aes?miH+s6Yl&WquPxEdqjdbjl#0AOH9B+6x=MG=cgbpLCfzO4qu_>p;=} zen75(TNQcMX5^nm3H5l*5ox8v3Ttp5 z6&HSgjUH0_UKj*lusm7+PWvcL5cSLArurYYymJSQm#5w98eovZ2=MnM4K%FT^42AY601!<;t<;0aVI;N=0yjBD{E-9f2 zbMVJsj;q{uIntmPBCMeUg=(1S#pTgx1IM2Az>X|fpI1S>&eo+Os7WIpaKMn=TQ@xs9gG0`w{Ob*4=T80* zM|D~&xHc3{p`mHB5e~a2XjA=4wUR*dwXW{te;10)Vx`>1>#Szda9_pcT6v~rzq4YG z$NCRHz06BOe4=0=zB^b4Eeqfn=`Z`A-#4))BQcxocnGaP4CVbG*qKfuYgNPjeW#$m z>4kJz^6p@G{Ro?M2Zy*K7ATWK6v;>|+&tT(JV4mqIyG>ArMhr43LvHvdRf1x+x>Rm z{rOt_1uB;=Uj5$l@`{LccfUC}BrYLYt|4IhlR*0gm;ZNO=d1NZ-&DUwYEDPT{0YcF ziRat)_oIX0m05c^n>Rn?@6|X2p813DWiS)aC-*&DF%$H{LKEbW?}&fhC&}W zBUSy>DFu;Nxg9{G0{w5uZb*)RT2VNO!W;hMvo%_{*YfotJl3cCy8jl-vEY8oTI?QB z>l3k--54d4PR~=!+|x6~M$(3iy zK2-)5|`;1dzkN3+M z5(xwhjfs!m3IfH?Gx4wuH5^{k+EnW^+7mZB9~|_uWB>iaD>Z9aIQ@_3YBp8GI_y)} zrx;6SW7qJFMevDACZ(3udi=MKdM#PRUB%%QYx`9c>}9Rj=duW8zG$0&=|JW?<`rG` z+Ltv~P3IG=iRWl?Elv(|ZcXtXPbhIo_JGX2Tp?qNj;#ni&Ef(2TAy51a%A%PD@$(f zSFY=MEm+-sW1P1|^lLPX1u;JW%%oMJHg2LDS}P*^SvJBb5ul$ngKaGMz_pl!stRV%jvUB;$7ZKF#3V%cwS z(XW|iudKOzamDj8Gao>gEtX+>lkg9gc@m=%fVKb%-aRg#z-4%o%x&-r{h;1(8Xq`0 z&FAJf_8j;IFw?4#v!6*E&$V~<&-;F+3IC_NF!$P2W&hXwzjF`s5_wmIov$m{C+w_3 zv_-2NrS|327X>cY#dP5D-2a@Ro(D(Ui|OcELf@w}!TJLN$!ij&Y;n?i9}o8)*u(BB z#p@5yyZsWm2EbU#o}K0H)Ji|aI@X8rr#8V}BamTI^%d$CYrTiRo6E30ESB)grv+pt zNFVE~r@jq#rpER37(WF8opR6-7w-^O3}_XYkKmxo7ZL15Oj+j2lqT-e6LGN-PuAO((JVL5 zfD_$iMack~#34-*wM&MbV;IwDq34vt)Z-&>Cg#kM^n#4|P`w-opZgyf0w5W8k%Z{; z{k(vDyiAd88<^Z@+6X3(kE9*uTPlc8aFN@9frL3;o!2<>eM+{;F;*iGlgCyMv^mBA zP!hCuc4RwX29fMw-pd~L4el;A91^0yn--rMoU_|UOz&oW& z@)&O(;{htXRZ3(_;vvhsM^ByK0%0M~mM?mwTNAuey@eSJf;H}w*BHx?r`D0H4qvWg zx9&R#tNSKP*>>4}YvJ|$2+=4MPpo5aVaUo<;AG04)oNJyq!Qqfh}HK>t_40CKrpvl zKI*IGi64>TP8bUJNpukcu9zv6y3NpwPPSFDfh4L)8rS7yd30UcUc(j4Cgy&Sb+bVY zibj5sh{nO|pI9>jlc#Qr_Ui{9#WpZ{{90DatGeZy^W;F4_DMdb3ByiWGsg2Zo=9(1 z#+Ic@!Eu)&I@IaYs?cMhY6a9ssGY*$^W*DTGW%mH?FL-^?gl8Cd03~i@i^k0L%0{( zrjGi4+5O7_<6%l-ggQm0llZLhv&xCA_X=VUW7*N*L=>xpa9y)wl%#pB*rx5=g+O(V ztE?Aj_xn9faC62*RZmm4EA}%erL%JzzRSMCruwT(1N(nW%O_5;M_Cx;m2R=+y54F^ zDqYx{(fUL-cx@i`$p)P`RSV|KqihP5WCb6c5Ojr51uP)CIU#|+KKCVzB{l{IClh^Q zr(Skkn@$pcS9%bwyeq8Gt-RlaaRvC(*)K5rwi?c`%H%>9#dK@uBYpr`3A+^qqbLP&d|)&Dt2a` z#TDw%S(_>4w8+Z2%$f?pG&$B0B+$n!b|NyQ^zL6|e+KVFjJ2kTaw+Ar#<3^_w=11>MWyTW*h=re`wr`v|a+ zCX%5>(sq6}{c?!P7TyOJh=gk$iQpOzQF+L-LjaXE-Y`S(##JvitfNgv z62Rnhu!x{lMT^rvCFQVYw|vu)y3XR#ZUW6?0we*zOu@-|kQ{NPwc_2%Z82@;KTat* zX@5&X683A)}H4VAhk&|>1oM8?pN(C6!ICaM^k&XSZugtwS} zjRF8Jhw;3bv}Y=Dg`yysu&6p$SEF)krHw4)#HHmn%m_=mN0uvef{ua!aJU2=60Ovp zo#(Ma8xhApVJ-!1*}mGQ6%-Vrt@6CEbIzPQ9BIHX!r7j2_TGaPUz6C<@x`5Zmrks2 zEVWokB*AHw%wsG@128tduF*g=%$VVv%U>HBK|;kQ)fe>Awe^cla;W;4mso&s4gtQR zHz~`zq>ZXV>Kngtf^^w3L)pieasjcP5VCg#l^*G+ZX1=SG@TVXHvc9GzrQ#5 zDN8coSMYl!`r#uQnpkg~BkVWK~SCcv?f{4=c<|n1NI7@O88;smP zN{LqzvbfC`F-!)Vo@pPHx=It(i!=p%Dh)N|TIJPaPVKc_rC$3n7ZJz2%|BD93p^DB|hSnFlIAJO!WR&!s!V>~c zULnqXO%rG954}ECVBR*t6CWtiiL;ytcq3q#30Tj7IgIcW-lX`dW~jU5vrymNto3aU zy5_kJ6v=Q>5HHfX*`aC@*{x@ky6REAa1lcZncG31p++-lV$m+W^19)hC?#0~nBe|g zV1$mSwz=srqDbkw?G}X^JD70aC?f$04UzO`9?P9p%S~xMe?(g#pJvv^lwQLJSEVdV zE85A32vk%hx{nV}LvM=OpGy(H&$Uod|EYi8IGB4ItlQuJ>&_J08_Yh&**slj|H*sz zECSTD0k}IThAz%A8%BKPVh0(w=9C|Smbmq1wqMrLGrgpfJuf_ymJ8`Y-1SM?o;h+a z)p9jVDs0UDAN%@(I!;d0sbgN!f>-)jT1bzZ4s-%G#48jV$eJ_J?bFoJ-s~vg$Gg0j zV7^yFYuxS$4IOmA+?eLkI6Nfiy_9Db?W-oa+f|j)q;?5{t{*>X8QfCH$lZ5NUl)IJ zmQxI@SPTVK2l$Z)m?_u8!dP2{D(RzV+6dO`$sw48njwc4%j>GWrCIMxG%vWhX0RpK zgN2xC6r^(1fJI$&iNz$ArBV?Dl3~F?L+F}V5DX52#${}2Zwg{p199|H!sl`0H#Ds13L2-O3SSYA?Q$OfoXgiHhxk+Gpv5W-<3!i%X02VGH!4m=J-TWV!hwOeLefJCXK1MeDg1y4|z^H@ReE(x_ zx%po*o|m)g_xpzX{@2Uf-AOEt`;PSf?v4TnSeo@2HX3V zfP3%~VR>+0^74+GzWduF3$T#UgZfI}jAz&+akI~*Fmy*m8t3epqpE+l-5P-x#5An^ z|KrsA7d{)ozN)hpL1}(5{)e`k*3M-mfB$yzu!G6tVHS;SNn>llPeqK3*02h4|+Ovu|(|JEH-o|rg2eZuvHynSTX9q7N}6KX1chV$7# zgOd1L49y+?eSh|Vt+52+E+|+=8ZBOJKMs+rZQEEtf*1+*FH!(jQ{VwPePQUnyWbtK z`xWAisagGS+1cx>zuC-?>i5Ch$|M7cYfA@V`{rd9jEbp=@|`1L4xRqcg^10G1A@f zy$Xj0-3o*G&4)wpYH;oFCv!yhz;N=23EqFu(ME)60iQ|7!~_^beeI+`Vd}VfYX><$ zs%H0OcNZf3Pb`zcq+Tp{I#sBrF{wZYwKn)BvZLUW{%?x=g3=&~qWrHsgv8SFr zEb0-Ewj;NSFu8+HcHmpRElw0&8AaN4G;>HJ-km4jkkBLj|B8_Rf5QA#_mV3Mx|`DcB}^{lVE2Rnm;Y^vL!+u3 z(hNW%Ss~bU11RJk9opW3y$rtHRKg43)d!CFy~@TO2?#+sQn}HAFgJ2lW{3d<7(kJg z0}OdkGfEgdY!5>6nh0CR*lG% z+!vx`)x#B~`xy8$P)&`q;kvOn7UYwgNdL>lZ^bN=(^?3=#8C^3|cg*g;`s?2BlWFXKPsVM@z#t%xB2bb?;{FnS zV?jPmc$+!782Jr|P}F`8M}{K*G~#m)?Ggxjr$`I`&LE(>0*Rn>IN|>oZX^fy5A(gk z^4dhZOKr;-PQG{TD9{JK2|pY8UN81uSN$g3|4FI6@Bd})eH~xXh3fiVMj!a>Z<)FA zv*4$BBgn@q+neZz=zmH7t_Rls_uObLK-eEMfxXMz`qv-B|38Y~i{&2q|C8}=v^0M| z>GS`0q>I4&KT|t>{yrbqhPj{UMx(vZ_sDI$uAlyTe}BO7kOKST`M(t3ee-_z1K*kd zKIi`a55w>OmUk78eQt}iKt8uMO0zFaw3B>Kd(VP$DhoPB9}b9xf8V?RD<42}p$~c2 zK&#=FRaBzu_?iAOKh;UY0t3h*0qh8*nu~uLbYdu8>8SgoHgqLEka>)|H+AxU#Ztnb zv>k~B)^-cn>8Ax)GU%W@hk*^}f5qu_ttata`NyqWZb~ry(a~K$i1p<18)MYuPwOpZ zXhI=$hXqU8uADSR1gR+%AK~_lX{-CcF-2sBkAjL9G^G6eqfGj9G~ybVL1=44#igof z_Dn=yPvi8u7d9FBdwhdCo5`|?+x|Cxrn+rUOo8`jZ+ovpJQrolZ;gn^9Jt z9}DupULeVV_Z@2Wsb7IiFd*mI1Twp0<$*NYe#?GuJSSaGK$^g zI_D2kuTKZMuv)B2qSimCrPzO?pX@#Lg@%{(Cp^1DBwi2xI!^{V179EMtzh(+%T=^! zYE-~{ygfK_{+bjYNk-$*FR)~qS#IHn28a1;t^Z^6EnwCCX{~43%2j>tV4n?<(tHRKA-hjr}7cU|JO}j>!YLQSn>BjxT z3gN?oHIHqAaam7fv=N(ak}P9D8Z`c$0Cvt_|Fv{D7(X-106V370OOi4d9?VoB|tTg zVVe`*aP3s|$zj1J;NHmAQu$^J2rA^n+zXQp@`lwC{5J`$AXSN|w(TKz+PL$BC)=%a z*eK3f=dj-#c1(=5H&=(Xr z!Bth8pO+~r@NaZ4x-Ur26-fCR@DZ{Cnh!;Fk}e@8xgh{+-Q+EDIbvsGVDl!9?vT)M z_J#$pgBnFRIN;WLB>H-K7sgnlv9ooU5(65$VQ-q8`}|JWmhce;BMU=iN_mVQVJ~4#i)R>``CI%rBK7b^!^Fwou+0VDksKOW+Tx%4 zTk(w00R(1*-4dJsjev0)Y;4brPaVOqB{XyvQh|7xtG+}(7+ z{Y8V%nr}BZpf?v059&YgIgm;OpP+g@7G37cN9bI(=^y%)5%qA3N^2#3pW&Yp{sp~O z984E}98XZA zq7T+Dnkc~G2tijKfNb04KrL6Vi;hUQhrVE&!!FBzulgB{oyV&x%VZ|wx;A;561)t5D-|FO++?Wd)&&}w9rL&EoS zTQ**~ePekYsl%$}WEVPzl|d$iDvKer3yepB^GcrC_#Vq(LKFKKk6LrN?c=a;~&@Pel2Wn5+Z_+m}c!#U^a{dF-l?LE?}tSOc$CfmGzYS~#)A=23>CE0sg8oL3W zcHbEZ;6bykcVy|_7c}&3T(qpD76t!Rh8pJ}e8o|T!9`{>^F zi7n1EXk@i7=bu1^JV_)Slh^y#P~GC>)x25Rcd}b6r*VC4bF4 zasSV6!Dn)}P>27*0GmPwPRFuNxAYqgKDWB&x?t=ZqQ04>2TpKT%hVFs@$^@h`*9NP zELdJJwR;t>OEs(_NbGBS24Q#(6x@lV)$u1Q(aG8vtT+}OyQ%mt@M1Ziq4iuHt7