From a7c523e26df468782691c7e16b2e033c6256beb3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 13 Feb 2020 20:40:50 +0100 Subject: [PATCH 001/192] src: prefer 3-argument Array::New() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is nicer, because: 1. It reduces overall code size, 2. It’s faster, because `Object::Set()` calls are relatively slow, and 3. It helps avoid invalid `.Check()`/`.FromJust()` calls. PR-URL: https://github.com/nodejs/node/pull/31775 Reviewed-By: James M Snell Reviewed-By: Ben Noordhuis Reviewed-By: Colin Ihrig Reviewed-By: David Carlier --- src/cares_wrap.cc | 30 +++++++++--------- src/js_stream.cc | 9 +++--- src/module_wrap.cc | 18 ++++++----- src/node_crypto.cc | 78 ++++++++++++++++++++-------------------------- src/node_file.cc | 27 ++++++---------- src/node_v8.cc | 16 +++++----- src/spawn_sync.cc | 10 +++--- 7 files changed, 85 insertions(+), 103 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index f7a02e469aa..8eeaeddf830 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -749,16 +749,12 @@ Local AddrTTLToArray(Environment* env, const T* addrttls, size_t naddrttls) { auto isolate = env->isolate(); - EscapableHandleScope escapable_handle_scope(isolate); - auto context = env->context(); - Local ttls = Array::New(isolate, naddrttls); - for (size_t i = 0; i < naddrttls; i++) { - auto value = Integer::NewFromUnsigned(isolate, addrttls[i].ttl); - ttls->Set(context, i, value).Check(); - } + MaybeStackBuffer, 8> ttls(naddrttls); + for (size_t i = 0; i < naddrttls; i++) + ttls[i] = Integer::NewFromUnsigned(isolate, addrttls[i].ttl); - return escapable_handle_scope.Escape(ttls); + return Array::New(isolate, ttls.out(), naddrttls); } @@ -2039,6 +2035,7 @@ void GetServers(const FunctionCallbackInfo& args) { int r = ares_get_servers_ports(channel->cares_channel(), &servers); CHECK_EQ(r, ARES_SUCCESS); + auto cleanup = OnScopeLeave([&]() { ares_free_data(servers); }); ares_addr_port_node* cur = servers; @@ -2049,17 +2046,18 @@ void GetServers(const FunctionCallbackInfo& args) { int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); CHECK_EQ(err, 0); - Local ret = Array::New(env->isolate(), 2); - ret->Set(env->context(), 0, OneByteString(env->isolate(), ip)).Check(); - ret->Set(env->context(), - 1, - Integer::New(env->isolate(), cur->udp_port)).Check(); + Local ret[] = { + OneByteString(env->isolate(), ip), + Integer::New(env->isolate(), cur->udp_port) + }; - server_array->Set(env->context(), i, ret).Check(); + if (server_array->Set(env->context(), i, + Array::New(env->isolate(), ret, arraysize(ret))) + .IsNothing()) { + return; + } } - ares_free_data(servers); - args.GetReturnValue().Set(server_array); } diff --git a/src/js_stream.cc b/src/js_stream.cc index a67fd37dbdb..64941b1c4e4 100644 --- a/src/js_stream.cc +++ b/src/js_stream.cc @@ -116,16 +116,15 @@ int JSStream::DoWrite(WriteWrap* w, HandleScope scope(env()->isolate()); Context::Scope context_scope(env()->context()); - Local bufs_arr = Array::New(env()->isolate(), count); - Local buf; + MaybeStackBuffer, 16> bufs_arr(count); for (size_t i = 0; i < count; i++) { - buf = Buffer::Copy(env(), bufs[i].base, bufs[i].len).ToLocalChecked(); - bufs_arr->Set(env()->context(), i, buf).Check(); + bufs_arr[i] = + Buffer::Copy(env(), bufs[i].base, bufs[i].len).ToLocalChecked(); } Local argv[] = { w->object(), - bufs_arr + Array::New(env()->isolate(), bufs_arr.out(), count) }; TryCatchScope try_catch(env()); diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 0bc32f7846b..68359178f4a 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -264,11 +264,11 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { Local mod_context = obj->context_.Get(isolate); Local module = obj->module_.Get(isolate); - Local promises = Array::New(isolate, - module->GetModuleRequestsLength()); + const int module_requests_length = module->GetModuleRequestsLength(); + MaybeStackBuffer, 16> promises(module_requests_length); // call the dependency resolve callbacks - for (int i = 0; i < module->GetModuleRequestsLength(); i++) { + for (int i = 0; i < module_requests_length; i++) { Local specifier = module->GetModuleRequest(i); Utf8Value specifier_utf8(env->isolate(), specifier); std::string specifier_std(*specifier_utf8, specifier_utf8.length()); @@ -290,10 +290,11 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { Local resolve_promise = resolve_return_value.As(); obj->resolve_cache_[specifier_std].Reset(env->isolate(), resolve_promise); - promises->Set(mod_context, i, resolve_promise).Check(); + promises[i] = resolve_promise; } - args.GetReturnValue().Set(promises); + args.GetReturnValue().Set( + Array::New(isolate, promises.out(), promises.length())); } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { @@ -426,12 +427,13 @@ void ModuleWrap::GetStaticDependencySpecifiers( int count = module->GetModuleRequestsLength(); - Local specifiers = Array::New(env->isolate(), count); + MaybeStackBuffer, 16> specifiers(count); for (int i = 0; i < count; i++) - specifiers->Set(env->context(), i, module->GetModuleRequest(i)).Check(); + specifiers[i] = module->GetModuleRequest(i); - args.GetReturnValue().Set(specifiers); + args.GetReturnValue().Set( + Array::New(env->isolate(), specifiers.out(), count)); } void ModuleWrap::GetError(const FunctionCallbackInfo& args) { diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 92760fb8c85..2176fffc543 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1011,19 +1011,19 @@ static X509_STORE* NewRootCertStore() { void GetRootCertificates(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local result = Array::New(env->isolate(), arraysize(root_certs)); + Local result[arraysize(root_certs)]; for (size_t i = 0; i < arraysize(root_certs); i++) { - Local value; - if (!String::NewFromOneByte(env->isolate(), - reinterpret_cast(root_certs[i]), - NewStringType::kNormal).ToLocal(&value) || - !result->Set(env->context(), i, value).FromMaybe(false)) { + if (!String::NewFromOneByte( + env->isolate(), + reinterpret_cast(root_certs[i]), + NewStringType::kNormal).ToLocal(&result[i])) { return; } } - args.GetReturnValue().Set(result); + args.GetReturnValue().Set( + Array::New(env->isolate(), result, arraysize(root_certs))); } @@ -2138,22 +2138,22 @@ static Local X509ToObject(Environment* env, X509* cert) { StackOfASN1 eku(static_cast( X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr))); if (eku) { - Local ext_key_usage = Array::New(env->isolate()); + const int count = sk_ASN1_OBJECT_num(eku.get()); + MaybeStackBuffer, 16> ext_key_usage(count); char buf[256]; int j = 0; - for (int i = 0; i < sk_ASN1_OBJECT_num(eku.get()); i++) { + for (int i = 0; i < count; i++) { if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { - ext_key_usage->Set(context, - j++, - OneByteString(env->isolate(), buf)).Check(); + ext_key_usage[j++] = OneByteString(env->isolate(), buf); } } eku.reset(); - info->Set(context, env->ext_key_usage_string(), ext_key_usage).Check(); + info->Set(context, env->ext_key_usage_string(), + Array::New(env->isolate(), ext_key_usage.out(), count)).Check(); } if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { @@ -6799,15 +6799,8 @@ void GenerateKeyPair(const FunctionCallbackInfo& args, Local err, pubkey, privkey; job->ToResult(&err, &pubkey, &privkey); - bool (*IsNotTrue)(Maybe) = [](Maybe maybe) { - return maybe.IsNothing() || !maybe.ToChecked(); - }; - Local ret = Array::New(env->isolate(), 3); - if (IsNotTrue(ret->Set(env->context(), 0, err)) || - IsNotTrue(ret->Set(env->context(), 1, pubkey)) || - IsNotTrue(ret->Set(env->context(), 2, privkey))) - return; - args.GetReturnValue().Set(ret); + Local ret[] = { err, pubkey, privkey }; + args.GetReturnValue().Set(Array::New(env->isolate(), ret, arraysize(ret))); } void GenerateKeyPairRSA(const FunctionCallbackInfo& args) { @@ -6940,17 +6933,6 @@ void GetSSLCiphers(const FunctionCallbackInfo& args) { CHECK(ssl); STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get()); - int n = sk_SSL_CIPHER_num(ciphers); - Local arr = Array::New(env->isolate(), n); - - for (int i = 0; i < n; ++i) { - const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); - arr->Set(env->context(), - i, - OneByteString(args.GetIsolate(), - SSL_CIPHER_get_name(cipher))).Check(); - } - // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just // document them, but since there are only 5, easier to just add them manually // and not have to explain their absence in the API docs. They are lower-cased @@ -6963,13 +6945,20 @@ void GetSSLCiphers(const FunctionCallbackInfo& args) { "tls_aes_128_ccm_sha256" }; + const int n = sk_SSL_CIPHER_num(ciphers); + std::vector> arr(n + arraysize(TLS13_CIPHERS)); + + for (int i = 0; i < n; ++i) { + const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); + arr[i] = OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher)); + } + for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) { const char* name = TLS13_CIPHERS[i]; - arr->Set(env->context(), - arr->Length(), OneByteString(args.GetIsolate(), name)).Check(); + arr[n + i] = OneByteString(env->isolate(), name); } - args.GetReturnValue().Set(arr); + args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size())); } @@ -7020,22 +7009,23 @@ void GetHashes(const FunctionCallbackInfo& args) { void GetCurves(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const size_t num_curves = EC_get_builtin_curves(nullptr, 0); - Local arr = Array::New(env->isolate(), num_curves); if (num_curves) { std::vector curves(num_curves); if (EC_get_builtin_curves(curves.data(), num_curves)) { - for (size_t i = 0; i < num_curves; i++) { - arr->Set(env->context(), - i, - OneByteString(env->isolate(), - OBJ_nid2sn(curves[i].nid))).Check(); - } + std::vector> arr(num_curves); + + for (size_t i = 0; i < num_curves; i++) + arr[i] = OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid)); + + args.GetReturnValue().Set( + Array::New(env->isolate(), arr.data(), arr.size())); + return; } } - args.GetReturnValue().Set(arr); + args.GetReturnValue().Set(Array::New(env->isolate())); } diff --git a/src/node_file.cc b/src/node_file.cc index 2f66080e5ca..4d4960fc583 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -748,16 +748,11 @@ void AfterScanDirWithTypes(uv_fs_t* req) { type_v.emplace_back(Integer::New(isolate, ent.type)); } - Local result = Array::New(isolate, 2); - result->Set(env->context(), - 0, - Array::New(isolate, name_v.data(), - name_v.size())).Check(); - result->Set(env->context(), - 1, - Array::New(isolate, type_v.data(), - type_v.size())).Check(); - req_wrap->Resolve(result); + Local result[] = { + Array::New(isolate, name_v.data(), name_v.size()), + Array::New(isolate, type_v.data(), type_v.size()) + }; + req_wrap->Resolve(Array::New(isolate, result, arraysize(result))); } void Access(const FunctionCallbackInfo& args) { @@ -1611,13 +1606,11 @@ static void ReadDir(const FunctionCallbackInfo& args) { Local names = Array::New(isolate, name_v.data(), name_v.size()); if (with_types) { - Local result = Array::New(isolate, 2); - result->Set(env->context(), 0, names).Check(); - result->Set(env->context(), - 1, - Array::New(isolate, type_v.data(), - type_v.size())).Check(); - args.GetReturnValue().Set(result); + Local result[] = { + names, + Array::New(isolate, type_v.data(), type_v.size()) + }; + args.GetReturnValue().Set(Array::New(isolate, result, arraysize(result))); } else { args.GetReturnValue().Set(names); } diff --git a/src/node_v8.cc b/src/node_v8.cc index d1fb3666d1a..00bfaf796b1 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -218,19 +218,19 @@ void Initialize(Local target, // Heap space names are extracted once and exposed to JavaScript to // avoid excessive creation of heap space name Strings. HeapSpaceStatistics s; - const Local heap_spaces = Array::New(env->isolate(), - number_of_heap_spaces); + MaybeStackBuffer, 16> heap_spaces(number_of_heap_spaces); for (size_t i = 0; i < number_of_heap_spaces; i++) { env->isolate()->GetHeapSpaceStatistics(&s, i); - Local heap_space_name = String::NewFromUtf8(env->isolate(), - s.space_name(), - NewStringType::kNormal) - .ToLocalChecked(); - heap_spaces->Set(env->context(), i, heap_space_name).Check(); + heap_spaces[i] = String::NewFromUtf8(env->isolate(), + s.space_name(), + NewStringType::kNormal) + .ToLocalChecked(); } target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "kHeapSpaces"), - heap_spaces).Check(); + Array::New(env->isolate(), + heap_spaces.out(), + number_of_heap_spaces)).Check(); env->SetMethod(target, "updateHeapSpaceStatisticsArrayBuffer", diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index 3b277ad70ad..589b77f6c1e 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -721,18 +721,18 @@ Local SyncProcessRunner::BuildOutputArray() { CHECK(!stdio_pipes_.empty()); EscapableHandleScope scope(env()->isolate()); - Local context = env()->context(); - Local js_output = Array::New(env()->isolate(), stdio_count_); + MaybeStackBuffer, 8> js_output(stdio_pipes_.size()); for (uint32_t i = 0; i < stdio_pipes_.size(); i++) { SyncProcessStdioPipe* h = stdio_pipes_[i].get(); if (h != nullptr && h->writable()) - js_output->Set(context, i, h->GetOutputAsBuffer(env())).Check(); + js_output[i] = h->GetOutputAsBuffer(env()); else - js_output->Set(context, i, Null(env()->isolate())).Check(); + js_output[i] = Null(env()->isolate()); } - return scope.Escape(js_output); + return scope.Escape( + Array::New(env()->isolate(), js_output.out(), js_output.length())); } Maybe SyncProcessRunner::ParseOptions(Local js_value) { From c776a37791007e0d570355b731647e58ea5885be Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sat, 13 Jul 2019 14:04:37 +0200 Subject: [PATCH 002/192] http: end with data can cause write after end Calling end() with data while ending should trigger a write after end error. PR-URL: https://github.com/nodejs/node/pull/28666 Reviewed-By: Luigi Pinca Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat Reviewed-By: Matteo Collina Reviewed-By: Rich Trott --- lib/_http_outgoing.js | 47 +++++++++++-------- .../test-http-server-write-end-after-end.js | 27 +++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 test/parallel/test-http-server-write-end-after-end.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index c1f277da3ff..b8ee0b67689 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -641,17 +641,20 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) { return ret; }; +function writeAfterEnd(msg, callback) { + const err = new ERR_STREAM_WRITE_AFTER_END(); + const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined; + defaultTriggerAsyncIdScope(triggerAsyncId, + process.nextTick, + writeAfterEndNT, + msg, + err, + callback); +} + function write_(msg, chunk, encoding, callback, fromEnd) { if (msg.finished) { - const err = new ERR_STREAM_WRITE_AFTER_END(); - const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined; - defaultTriggerAsyncIdScope(triggerAsyncId, - process.nextTick, - writeAfterEndNT, - msg, - err, - callback); - + writeAfterEnd(msg, callback); return true; } @@ -748,17 +751,6 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { encoding = null; } - if (this.finished) { - if (typeof callback === 'function') { - if (!this.writableFinished) { - this.on('finish', callback); - } else { - callback(new ERR_STREAM_ALREADY_FINISHED('end')); - } - } - return this; - } - if (this.socket) { this.socket.cork(); } @@ -767,6 +759,12 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { throw new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk); } + + if (this.finished) { + writeAfterEnd(this, callback); + return this; + } + if (!this._header) { if (typeof chunk === 'string') this._contentLength = Buffer.byteLength(chunk, encoding); @@ -774,6 +772,15 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { this._contentLength = chunk.length; } write_(this, chunk, encoding, null, true); + } else if (this.finished) { + if (typeof callback === 'function') { + if (!this.writableFinished) { + this.on('finish', callback); + } else { + callback(new ERR_STREAM_ALREADY_FINISHED('end')); + } + } + return this; } else if (!this._header) { this._contentLength = 0; this._implicitHeader(); diff --git a/test/parallel/test-http-server-write-end-after-end.js b/test/parallel/test-http-server-write-end-after-end.js new file mode 100644 index 00000000000..37fbe062f12 --- /dev/null +++ b/test/parallel/test-http-server-write-end-after-end.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +const http = require('http'); + +const server = http.createServer(handle); + +function handle(req, res) { + res.on('error', common.mustCall((err) => { + common.expectsError({ + code: 'ERR_STREAM_WRITE_AFTER_END', + name: 'Error' + })(err); + server.close(); + })); + + res.write('hello'); + res.end(); + + setImmediate(common.mustCall(() => { + res.end('world'); + })); +} + +server.listen(0, common.mustCall(() => { + http.get(`http://localhost:${server.address().port}`); +})); From 568fdfb1658bfcaa7832759e03661f2cb958c155 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Fri, 14 Feb 2020 13:32:45 +0100 Subject: [PATCH 003/192] fs: fix WriteStream autoClose order WriteStream autoClose was implemented by manually calling .destroy() instead of using autoDestroy and callback. This caused some invariants related to order of events to be broken. Fixes: https://github.com/nodejs/node/issues/31776 PR-URL: https://github.com/nodejs/node/pull/31790 Reviewed-By: Matteo Collina Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- lib/internal/fs/streams.js | 12 +++--------- test/parallel/test-fs-read-stream-autoClose.js | 16 ++++++++++++++++ .../test-fs-write-stream-autoclose-option.js | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 test/parallel/test-fs-read-stream-autoClose.js diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js index c121273861d..5b37dca74be 100644 --- a/lib/internal/fs/streams.js +++ b/lib/internal/fs/streams.js @@ -291,7 +291,8 @@ function WriteStream(path, options) { options.decodeStrings = true; if (options.autoDestroy === undefined) { - options.autoDestroy = false; + options.autoDestroy = options.autoClose === undefined ? + true : (options.autoClose || false); } this[kFs] = options.fs || fs; @@ -337,7 +338,7 @@ function WriteStream(path, options) { this.mode = options.mode === undefined ? 0o666 : options.mode; this.start = options.start; - this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; + this.autoClose = options.autoDestroy; this.pos = undefined; this.bytesWritten = 0; this.closed = false; @@ -365,10 +366,6 @@ WriteStream.prototype._final = function(callback) { }); } - if (this.autoClose) { - this.destroy(); - } - callback(); }; @@ -419,9 +416,6 @@ WriteStream.prototype._write = function(data, encoding, cb) { } if (er) { - if (this.autoClose) { - this.destroy(); - } return cb(er); } this.bytesWritten += bytes; diff --git a/test/parallel/test-fs-read-stream-autoClose.js b/test/parallel/test-fs-read-stream-autoClose.js new file mode 100644 index 00000000000..e7989ee7911 --- /dev/null +++ b/test/parallel/test-fs-read-stream-autoClose.js @@ -0,0 +1,16 @@ +'use strict'; + +const common = require('../common'); +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const tmpdir = require('../common/tmpdir'); +const writeFile = path.join(tmpdir.path, 'write-autoClose.txt'); +tmpdir.refresh(); + +const file = fs.createWriteStream(writeFile, { autoClose: true }); + +file.on('finish', common.mustCall(() => { + assert.strictEqual(file.destroyed, false); +})); +file.end('asd'); diff --git a/test/parallel/test-fs-write-stream-autoclose-option.js b/test/parallel/test-fs-write-stream-autoclose-option.js index e39f4d615ab..8d205fbc69f 100644 --- a/test/parallel/test-fs-write-stream-autoclose-option.js +++ b/test/parallel/test-fs-write-stream-autoclose-option.js @@ -27,8 +27,8 @@ function next() { stream.end(); stream.on('finish', common.mustCall(function() { assert.strictEqual(stream.closed, false); - assert.strictEqual(stream.fd, null); stream.on('close', common.mustCall(function() { + assert.strictEqual(stream.fd, null); assert.strictEqual(stream.closed, true); process.nextTick(next2); })); @@ -51,8 +51,8 @@ function next3() { stream.end(); stream.on('finish', common.mustCall(function() { assert.strictEqual(stream.closed, false); - assert.strictEqual(stream.fd, null); stream.on('close', common.mustCall(function() { + assert.strictEqual(stream.fd, null); assert.strictEqual(stream.closed, true); })); })); From 093639614fa1cea7a1bc3493d28119a924c97bc9 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 13 Feb 2020 12:23:58 -0800 Subject: [PATCH 004/192] doc: claim ABI version 82 for Electron 10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/31778 Reviewed-By: Richard Lau Reviewed-By: Anna Henningsen Reviewed-By: Benjamin Gruenbaum Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Jiawen Geng Reviewed-By: Michaël Zasso Reviewed-By: Myles Borins Reviewed-By: Luigi Pinca --- doc/abi_version_registry.json | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/abi_version_registry.json b/doc/abi_version_registry.json index 50e7008bc75..f044d97599f 100644 --- a/doc/abi_version_registry.json +++ b/doc/abi_version_registry.json @@ -1,5 +1,6 @@ { "NODE_MODULE_VERSION": [ + { "modules": 82, "runtime": "electron", "variant": "electron", "versions": "10" }, { "modules": 81, "runtime": "node", "variant": "v8_7.9", "versions": "14.0.0-pre" }, { "modules": 80, "runtime": "electron", "variant": "electron", "versions": "9" }, { "modules": 79, "runtime": "node", "variant": "v8_7.8", "versions": "13" }, From 26e49d83323c78e89b2664b6eeaee90757d52868 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 13 Feb 2020 21:32:43 +0100 Subject: [PATCH 005/192] worker: unroll file extension regexp Refs: https://github.com/nodejs/node/pull/31662#discussion_r377016190 PR-URL: https://github.com/nodejs/node/pull/31779 Reviewed-By: Richard Lau Reviewed-By: Benjamin Gruenbaum Reviewed-By: Denys Otrishko Reviewed-By: Gus Caplan Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Yongsheng Zhang Reviewed-By: Ruben Bridgewater --- lib/internal/worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/worker.js b/lib/internal/worker.js index b690ab82deb..de626af1bef 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -104,7 +104,7 @@ class Worker extends EventEmitter { filename = path.resolve(filename); const ext = path.extname(filename); - if (!/^\.[cm]?js$/.test(ext)) { + if (ext !== '.js' && ext !== '.mjs' && ext !== '.cjs') { throw new ERR_WORKER_UNSUPPORTED_EXTENSION(ext); } } From e028ea0291b845e4bec3c7cff7319a027b8c815e Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 14 Feb 2020 22:36:11 +0800 Subject: [PATCH 006/192] doc: fix typos in doc/api/https.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/31793 Reviewed-By: Michaël Zasso Reviewed-By: James M Snell Reviewed-By: Yongsheng Zhang Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Richard Lau --- doc/api/https.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/https.md b/doc/api/https.md index 19d2053d377..ac53b6f0fde 100644 --- a/doc/api/https.md +++ b/doc/api/https.md @@ -372,7 +372,7 @@ const options = { return new Error(msg); } - // Pin the exact certificate, rather then the pub key + // Pin the exact certificate, rather than the pub key const cert256 = '25:FE:39:32:D9:63:8C:8A:FC:A1:9A:29:87:' + 'D8:3E:4C:1D:98:DB:71:E4:1A:48:03:98:EA:22:6A:BD:8B:93:16'; if (cert.fingerprint256 !== cert256) { From 4d6c861800cc6ac1dc6a0fd9d3a8b0053baec62a Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Mon, 10 Feb 2020 10:46:02 -0800 Subject: [PATCH 007/192] doc: move @Fishrock123 to a previous releaser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have not done a release in well over a year, maybe even two. I also don't really plan to do more, as Node.js releases are very tedious. PR-URL: https://github.com/nodejs/node/pull/31725 Reviewed-By: Matteo Collina Reviewed-By: Daniel Bevenius Reviewed-By: James M Snell Reviewed-By: Myles Borins Reviewed-By: Anna Henningsen Reviewed-By: Anto Aravinth Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Gus Caplan Reviewed-By: Gireesh Punathil Reviewed-By: Michael Dawson --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 07d6ad4e7ed..7f82c490ccb 100644 --- a/README.md +++ b/README.md @@ -546,8 +546,6 @@ GPG keys used to sign Node.js releases: `77984A986EBC2AA786BC0F66B01FBB92821C587A` * **James M Snell** <jasnell@keybase.io> `71DCFD284A79C3B38668286BC97EC7A07EDE3FC1` -* **Jeremiah Senkpiel** <fishrock@keybase.io> -`FD3A5288F042B6850C66B31F09FE44734EB7990E` * **Michaël Zasso** <targos@protonmail.com> `8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600` * **Myles Borins** <myles.borins@gmail.com> @@ -568,7 +566,6 @@ gpg --keyserver pool.sks-keyservers.net --recv-keys 94AE36675C464D64BAFA68DD7434 gpg --keyserver pool.sks-keyservers.net --recv-keys B9AE9905FFD7803F25714661B63B535A4C206CA9 gpg --keyserver pool.sks-keyservers.net --recv-keys 77984A986EBC2AA786BC0F66B01FBB92821C587A gpg --keyserver pool.sks-keyservers.net --recv-keys 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 -gpg --keyserver pool.sks-keyservers.net --recv-keys FD3A5288F042B6850C66B31F09FE44734EB7990E gpg --keyserver pool.sks-keyservers.net --recv-keys 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 gpg --keyserver pool.sks-keyservers.net --recv-keys C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 gpg --keyserver pool.sks-keyservers.net --recv-keys DD8F2338BAE7501E3DD5AC78C273792F7D83545D @@ -580,6 +577,8 @@ use these keys to verify a downloaded file. Other keys used to sign some previous releases: +* **Jeremiah Senkpiel** <fishrock@keybase.io> +`FD3A5288F042B6850C66B31F09FE44734EB7990E` * **Chris Dickinson** <christopher.s.dickinson@gmail.com> `9554F04D7259F04124DE6B476D5A82AC7E37093B` * **Isaac Z. Schlueter** <i@izs.me> From 928c210a611fb6b4575843708e7cb921f311ebde Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Mon, 10 Feb 2020 10:56:58 -0800 Subject: [PATCH 008/192] doc: move @Fishrock123 to TSC Emeriti MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was a good run. Almost 5 years. I haven't really been involved in the last 3+? months though, so it's time I call it and 'retire'. I think it is unlikely that I'll be on the TSC again, as node is unfortunately becoming increasingly disinteresting (& frustrating) to me. (So long and thanks for all the fish!) PR-URL: https://github.com/nodejs/node/pull/31725 Reviewed-By: Matteo Collina Reviewed-By: Daniel Bevenius Reviewed-By: James M Snell Reviewed-By: Myles Borins Reviewed-By: Anna Henningsen Reviewed-By: Anto Aravinth Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Gus Caplan Reviewed-By: Gireesh Punathil Reviewed-By: Michael Dawson --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f82c490ccb..9f876bec200 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,6 @@ For information about the governance of the Node.js project, see **Daniel Bevenius** <daniel.bevenius@gmail.com> (he/him) * [fhinkel](https://github.com/fhinkel) - **Franziska Hinkelmann** <franziska.hinkelmann@gmail.com> (she/her) -* [Fishrock123](https://github.com/Fishrock123) - -**Jeremiah Senkpiel** <fishrock123@rocketmail.com> * [gabrielschulhof](https://github.com/gabrielschulhof) - **Gabriel Schulhof** <gabriel.schulhof@intel.com> * [gireeshpunathil](https://github.com/gireeshpunathil) - @@ -200,6 +198,8 @@ For information about the governance of the Node.js project, see **Chris Dickinson** <christopher.s.dickinson@gmail.com> * [evanlucas](https://github.com/evanlucas) - **Evan Lucas** <evanlucas@me.com> (he/him) +* [Fishrock123](https://github.com/Fishrock123) - +**Jeremiah Senkpiel** <fishrock123@rocketmail.com> * [gibfahn](https://github.com/gibfahn) - **Gibson Fahnestock** <gibfahn@gmail.com> (he/him) * [indutny](https://github.com/indutny) - From 30e6049c75590dec8d5edbf033edbc20e79efcb7 Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Mon, 10 Feb 2020 10:59:27 -0800 Subject: [PATCH 009/192] doc: pronouns for @Fishrock123 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit might as well while I'm at it feels a bit weird being the first person on this list with '/they' but I guess someone's gota do it PR-URL: https://github.com/nodejs/node/pull/31725 Reviewed-By: Matteo Collina Reviewed-By: Daniel Bevenius Reviewed-By: James M Snell Reviewed-By: Myles Borins Reviewed-By: Anna Henningsen Reviewed-By: Anto Aravinth Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Gus Caplan Reviewed-By: Gireesh Punathil Reviewed-By: Michael Dawson --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f876bec200..c71cbba4090 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ For information about the governance of the Node.js project, see * [evanlucas](https://github.com/evanlucas) - **Evan Lucas** <evanlucas@me.com> (he/him) * [Fishrock123](https://github.com/Fishrock123) - -**Jeremiah Senkpiel** <fishrock123@rocketmail.com> +**Jeremiah Senkpiel** <fishrock123@rocketmail.com> (he/they) * [gibfahn](https://github.com/gibfahn) - **Gibson Fahnestock** <gibfahn@gmail.com> (he/him) * [indutny](https://github.com/indutny) - @@ -292,7 +292,7 @@ For information about the governance of the Node.js project, see * [fhinkel](https://github.com/fhinkel) - **Franziska Hinkelmann** <franziska.hinkelmann@gmail.com> (she/her) * [Fishrock123](https://github.com/Fishrock123) - -**Jeremiah Senkpiel** <fishrock123@rocketmail.com> +**Jeremiah Senkpiel** <fishrock123@rocketmail.com> (he/they) * [gabrielschulhof](https://github.com/gabrielschulhof) - **Gabriel Schulhof** <gabriel.schulhof@intel.com> * [gdams](https://github.com/gdams) - From 4c746a6cfda980c1cd0de6246781c0083d9e416c Mon Sep 17 00:00:00 2001 From: Gireesh Punathil Date: Thu, 13 Feb 2020 21:58:27 +0530 Subject: [PATCH 010/192] doc: move gireeshpunathil to TSC emeritus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/31770 Reviewed-By: James M Snell Reviewed-By: Myles Borins Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c71cbba4090..a06c51f22a4 100644 --- a/README.md +++ b/README.md @@ -167,8 +167,6 @@ For information about the governance of the Node.js project, see **Franziska Hinkelmann** <franziska.hinkelmann@gmail.com> (she/her) * [gabrielschulhof](https://github.com/gabrielschulhof) - **Gabriel Schulhof** <gabriel.schulhof@intel.com> -* [gireeshpunathil](https://github.com/gireeshpunathil) - -**Gireesh Punathil** <gpunathi@in.ibm.com> (he/him) * [jasnell](https://github.com/jasnell) - **James M Snell** <jasnell@gmail.com> (he/him) * [joyeecheung](https://github.com/joyeecheung) - @@ -202,6 +200,8 @@ For information about the governance of the Node.js project, see **Jeremiah Senkpiel** <fishrock123@rocketmail.com> (he/they) * [gibfahn](https://github.com/gibfahn) - **Gibson Fahnestock** <gibfahn@gmail.com> (he/him) +* [gireeshpunathil](https://github.com/gireeshpunathil) - +**Gireesh Punathil** <gpunathi@in.ibm.com> (he/him) * [indutny](https://github.com/indutny) - **Fedor Indutny** <fedor.indutny@gmail.com> * [isaacs](https://github.com/isaacs) - From 5bef2ccf20cceda7975f8bce860e0f60595482fc Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 12 Feb 2020 12:21:34 -0800 Subject: [PATCH 011/192] test: add known issue test for sync writable callback If the write callbacks are invoked synchronously with an error, onwriteError would cause the error event to be emitted synchronously, making it impossible to attach an error handler after the call that triggered it. PR-URL: https://github.com/nodejs/node/pull/31756 Refs: https://github.com/nodejs/quic/commit/b0d469c69c49c9186c1a581a7cebce4c5d398947 Refs: https://github.com/nodejs/quic/pull/341 Reviewed-By: Robert Nagy Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen Reviewed-By: Minwoo Jung --- .../test-stream-writable-sync-error.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/known_issues/test-stream-writable-sync-error.js diff --git a/test/known_issues/test-stream-writable-sync-error.js b/test/known_issues/test-stream-writable-sync-error.js new file mode 100644 index 00000000000..202cf7bf23e --- /dev/null +++ b/test/known_issues/test-stream-writable-sync-error.js @@ -0,0 +1,44 @@ +'use strict'; +const common = require('../common'); + +// Tests for the regression in _stream_writable discussed in +// https://github.com/nodejs/node/pull/31756 + +// Specifically, when a write callback is invoked synchronously +// with an error, and autoDestroy is not being used, the error +// should still be emitted on nextTick. + +const { Writable } = require('stream'); + +class MyStream extends Writable { + #cb = undefined; + + constructor() { + super({ autoDestroy: false }); + } + + _write(_, __, cb) { + this.#cb = cb; + } + + close() { + // Synchronously invoke the callback with an error. + this.#cb(new Error('foo')); + } +} + +const stream = new MyStream(); + +const mustError = common.mustCall(2); + +stream.write('test', () => {}); + +// Both error callbacks should be invoked. + +stream.on('error', mustError); + +stream.close(); + +// Without the fix in #31756, the error handler +// added after the call to close will not be invoked. +stream.on('error', mustError); From 85c6fcd1cd11ec6ee22891e48a67cc97f60880e8 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sat, 15 Feb 2020 01:34:35 +0100 Subject: [PATCH 012/192] stream: avoid writing to writable A remainder from a previous refactoring. Refs: https://github.com/nodejs/node/pull/31197 PR-URL: https://github.com/nodejs/node/pull/31805 Reviewed-By: Luigi Pinca Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- lib/_stream_writable.js | 3 +-- test/parallel/test-stream-writable-ended-state.js | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index 2180b091b7b..fe8f273a022 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -687,7 +687,6 @@ function endWritable(stream, state, cb) { onFinished(stream, state, cb); } state.ended = true; - stream.writable = false; } function onCorkedFinish(corkReq, state, err) { @@ -741,7 +740,7 @@ ObjectDefineProperties(Writable.prototype, { get() { const w = this._writableState; if (!w) return false; - if (w.writable !== undefined) return w.writable; + if (w.writable !== undefined) return w.writable && !w.ended; return Boolean(!w.destroyed && !w.errored && !w.ending); }, set(val) { diff --git a/test/parallel/test-stream-writable-ended-state.js b/test/parallel/test-stream-writable-ended-state.js index e5fa624c12a..2c40c62a9ee 100644 --- a/test/parallel/test-stream-writable-ended-state.js +++ b/test/parallel/test-stream-writable-ended-state.js @@ -9,17 +9,24 @@ const writable = new stream.Writable(); writable._write = (chunk, encoding, cb) => { assert.strictEqual(writable._writableState.ended, false); + assert.strictEqual(writable._writableState.writable, undefined); assert.strictEqual(writable.writableEnded, false); cb(); }; assert.strictEqual(writable._writableState.ended, false); +assert.strictEqual(writable._writableState.writable, undefined); +assert.strictEqual(writable.writable, true); assert.strictEqual(writable.writableEnded, false); writable.end('testing ended state', common.mustCall(() => { assert.strictEqual(writable._writableState.ended, true); + assert.strictEqual(writable._writableState.writable, undefined); + assert.strictEqual(writable.writable, false); assert.strictEqual(writable.writableEnded, true); })); assert.strictEqual(writable._writableState.ended, true); +assert.strictEqual(writable._writableState.writable, undefined); +assert.strictEqual(writable.writable, false); assert.strictEqual(writable.writableEnded, true); From 99428e0858bb3cce820190770ca8217460d95c80 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 14:57:12 -1000 Subject: [PATCH 013/192] doc: reword possessive form of Node.js in debugger.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throughout the docs, we sometimes write the possessive of _Node.js_ as _Node.js'_ and other times as _Node.js's_. The former conforms with some generally accepted style guides (e.g., Associated Press Stylebook) while the latter complies with others (e.g., Chicago Manual of Style). Since there is no clear authoritative answer as to which form is correct, and since (at least to me) both are visually jarring and sometimes cause a pause to understand, I'd like to reword things to eliminate the possessive form where possible. This is one of those examples. PR-URL: https://github.com/nodejs/node/pull/31748 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- doc/api/debugger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/debugger.md b/doc/api/debugger.md index e43cfa8a163..2821cd605c5 100644 --- a/doc/api/debugger.md +++ b/doc/api/debugger.md @@ -23,7 +23,7 @@ Break on start in myscript.js:1 debug> ``` -Node.js's debugger client is not a full-featured debugger, but simple step and +The Node.js debugger client is not a full-featured debugger, but simple step and inspection are possible. Inserting the statement `debugger;` into the source code of a script will From d40c648e884bd64028fa0a67122a0af87c60f467 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 14:59:59 -1000 Subject: [PATCH 014/192] doc: reword possessive form of Node.js in process.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throughout the docs, we sometimes write the possessive of _Node.js_ as _Node.js'_ and other times as _Node.js's_. The former conforms with some generally accepted style guides (e.g., Associated Press Stylebook) while the latter complies with others (e.g., Chicago Manual of Style). Since there is no clear authoritative answer as to which form is correct, and since (at least to me) both are visually jarring and sometimes cause a pause to understand, I'd like to reword things to eliminate the possessive form where possible. This is one of those examples. PR-URL: https://github.com/nodejs/node/pull/31748 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- doc/api/process.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/process.md b/doc/api/process.md index 3ccd9fc776d..c5c99f2a707 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -833,7 +833,7 @@ added: v0.7.2 * {number} -The port used by Node.js's debugger when enabled. +The port used by the Node.js debugger when enabled. ```js process.debugPort = 5858; @@ -2486,11 +2486,11 @@ cases: handler. * `2`: Unused (reserved by Bash for builtin misuse) * `3` **Internal JavaScript Parse Error**: The JavaScript source code - internal in Node.js's bootstrapping process caused a parse error. This + internal in the Node.js bootstrapping process caused a parse error. This is extremely rare, and generally can only happen during development of Node.js itself. * `4` **Internal JavaScript Evaluation Failure**: The JavaScript - source code internal in Node.js's bootstrapping process failed to + source code internal in the Node.js bootstrapping process failed to return a function value when evaluated. This is extremely rare, and generally can only happen during development of Node.js itself. * `5` **Fatal Error**: There was a fatal unrecoverable error in V8. @@ -2509,7 +2509,7 @@ cases: * `9` **Invalid Argument**: Either an unknown option was specified, or an option requiring a value was provided without a value. * `10` **Internal JavaScript Run-Time Failure**: The JavaScript - source code internal in Node.js's bootstrapping process threw an error + source code internal in the Node.js bootstrapping process threw an error when the bootstrapping function was called. This is extremely rare, and generally can only happen during development of Node.js itself. * `12` **Invalid Debug Argument**: The `--inspect` and/or `--inspect-brk` From d25fcb0c201286d18c07653347258ce02951ddf8 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 15:03:31 -1000 Subject: [PATCH 015/192] doc: reword possessive form of Node.js in http.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throughout the docs, we sometimes write the possessive of _Node.js_ as _Node.js'_ and other times as _Node.js's_. The former conforms with some generally accepted style guides (e.g., Associated Press Stylebook) while the latter complies with others (e.g., Chicago Manual of Style). Since there is no clear authoritative answer as to which form is correct, and since (at least to me) both are visually jarring and sometimes cause a pause to understand, I'd like to reword things to eliminate the possessive form where possible. This is one of those examples. PR-URL: https://github.com/nodejs/node/pull/31748 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- doc/api/http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/http.md b/doc/api/http.md index beba834bd72..cf1e5d76bf4 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -25,7 +25,7 @@ HTTP message headers are represented by an object like this: Keys are lowercased. Values are not modified. -In order to support the full spectrum of possible HTTP applications, Node.js's +In order to support the full spectrum of possible HTTP applications, the Node.js HTTP API is very low-level. It deals with stream handling and message parsing only. It parses a message into headers and body but it does not parse the actual headers or the body. From eb0ade10a7af855ecdaa822dea028d661664cd43 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 15:07:06 -1000 Subject: [PATCH 016/192] doc: reword possessive form of Node.js in adding-new-napi-api.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throughout the docs, we sometimes write the possessive of _Node.js_ as _Node.js'_ and other times as _Node.js's_. The former conforms with some generally accepted style guides (e.g., Associated Press Stylebook) while the latter complies with others (e.g., Chicago Manual of Style). Since there is no clear authoritative answer as to which form is correct, and since (at least to me) both are visually jarring and sometimes cause a pause to understand, I'd like to reword things to eliminate the possessive form where possible. This is one of those examples. PR-URL: https://github.com/nodejs/node/pull/31748 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- doc/guides/adding-new-napi-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guides/adding-new-napi-api.md b/doc/guides/adding-new-napi-api.md index dc8d9dda233..825b4877783 100644 --- a/doc/guides/adding-new-napi-api.md +++ b/doc/guides/adding-new-napi-api.md @@ -1,6 +1,6 @@ # Contributing a new API to N-API -N-API is Node.js's next generation ABI-stable API for native modules. +N-API is the next-generation ABI-stable API for native modules. While improving the API surface is encouraged and welcomed, the following are a set of principles and guidelines to keep in mind while adding a new N-API API. From fab3eff2e53eb9bfbb890e5ac45523ae68ae95e5 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 14 Feb 2020 22:25:57 +0100 Subject: [PATCH 017/192] src: inform callback scopes about exceptions in HTTP parser Refs: https://github.com/nodejs/node/commit/4aca277f16b8649b5fc21d41f340fad0a47c2e61 Refs: https://github.com/nodejs/node/pull/30236 Fixes: https://github.com/nodejs/node/issues/31796 PR-URL: https://github.com/nodejs/node/pull/31801 Reviewed-By: James M Snell Reviewed-By: David Carlier --- src/node_http_parser.cc | 2 ++ ...est-http-uncaught-from-request-callback.js | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 test/parallel/test-http-uncaught-from-request-callback.js diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index a8c48999c57..40ece82b625 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -330,6 +330,7 @@ class Parser : public AsyncWrap, public StreamListener { this, InternalCallbackScope::kSkipTaskQueues); head_response = cb.As()->Call( env()->context(), object(), arraysize(argv), argv); + if (head_response.IsEmpty()) callback_scope.MarkAsFailed(); } int64_t val; @@ -401,6 +402,7 @@ class Parser : public AsyncWrap, public StreamListener { InternalCallbackScope callback_scope( this, InternalCallbackScope::kSkipTaskQueues); r = cb.As()->Call(env()->context(), object(), 0, nullptr); + if (r.IsEmpty()) callback_scope.MarkAsFailed(); } if (r.IsEmpty()) { diff --git a/test/parallel/test-http-uncaught-from-request-callback.js b/test/parallel/test-http-uncaught-from-request-callback.js new file mode 100644 index 00000000000..5c759586178 --- /dev/null +++ b/test/parallel/test-http-uncaught-from-request-callback.js @@ -0,0 +1,29 @@ +'use strict'; +const common = require('../common'); +const asyncHooks = require('async_hooks'); +const http = require('http'); + +// Regression test for https://github.com/nodejs/node/issues/31796 + +asyncHooks.createHook({ + after: () => {} +}).enable(); + + +process.once('uncaughtException', common.mustCall(() => { + server.close(); +})); + +const server = http.createServer(common.mustCall((request, response) => { + response.writeHead(200, { 'Content-Type': 'text/plain' }); + response.end(); +})); + +server.listen(0, common.mustCall(() => { + http.get({ + host: 'localhost', + port: server.address().port + }, common.mustCall(() => { + throw new Error('whoah'); + })); +})); From 794bfacb26c4059c2eab7efb475489b7010054c3 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 19:14:57 -0800 Subject: [PATCH 018/192] test: remove common.PORT from test-net-timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch test-net-timeout from common.PORT to a port assigned by the operating system. PR-URL: https://github.com/nodejs/node/pull/31749 Reviewed-By: Denys Otrishko Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater --- test/pummel/test-net-timeout.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/pummel/test-net-timeout.js b/test/pummel/test-net-timeout.js index 59a8d50f796..5b9f2a01b38 100644 --- a/test/pummel/test-net-timeout.js +++ b/test/pummel/test-net-timeout.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const net = require('net'); @@ -54,10 +54,11 @@ const echo_server = net.createServer((socket) => { }); }); -echo_server.listen(common.PORT, () => { - console.log(`server listening at ${common.PORT}`); +echo_server.listen(0, () => { + const port = echo_server.address().port; + console.log(`server listening at ${port}`); - const client = net.createConnection(common.PORT); + const client = net.createConnection(port); client.setEncoding('UTF8'); client.setTimeout(0); // Disable the timeout for client client.on('connect', () => { From 76fd17a7dff6b89b8851044b17125a88a3043b4e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 19:18:27 -0800 Subject: [PATCH 019/192] test: remove common.PORT from test-net-throttle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch test-net-throttle from common.PORT to a port assigned by the operating system. PR-URL: https://github.com/nodejs/node/pull/31749 Reviewed-By: Denys Otrishko Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater --- test/pummel/test-net-throttle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/pummel/test-net-throttle.js b/test/pummel/test-net-throttle.js index 190c242d6e1..9708d69f962 100644 --- a/test/pummel/test-net-throttle.js +++ b/test/pummel/test-net-throttle.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const net = require('net'); @@ -32,8 +32,6 @@ let npauses = 0; console.log('build big string'); const body = 'C'.repeat(N); -console.log(`start server on port ${common.PORT}`); - const server = net.createServer((connection) => { connection.write(body.slice(0, part_N)); connection.write(body.slice(part_N, 2 * part_N)); @@ -44,9 +42,11 @@ const server = net.createServer((connection) => { connection.end(); }); -server.listen(common.PORT, () => { +server.listen(0, () => { + const port = server.address().port; + console.log(`server started on port ${port}`); let paused = false; - const client = net.createConnection(common.PORT); + const client = net.createConnection(port); client.setEncoding('ascii'); client.on('data', (d) => { chars_recved += d.length; From a092886b1d29d17c94683348430e062cf7ae2fc9 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 19:21:12 -0800 Subject: [PATCH 020/192] test: remove common.PORT from test-tls-server-large-request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch test-tls-server-large-request from common.PORT to a port assigned by the operating system. PR-URL: https://github.com/nodejs/node/pull/31749 Reviewed-By: Denys Otrishko Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater --- test/pummel/test-tls-server-large-request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pummel/test-tls-server-large-request.js b/test/pummel/test-tls-server-large-request.js index 5d3a0615bad..7537ca813af 100644 --- a/test/pummel/test-tls-server-large-request.js +++ b/test/pummel/test-tls-server-large-request.js @@ -59,9 +59,9 @@ const server = tls.Server(options, common.mustCall(function(socket) { socket.pipe(mediator); })); -server.listen(common.PORT, common.mustCall(function() { +server.listen(0, common.mustCall(() => { const client1 = tls.connect({ - port: common.PORT, + port: server.address().port, rejectUnauthorized: false }, common.mustCall(function() { client1.end(request); From af19f4116c5c3ce0fc7b18b62ffb05e8138bf74b Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 19:23:51 -0800 Subject: [PATCH 021/192] test: remove common.PORT from test-net-pause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch test-net-pause from common.PORT to a port assigned by the operating system. PR-URL: https://github.com/nodejs/node/pull/31749 Reviewed-By: Denys Otrishko Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater --- test/pummel/test-net-pause.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pummel/test-net-pause.js b/test/pummel/test-net-pause.js index 512d833ae75..76237c17214 100644 --- a/test/pummel/test-net-pause.js +++ b/test/pummel/test-net-pause.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const net = require('net'); @@ -43,7 +43,7 @@ const server = net.createServer((connection) => { }); server.on('listening', () => { - const client = net.createConnection(common.PORT); + const client = net.createConnection(server.address().port); client.setEncoding('ascii'); client.on('data', (d) => { console.log(d); @@ -83,7 +83,7 @@ server.on('listening', () => { client.end(); }); }); -server.listen(common.PORT); +server.listen(0); process.on('exit', () => { assert.strictEqual(recv.length, N); From 58de9b46b80fd3e48f5faf9db8101930a6493fc4 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 3 Feb 2020 13:31:12 +0200 Subject: [PATCH 022/192] module: package "exports" error refinements PR-URL: https://github.com/nodejs/node/pull/31625 Reviewed-By: Jan Krems --- doc/api/errors.md | 20 ++ doc/api/esm.md | 75 ++--- lib/internal/errors.js | 29 +- lib/internal/modules/cjs/loader.js | 104 ++++--- src/module_wrap.cc | 258 ++++++++++-------- src/node_errors.h | 4 +- test/es-module/test-esm-exports.mjs | 54 ++-- .../node_modules/pkgexports/package.json | 4 + .../pkgexports/resolve-self-invalid.js | 1 + .../pkgexports/resolve-self-invalid.mjs | 1 + 10 files changed, 331 insertions(+), 219 deletions(-) create mode 100644 test/fixtures/node_modules/pkgexports/resolve-self-invalid.js create mode 100644 test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs diff --git a/doc/api/errors.md b/doc/api/errors.md index b186275807a..8a1af870b1d 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1319,6 +1319,12 @@ An invalid HTTP token was supplied. An IP address is not valid. + +### `ERR_INVALID_MODULE_SPECIFIER` + +The imported module string is an invalid URL, package name, or package subpath +specifier. + ### `ERR_INVALID_OPT_VALUE` @@ -1334,6 +1340,12 @@ An invalid or unknown file encoding was passed. An invalid `package.json` file was found which failed parsing. + +### `ERR_INVALID_PACKAGE_TARGET` + +The `package.json` [exports][] field contains an invalid target mapping value +for the attempted module resolution. + ### `ERR_INVALID_PERFORMANCE_MARK` @@ -1640,6 +1652,13 @@ A non-context-aware native addon was loaded in a process that disallows them. A given value is out of the accepted range. + +### `ERR_PACKAGE_PATH_NOT_EXPORTED` + +The `package.json` [exports][] field does not export the requested subpath. +Because exports are encapsulated, private internal modules that are not exported +cannot be imported through the package resolution, unless using an absolute URL. + ### `ERR_REQUIRE_ESM` @@ -2505,6 +2524,7 @@ such as `process.stdout.on('data')`. [crypto digest algorithm]: crypto.html#crypto_crypto_gethashes [domains]: domain.html [event emitter-based]: events.html#events_class_eventemitter +[exports]: esm.html#esm_package_exports [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor [policy]: policy.html [stream-based]: stream.html diff --git a/doc/api/esm.md b/doc/api/esm.md index f1378b59ad1..7abaab87e0f 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -1391,6 +1391,17 @@ of these top-level routines unless stated otherwise. _defaultEnv_ is the conditional environment name priority array, `["node", "import"]`. +The resolver can throw the following errors: +* _Invalid Module Specifier_: Module specifier is an invalid URL, package name + or package subpath specifier. +* _Invalid Package Configuration_: package.json configuration is invalid or + contains an invalid configuration. +* _Invalid Package Target_: Package exports define a target module within the + package that is an invalid type or string target. +* _Package Path Not Exported_: Package exports do not define or permit a target + subpath in the package for the given module. +* _Module Not Found_: The package or module requested does not exist. +
Resolver algorithm specification @@ -1401,7 +1412,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Set _resolvedURL_ to the result of parsing and reserializing > _specifier_ as a URL. > 1. Otherwise, if _specifier_ starts with _"/"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then > 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to > _parentURL_. @@ -1411,7 +1422,7 @@ _defaultEnv_ is the conditional environment name priority array, > **PACKAGE_RESOLVE**(_specifier_, _parentURL_). > 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_ > and _"%5C"_ respectively), then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. If _resolvedURL_ does not end with a trailing _"/"_ and the file at > _resolvedURL_ does not exist, then > 1. Throw a _Module Not Found_ error. @@ -1425,14 +1436,14 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Let _packageName_ be *undefined*. > 1. Let _packageSubpath_ be *undefined*. > 1. If _packageSpecifier_ is an empty string, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Otherwise, > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Set _packageName_ to the substring of _packageSpecifier_ > until the second _"/"_ separator or the end of the string. > 1. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Let _packageSubpath_ be _undefined_. > 1. If the length of _packageSpecifier_ is greater than the length of > _packageName_, then @@ -1440,7 +1451,7 @@ _defaultEnv_ is the conditional environment name priority array, > _packageSpecifier_ from the position at the length of _packageName_. > 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent > encoded strings for _"/"_ or _"\\"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Set _selfUrl_ to the result of > **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_). > 1. If _selfUrl_ isn't empty, return _selfUrl_. @@ -1497,7 +1508,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Throw a _Module Not Found_ error. > 1. If _pjson.exports_ is not **null** or **undefined**, then > 1. If _exports_ is an Object with both a key starting with _"."_ and a key -> not starting with _"."_, throw an "Invalid Package Configuration" error. +> not starting with _"."_, throw an _Invalid Package Configuration_ error. > 1. If _pjson.exports_ is a String or Array, or an Object containing no > keys starting with _"."_, then > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, @@ -1506,6 +1517,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Let _mainExport_ be the _"."_ property in _pjson.exports_. > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, > _mainExport_, _""_). +> 1. Throw a _Package Path Not Exported_ error. > 1. If _pjson.main_ is a String, then > 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and > _pjson.main_. @@ -1520,7 +1532,7 @@ _defaultEnv_ is the conditional environment name priority array, **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_) > 1. If _exports_ is an Object with both a key starting with _"."_ and a key not -> starting with _"."_, throw an "Invalid Package Configuration" error. +> starting with _"."_, throw an _Invalid Package Configuration_ error. > 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then > 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_. > 1. If _packagePath_ is a key of _exports_, then @@ -1536,43 +1548,44 @@ _defaultEnv_ is the conditional environment name priority array, > of the length of _directory_. > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, > _subpath_, _defaultEnv_). -> 1. Throw a _Module Not Found_ error. +> 1. Throw a _Package Path Not Exported_ error. **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_) -> 1. If _target_ is a String, then -> 1. If _target_ does not start with _"./"_, throw a _Module Not Found_ -> error. -> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, -> throw a _Module Not Found_ error. -> 1. If _target_ or _subpath_ contain any _"node_modules"_ segments including -> _"node_modules"_ percent-encoding, throw a _Module Not Found_ error. +> 1.If _target_ is a String, then +> 1. If _target_ does not start with _"./"_ or contains any _"node_modules"_ +> segments including _"node_modules"_ percent-encoding, throw an +> _Invalid Package Target_ error. > 1. Let _resolvedTarget_ be the URL resolution of the concatenation of > _packageURL_ and _target_. -> 1. If _resolvedTarget_ is contained in _packageURL_, then -> 1. Let _resolved_ be the URL resolution of the concatenation of -> _subpath_ and _resolvedTarget_. -> 1. If _resolved_ is contained in _resolvedTarget_, then -> 1. Return _resolved_. +> 1. If _resolvedTarget_ is not contained in _packageURL_, throw an +> _Invalid Package Target_ error. +> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, +> throw an _Invalid Module Specifier_ error. +> 1. Let _resolved_ be the URL resolution of the concatenation of +> _subpath_ and _resolvedTarget_. +> 1. If _resolved_ is not contained in _resolvedTarget_, throw an +> _Invalid Module Specifier_ error. +> 1. Return _resolved_. > 1. Otherwise, if _target_ is a non-null Object, then > 1. If _exports_ contains any index property keys, as defined in ECMA-262 > [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error. > 1. For each property _p_ of _target_, in object insertion order as, > 1. If _env_ contains an entry for _p_, then > 1. Let _targetValue_ be the value of the _p_ property in _target_. -> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE** -> (_packageURL_, _targetValue_, _subpath_, _env_). -> 1. Assert: _resolved_ is a String. -> 1. Return _resolved_. +> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**( +> _packageURL_, _targetValue_, _subpath_, _env_), continuing the +> loop on any _Package Path Not Exported_ error. +> 1. Throw a _Package Path Not Exported_ error. > 1. Otherwise, if _target_ is an Array, then +> 1. If _target.length is zero, throw an _Invalid Package Target_ error. > 1. For each item _targetValue_ in _target_, do > 1. If _targetValue_ is an Array, continue the loop. -> 1. Let _resolved_ be the result of -> **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_, -> _subpath_, _env_), continuing the loop on abrupt completion. -> 1. Assert: _resolved_ is a String. -> 1. Return _resolved_. -> 1. Throw a _Module Not Found_ error. +> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, +> _targetValue_, _subpath_, _env_), continuing the loop on any +> _Package Path Not Exported_ or _Invalid Package Target_ error. +> 1. Throw the last fallback resolution error. +> 1. Otherwise throw an _Invalid Package Target_ error. **ESM_FORMAT**(_url_) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 392a297070d..e3cb83268e2 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -13,16 +13,20 @@ const { ArrayIsArray, Error, + JSONStringify, Map, MathAbs, NumberIsInteger, ObjectDefineProperty, ObjectKeys, + StringPrototypeSlice, Symbol, SymbolFor, WeakMap, } = primordials; +const sep = process.platform === 'win32' ? '\\' : '/'; + const messages = new Map(); const codes = {}; @@ -1073,6 +1077,11 @@ E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError); E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError); E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError); E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError); +E('ERR_INVALID_MODULE_SPECIFIER', (pkgPath, subpath) => { + assert(subpath !== '.'); + return `Package subpath '${subpath}' is not a valid module request for the ` + + `"exports" resolution of ${pkgPath}${sep}package.json`; +}, TypeError); E('ERR_INVALID_OPT_VALUE', (name, value) => `The value "${String(value)}" is invalid for option "${name}"`, TypeError, @@ -1080,7 +1089,17 @@ E('ERR_INVALID_OPT_VALUE', (name, value) => E('ERR_INVALID_OPT_VALUE_ENCODING', 'The value "%s" is invalid for option "encoding"', TypeError); E('ERR_INVALID_PACKAGE_CONFIG', - 'Invalid package config for \'%s\', %s', Error); + `Invalid package config %s${sep}package.json, %s`, Error); +E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => { + if (key === '.') { + return `Invalid "exports" main target ${JSONStringify(target)} defined ` + + `in the package config ${pkgPath}${sep}package.json`; + } else { + return `Invalid "exports" target ${JSONStringify(target)} defined for '${ + StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` + + `package config ${pkgPath}${sep}package.json`; + } +}, Error); E('ERR_INVALID_PERFORMANCE_MARK', 'The "%s" performance mark has not been set', Error); E('ERR_INVALID_PROTOCOL', @@ -1225,6 +1244,14 @@ E('ERR_OUT_OF_RANGE', msg += ` It must be ${range}. Received ${received}`; return msg; }, RangeError); +E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath) => { + if (subpath === '.') { + return `No "exports" main resolved in ${pkgPath}${sep}package.json`; + } else { + return `Package subpath '${subpath}' is not defined by "exports" in ${ + pkgPath}${sep}package.json`; + } +}, Error); E('ERR_REQUIRE_ESM', (filename, parentPath = null, packageJsonPath = null) => { let msg = `Must use import to load ES Module: ${filename}`; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index a5ea81b55f1..c0c565f1230 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -86,6 +86,9 @@ const { ERR_INVALID_ARG_VALUE, ERR_INVALID_OPT_VALUE, ERR_INVALID_PACKAGE_CONFIG, + ERR_INVALID_PACKAGE_TARGET, + ERR_INVALID_MODULE_SPECIFIER, + ERR_PACKAGE_PATH_NOT_EXPORTED, ERR_REQUIRE_ESM } = require('internal/errors').codes; const { validateString } = require('internal/validators'); @@ -502,13 +505,9 @@ function applyExports(basePath, expansion) { if (ObjectPrototypeHasOwnProperty(pkgExports, mappingKey)) { const mapping = pkgExports[mappingKey]; return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '', - basePath, mappingKey); + mappingKey); } - // Fallback to CJS main lookup when no main export is defined - if (mappingKey === '.') - return basePath; - let dirMatch = ''; for (const candidateKey of ObjectKeys(pkgExports)) { if (candidateKey[candidateKey.length - 1] !== '/') continue; @@ -522,18 +521,11 @@ function applyExports(basePath, expansion) { const mapping = pkgExports[dirMatch]; const subpath = StringPrototypeSlice(mappingKey, dirMatch.length); return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, - subpath, basePath, mappingKey); + subpath, mappingKey); } } - // Fallback to CJS main lookup when no main export is defined - if (mappingKey === '.') - return basePath; - // eslint-disable-next-line no-restricted-syntax - const e = new Error(`Package exports for '${basePath}' do not define ` + - `a '${mappingKey}' subpath`); - e.code = 'MODULE_NOT_FOUND'; - throw e; + throw new ERR_PACKAGE_PATH_NOT_EXPORTED(basePath, mappingKey); } // This only applies to requests of a specific form: @@ -568,39 +560,53 @@ function isArrayIndex(p) { return n >= 0 && n < (2 ** 32) - 1; } -function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { +function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { if (typeof target === 'string') { - if (target.startsWith('./') && - (subpath.length === 0 || target.endsWith('/'))) { - const resolvedTarget = new URL(target, pkgPath); - const pkgPathPath = pkgPath.pathname; - const resolvedTargetPath = resolvedTarget.pathname; - if (StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) && + let resolvedTarget, resolvedTargetPath; + const pkgPathPath = baseUrl.pathname; + if (StringPrototypeStartsWith(target, './')) { + resolvedTarget = new URL(target, baseUrl); + resolvedTargetPath = resolvedTarget.pathname; + if (!StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) || StringPrototypeIndexOf(resolvedTargetPath, '/node_modules/', - pkgPathPath.length - 1) === -1) { - const resolved = new URL(subpath, resolvedTarget); - const resolvedPath = resolved.pathname; - if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) && - StringPrototypeIndexOf(resolvedPath, '/node_modules/', - pkgPathPath.length - 1) === -1) { - return fileURLToPath(resolved); - } - } + pkgPathPath.length - 1) !== -1) + resolvedTarget = undefined; } + if (subpath.length > 0 && target[target.length - 1] !== '/') + resolvedTarget = undefined; + if (resolvedTarget === undefined) + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey, subpath, target); + const resolved = new URL(subpath, resolvedTarget); + const resolvedPath = resolved.pathname; + if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) && + StringPrototypeIndexOf(resolvedPath, '/node_modules/', + pkgPathPath.length - 1) === -1) { + return fileURLToPath(resolved); + } + throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey); } else if (ArrayIsArray(target)) { + if (target.length === 0) + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey, subpath, target); for (const targetValue of target) { - if (ArrayIsArray(targetValue)) continue; try { - return resolveExportsTarget(pkgPath, targetValue, subpath, basePath, - mappingKey); + return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && + e.code !== 'ERR_INVALID_PACKAGE_TARGET') + throw e; } } + // Throw last fallback error + resolveExportsTarget(baseUrl, target[target.length - 1], subpath, + mappingKey); + assert(false); } else if (typeof target === 'object' && target !== null) { const keys = ObjectKeys(target); if (keys.some(isArrayIndex)) { - throw new ERR_INVALID_PACKAGE_CONFIG(basePath, '"exports" cannot ' + + throw new ERR_INVALID_PACKAGE_CONFIG(baseUrl, '"exports" cannot ' + 'contain numeric property keys.'); } for (const p of keys) { @@ -609,34 +615,26 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { case 'require': try { emitExperimentalWarning('Conditional exports'); - const result = resolveExportsTarget(pkgPath, target[p], subpath, - basePath, mappingKey); - return result; + return resolveExportsTarget(baseUrl, target[p], subpath, + mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e; } break; case 'default': try { - return resolveExportsTarget(pkgPath, target.default, subpath, - basePath, mappingKey); + return resolveExportsTarget(baseUrl, target.default, subpath, + mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e; } } } + throw new ERR_PACKAGE_PATH_NOT_EXPORTED( + StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath); } - let e; - if (mappingKey !== '.') { - // eslint-disable-next-line no-restricted-syntax - e = new Error(`Package exports for '${basePath}' do not define a ` + - `valid '${mappingKey}' target${subpath ? ' for ' + subpath : ''}`); - } else { - // eslint-disable-next-line no-restricted-syntax - e = new Error(`No valid exports main found for '${basePath}'`); - } - e.code = 'MODULE_NOT_FOUND'; - throw e; + throw new ERR_INVALID_PACKAGE_TARGET( + StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); } Module._findPath = function(request, paths, isMain) { diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 68359178f4a..436a6e98e73 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -858,10 +858,20 @@ void ThrowExportsNotFound(Environment* env, const std::string& subpath, const URL& pjson_url, const URL& base) { - const std::string msg = "Package exports for " + - pjson_url.ToFilePath() + " do not define a '" + subpath + - "' subpath, imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + const std::string msg = "Package subpath '" + subpath + "' is not defined" + + " by \"exports\" in " + pjson_url.ToFilePath() + " imported from " + + base.ToFilePath(); + node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); +} + +void ThrowSubpathInvalid(Environment* env, + const std::string& subpath, + const URL& pjson_url, + const URL& base) { + const std::string msg = "Package subpath '" + subpath + "' is not a valid " + + "module request for the \"exports\" resolution of " + + pjson_url.ToFilePath() + " imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str()); } void ThrowExportsInvalid(Environment* env, @@ -870,14 +880,15 @@ void ThrowExportsInvalid(Environment* env, const URL& pjson_url, const URL& base) { if (subpath.length()) { - const std::string msg = "Cannot resolve package exports target '" + target + - "' matched for '" + subpath + "' in " + pjson_url.ToFilePath() + - ", imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + const std::string msg = "Invalid \"exports\" target \"" + target + + "\" defined for '" + subpath + "' in the package config " + + pjson_url.ToFilePath() + " imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_TARGET(env, msg.c_str()); } else { - const std::string msg = "Cannot resolve package main '" + target + "' in" + - pjson_url.ToFilePath() + ", imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + const std::string msg = "Invalid \"exports\" main target " + target + + " defined in the package config " + pjson_url.ToFilePath() + + " imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_TARGET(env, msg.c_str()); } } @@ -887,14 +898,20 @@ void ThrowExportsInvalid(Environment* env, const URL& pjson_url, const URL& base) { Local target_string; - if (target->ToString(env->context()).ToLocal(&target_string)) { - Utf8Value target_utf8(env->isolate(), target_string); - std::string target_str(*target_utf8, target_utf8.length()); - if (target->IsArray()) { - target_str = '[' + target_str + ']'; - } - ThrowExportsInvalid(env, subpath, target_str, pjson_url, base); + if (target->IsObject()) { + if (!v8::JSON::Stringify(env->context(), target.As(), + v8::String::Empty(env->isolate())).ToLocal(&target_string)) + return; + } else { + if (!target->ToString(env->context()).ToLocal(&target_string)) + return; + } + Utf8Value target_utf8(env->isolate(), target_string); + std::string target_str(*target_utf8, target_utf8.length()); + if (target->IsArray()) { + target_str = '[' + target_str + ']'; } + ThrowExportsInvalid(env, subpath, target_str, pjson_url, base); } Maybe ResolveExportsTargetString(Environment* env, @@ -902,18 +919,13 @@ Maybe ResolveExportsTargetString(Environment* env, const std::string& subpath, const std::string& match, const URL& pjson_url, - const URL& base, - bool throw_invalid = true) { + const URL& base) { if (target.substr(0, 2) != "./") { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } if (subpath.length() > 0 && target.back() != '/') { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } URL resolved(target, pjson_url); @@ -922,9 +934,7 @@ Maybe ResolveExportsTargetString(Environment* env, if (resolved_path.find(pkg_path) != 0 || resolved_path.find("/node_modules/", pkg_path.length() - 1) != std::string::npos) { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } if (subpath.length() == 0) return Just(resolved); @@ -933,9 +943,7 @@ Maybe ResolveExportsTargetString(Environment* env, if (subpath_resolved_path.find(resolved_path) != 0 || subpath_resolved_path.find("/node_modules/", pkg_path.length() - 1) != std::string::npos) { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target + subpath, pjson_url, base); - } + ThrowSubpathInvalid(env, match + subpath, pjson_url, base); return Nothing(); } return Just(subpath_resolved); @@ -965,15 +973,14 @@ Maybe ResolveExportsTarget(Environment* env, Local target, const std::string& subpath, const std::string& pkg_subpath, - const URL& base, - bool throw_invalid = true) { + const URL& base) { Isolate* isolate = env->isolate(); Local context = env->context(); if (target->IsString()) { Utf8Value target_utf8(isolate, target.As()); std::string target_str(*target_utf8, target_utf8.length()); Maybe resolved = ResolveExportsTargetString(env, target_str, subpath, - pkg_subpath, pjson_url, base, throw_invalid); + pkg_subpath, pjson_url, base); if (resolved.IsNothing()) { return Nothing(); } @@ -982,40 +989,56 @@ Maybe ResolveExportsTarget(Environment* env, Local target_arr = target.As(); const uint32_t length = target_arr->Length(); if (length == 0) { - if (throw_invalid) { - ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); - } + ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); return Nothing(); } for (uint32_t i = 0; i < length; i++) { auto target_item = target_arr->Get(context, i).ToLocalChecked(); - if (!target_item->IsArray()) { + { + TryCatchScope try_catch(env); Maybe resolved = ResolveExportsTarget(env, pjson_url, - target_item, subpath, pkg_subpath, base, false); - if (resolved.IsNothing()) continue; + target_item, subpath, pkg_subpath, base); + if (resolved.IsNothing()) { + CHECK(try_catch.HasCaught()); + if (try_catch.Exception().IsEmpty()) return Nothing(); + Local e; + if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) + return Nothing(); + Local code; + if (!e->Get(context, env->code_string()).ToLocal(&code)) + return Nothing(); + Local code_string; + if (!code->ToString(context).ToLocal(&code_string)) + return Nothing(); + Utf8Value code_utf8(env->isolate(), code_string); + if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0 || + strcmp(*code_utf8, "ERR_INVALID_PACKAGE_TARGET") == 0) { + continue; + } + try_catch.ReThrow(); + return Nothing(); + } + CHECK(!try_catch.HasCaught()); return FinalizeResolution(env, resolved.FromJust(), base); } } - if (throw_invalid) { - auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, invalid, - subpath, pkg_subpath, base, true); - CHECK(resolved.IsNothing()); - } + auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); + Maybe resolved = ResolveExportsTarget(env, pjson_url, invalid, + subpath, pkg_subpath, base); + CHECK(resolved.IsNothing()); return Nothing(); } else if (target->IsObject()) { Local target_obj = target.As(); Local target_obj_keys = target_obj->GetOwnPropertyNames(context).ToLocalChecked(); Local conditionalTarget; - bool matched = false; for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { Local key = target_obj_keys->Get(context, i).ToLocalChecked(); if (IsArrayIndex(env, key)) { - const std::string msg = "Invalid package config for " + - pjson_url.ToFilePath() + ", \"exports\" cannot contain numeric " + - "property keys."; + const std::string msg = "Invalid package config " + + pjson_url.ToFilePath() + " imported from " + base.ToFilePath() + + ". \"exports\" cannot contain numeric property keys."; node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); return Nothing(); } @@ -1026,35 +1049,60 @@ Maybe ResolveExportsTarget(Environment* env, key->ToString(context).ToLocalChecked()); std::string key_str(*key_utf8, key_utf8.length()); if (key_str == "node" || key_str == "import") { - matched = true; conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, false); - if (!resolved.IsNothing()) { + { + TryCatchScope try_catch(env); + Maybe resolved = ResolveExportsTarget(env, pjson_url, + conditionalTarget, subpath, pkg_subpath, base); + if (resolved.IsNothing()) { + CHECK(try_catch.HasCaught()); + if (try_catch.Exception().IsEmpty()) return Nothing(); + Local e; + if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) + return Nothing(); + Local code; + if (!e->Get(context, env->code_string()).ToLocal(&code)) + return Nothing(); + Local code_string; + if (!code->ToString(context).ToLocal(&code_string)) + return Nothing(); + Utf8Value code_utf8(env->isolate(), code_string); + if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0) + continue; + try_catch.ReThrow(); + return Nothing(); + } + CHECK(!try_catch.HasCaught()); ProcessEmitExperimentalWarning(env, "Conditional exports"); return resolved; } } else if (key_str == "default") { - matched = true; conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, false); - if (!resolved.IsNothing()) { + { + TryCatchScope try_catch(env); + Maybe resolved = ResolveExportsTarget(env, pjson_url, + conditionalTarget, subpath, pkg_subpath, base); + if (resolved.IsNothing()) { + CHECK(try_catch.HasCaught() && !try_catch.Exception().IsEmpty()); + auto e = try_catch.Exception()->ToObject(context).ToLocalChecked(); + auto code = e->Get(context, env->code_string()).ToLocalChecked(); + Utf8Value code_utf8(env->isolate(), + code->ToString(context).ToLocalChecked()); + std::string code_str(*code_utf8, code_utf8.length()); + if (code_str == "ERR_PACKAGE_PATH_NOT_EXPORTED") continue; + try_catch.ReThrow(); + return Nothing(); + } + CHECK(!try_catch.HasCaught()); ProcessEmitExperimentalWarning(env, "Conditional exports"); return resolved; } } } - if (matched && throw_invalid) { - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, true); - CHECK(resolved.IsNothing()); - return Nothing(); - } - } - if (throw_invalid) { - ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); + ThrowExportsNotFound(env, pkg_subpath, pjson_url, base); + return Nothing(); } + ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); return Nothing(); } @@ -1076,8 +1124,8 @@ Maybe IsConditionalExportsMainSugar(Environment* env, if (i == 0) { isConditionalSugar = curIsConditionalSugar; } else if (isConditionalSugar != curIsConditionalSugar) { - const std::string msg = "Cannot resolve package exports in " + - pjson_url.ToFilePath() + ", imported from " + base.ToFilePath() + ". " + + const std::string msg = "Invalid package config " + pjson_url.ToFilePath() + + " imported from " + base.ToFilePath() + ". " + "\"exports\" cannot contain some keys starting with '.' and some not." + " The exports object must either be an object of package subpath keys" + " or an object of main entry condition name keys only."; @@ -1102,8 +1150,7 @@ Maybe PackageMainResolve(Environment* env, if (isConditionalExportsMainSugar.IsNothing()) return Nothing(); if (isConditionalExportsMainSugar.FromJust()) { - return ResolveExportsTarget(env, pjson_url, exports, "", "", base, - true); + return ResolveExportsTarget(env, pjson_url, exports, "", "", base); } else if (exports->IsObject()) { Local exports_obj = exports.As(); if (exports_obj->HasOwnProperty(env->context(), env->dot_string()) @@ -1111,10 +1158,12 @@ Maybe PackageMainResolve(Environment* env, Local target = exports_obj->Get(env->context(), env->dot_string()) .ToLocalChecked(); - return ResolveExportsTarget(env, pjson_url, target, "", "", base, - true); + return ResolveExportsTarget(env, pjson_url, target, "", "", base); } } + std::string msg = "No \"exports\" main resolved in " + + pjson_url.ToFilePath(); + node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); } if (pcfg.has_main == HasMain::Yes) { URL resolved(pcfg.main, pjson_url); @@ -1206,39 +1255,6 @@ Maybe PackageExportsResolve(Environment* env, return Nothing(); } -Maybe ResolveSelf(Environment* env, - const std::string& pkg_name, - const std::string& pkg_subpath, - const URL& base) { - const PackageConfig* pcfg; - if (GetPackageScopeConfig(env, base, base).To(&pcfg) && - pcfg->exists == Exists::Yes) { - // TODO(jkrems): Find a way to forward the pair/iterator already generated - // while executing GetPackageScopeConfig - URL pjson_url(""); - bool found_pjson = false; - for (auto it = env->package_json_cache.begin(); - it != env->package_json_cache.end(); - ++it) { - if (&it->second == pcfg) { - pjson_url = URL::FromFilePath(it->first); - found_pjson = true; - } - } - if (!found_pjson || pcfg->name != pkg_name) return Nothing(); - if (pcfg->exports.IsEmpty()) return Nothing(); - if (pkg_subpath == "./") { - return Just(URL("./", pjson_url)); - } else if (!pkg_subpath.length()) { - return PackageMainResolve(env, pjson_url, *pcfg, base); - } else { - return PackageExportsResolve(env, pjson_url, pkg_subpath, *pcfg, base); - } - } - - return Nothing(); -} - Maybe PackageResolve(Environment* env, const std::string& specifier, const URL& base) { @@ -1279,10 +1295,30 @@ Maybe PackageResolve(Environment* env, pkg_subpath = "." + specifier.substr(sep_index); } - Maybe self_url = ResolveSelf(env, pkg_name, pkg_subpath, base); - if (self_url.IsJust()) { - ProcessEmitExperimentalWarning(env, "Package name self resolution"); - return self_url; + // ResolveSelf + const PackageConfig* pcfg; + if (GetPackageScopeConfig(env, base, base).To(&pcfg) && + pcfg->exists == Exists::Yes) { + // TODO(jkrems): Find a way to forward the pair/iterator already generated + // while executing GetPackageScopeConfig + URL pjson_url(""); + bool found_pjson = false; + for (const auto& it : env->package_json_cache) { + if (&it.second == pcfg) { + pjson_url = URL::FromFilePath(it.first); + found_pjson = true; + } + } + if (found_pjson && pcfg->name == pkg_name && !pcfg->exports.IsEmpty()) { + ProcessEmitExperimentalWarning(env, "Package name self resolution"); + if (pkg_subpath == "./") { + return Just(URL("./", pjson_url)); + } else if (!pkg_subpath.length()) { + return PackageMainResolve(env, pjson_url, *pcfg, base); + } else { + return PackageExportsResolve(env, pjson_url, pkg_subpath, *pcfg, base); + } + } } URL pjson_url("./node_modules/" + pkg_name + "/package.json", &base); diff --git a/src/node_errors.h b/src/node_errors.h index d56bf7ef5a5..a05ce8f6bfb 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -43,7 +43,8 @@ void OnFatalError(const char* location, const char* message); V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \ V(ERR_INVALID_ARG_TYPE, TypeError) \ V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \ - V(ERR_INVALID_PACKAGE_CONFIG, SyntaxError) \ + V(ERR_INVALID_PACKAGE_CONFIG, Error) \ + V(ERR_INVALID_PACKAGE_TARGET, Error) \ V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \ V(ERR_MEMORY_ALLOCATION_FAILED, Error) \ V(ERR_MISSING_ARGS, TypeError) \ @@ -53,6 +54,7 @@ void OnFatalError(const char* location, const char* message); V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \ V(ERR_MODULE_NOT_FOUND, Error) \ V(ERR_OUT_OF_RANGE, RangeError) \ + V(ERR_PACKAGE_PATH_NOT_EXPORTED, Error) \ V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \ V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \ V(ERR_STRING_TOO_LONG, Error) \ diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index bdd4a975cf7..7dbc9635028 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -32,7 +32,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; ['pkgexports/resolve-self', isRequire ? { default: 'self-cjs' } : { default: 'self-mjs' }], // Resolve self sugar - ['pkgexports-sugar', { default: 'main' }] + ['pkgexports-sugar', { default: 'main' }], ]); for (const [validSpecifier, expected] of validSpecifiers) { @@ -53,48 +53,59 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // Sugar cases still encapsulate ['pkgexports-sugar/not-exported.js', './not-exported.js'], ['pkgexports-sugar2/not-exported.js', './not-exported.js'], + // Conditional exports with no match are "not exported" errors + ['pkgexports/invalid1', './invalid1'], + ['pkgexports/invalid4', './invalid4'], ]); const invalidExports = new Map([ - // Even though 'pkgexports/sub/asdf.js' works, alternate "path-like" - // variants do not to prevent confusion and accidental loopholes. - ['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'], + // Directory mappings require a trailing / to work + ['pkgexports/missingtrailer/x', './missingtrailer/'], // This path steps back inside the package but goes through an exports // target that escapes the package, so we still catch that as invalid - ['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/pkgexports/asdf.js'], + ['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/'], // This target file steps below the package ['pkgexports/belowfile', './belowfile'], - // Directory mappings require a trailing / to work - ['pkgexports/missingtrailer/x', './missingtrailer/x'], // Invalid target handling ['pkgexports/null', './null'], - ['pkgexports/invalid1', './invalid1'], ['pkgexports/invalid2', './invalid2'], ['pkgexports/invalid3', './invalid3'], - ['pkgexports/invalid4', './invalid4'], // Missing / invalid fallbacks ['pkgexports/nofallback1', './nofallback1'], ['pkgexports/nofallback2', './nofallback2'], // Reaching into nested node_modules ['pkgexports/nodemodules', './nodemodules'], + // Self resolve invalid + ['pkgexports/resolve-self-invalid', './invalid2'], + ]); + + const invalidSpecifiers = new Map([ + // Even though 'pkgexports/sub/asdf.js' works, alternate "path-like" + // variants do not to prevent confusion and accidental loopholes. + ['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'], ]); for (const [specifier, subpath] of undefinedExports) { loadFixture(specifier).catch(mustCall((err) => { - strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, 'Package exports'); - assertIncludes(err.message, `do not define a '${subpath}' subpath`); + strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + assertStartsWith(err.message, 'Package subpath '); + assertIncludes(err.message, subpath); })); } for (const [specifier, subpath] of invalidExports) { loadFixture(specifier).catch(mustCall((err) => { - strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, (isRequire ? 'Package exports' : - 'Cannot resolve')); - assertIncludes(err.message, isRequire ? - `do not define a valid '${subpath}' target` : - `matched for '${subpath}'`); + strictEqual(err.code, 'ERR_INVALID_PACKAGE_TARGET'); + assertStartsWith(err.message, 'Invalid "exports"'); + assertIncludes(err.message, subpath); + })); + } + + for (const [specifier, subpath] of invalidSpecifiers) { + loadFixture(specifier).catch(mustCall((err) => { + strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER'); + assertStartsWith(err.message, 'Package subpath '); + assertIncludes(err.message, subpath); })); } @@ -102,8 +113,8 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // of falling back to main if (isRequire) { loadFixture('pkgexports-main').catch(mustCall((err) => { - strictEqual(err.code, 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, 'No valid export'); + strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + assertStartsWith(err.message, 'No "exports" main '); })); } @@ -130,8 +141,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // Sugar conditional exports main mixed failure case loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => { strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG'); - assertStartsWith(err.message, (isRequire ? 'Invalid package' : - 'Cannot resolve')); + assertStartsWith(err.message, 'Invalid package'); assertIncludes(err.message, '"exports" cannot contain some keys starting ' + 'with \'.\' and some not. The exports object must either be an object of ' + 'package subpath keys or an object of main entry condition name keys ' + diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json index 02e06f0ebe5..7f417ad5457 100644 --- a/test/fixtures/node_modules/pkgexports/package.json +++ b/test/fixtures/node_modules/pkgexports/package.json @@ -35,6 +35,10 @@ "./resolve-self": { "require": "./resolve-self.js", "import": "./resolve-self.mjs" + }, + "./resolve-self-invalid": { + "require": "./resolve-self-invalid.js", + "import": "./resolve-self-invalid.mjs" } } } diff --git a/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js new file mode 100644 index 00000000000..c3ebf76fc1b --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js @@ -0,0 +1 @@ +require('pkg-exports/invalid2'); diff --git a/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs new file mode 100644 index 00000000000..1edbf62c4b0 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs @@ -0,0 +1 @@ +import 'pkg-exports/invalid2'; From 1c2d77d3d9dd5753a96c0033265c5eeaf3531d00 Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Thu, 13 Feb 2020 15:03:18 -0800 Subject: [PATCH 023/192] 2020-02-18, Version 12.16.1 'Erbium' (LTS) Notable changes: Node.js 12.16.0 included 6 regressions that are being fixed in this release **Accidental Unflagging of Self Resolving Modules**: 12.16.0 included a large update to the ESM implementation. One of the new features, Self Referential Modules, was accidentally released without requiring the `--experimental-modules` flag. This release is being made to appropriately flag the feature. **Process Cleanup Changed Introduced WASM-Related Assertion**: A change during Node.js process cleanup led to a crash in combination with specific usage of WASM. This has been fixed by partially reverted said change. A regression test and a full fix are being worked on and will likely be included in future 12.x and 13.x releases. **Use Largepages Runtime Option Introduced Linking Failure**: A Semver-Minor change to introduce `--use-largepages` as a runtime option introduced a linking failure. This had been fixed in master but regressed as the fix has not yet gone out in a Current release. The feature has been reverted, but will be able to reland with a fix in a future Semver-Minor release. **Async Hooks was Causing an Exception When Handling Errors**: Changes in async hooks internals introduced a case where an internal api call could be called with undefined causing a process to crash. The change to async hooks was reverted. A regression test and fix has been proposed and the change could re land in a future Semver-Patch release if the regression is reliably fixed. **New Enumerable Read-Only Property on EventEmitter breaks @types/extend** A new property for enumerating events was added to the EventEmitter class. This broke existing code that was using the `@types/extend` module for extending classses as `@types/extend` was attemping to write over the existing field which the new change made read-only. As this is the first property on EventEmitter that is read-only this feature could be considered Semver-Major. The new feature has been reverted but could re land in a future Semver-Minor release if a non breaking way of applying it is found. **Exceptions in the HTTP parser were not emitting an uncaughtException** A refactoring to Node.js interanls resulted in a bug where errors in the HTTP parser were not being emitted by `process.on('uncaughtException')`. The fix to this bug has been included in this release. PR-URL: https://github.com/nodejs/node/pull/31781 --- CHANGELOG.md | 3 +- doc/changelogs/CHANGELOG_V12.md | 64 +++++++++++++++++++++++++++++++++ src/node_version.h | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da2f7373e5..291f7e1669f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,8 @@ release. 13.0.0
-12.16.0
+12.16.1
+12.16.0
12.15.0
12.14.1
12.14.0
diff --git a/doc/changelogs/CHANGELOG_V12.md b/doc/changelogs/CHANGELOG_V12.md index 8b860d9e460..36737e2f4fc 100644 --- a/doc/changelogs/CHANGELOG_V12.md +++ b/doc/changelogs/CHANGELOG_V12.md @@ -10,6 +10,7 @@ +12.16.1
12.16.0
12.15.0
12.14.1
@@ -54,6 +55,69 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-02-18, Version 12.16.1 'Erbium' (LTS), @MylesBorins + +### Notable changes + +Node.js 12.16.0 included 6 regressions that are being fixed in this release + +**Accidental Unflagging of Self Resolving Modules**: + +12.16.0 included a large update to the ESM implementation. One of the new features, +Self Referential Modules, was accidentally released without requiring the `--experimental-modules` +flag. This release is being made to appropriately flag the feature. + +**Process Cleanup Changed Introduced WASM-Related Assertion**: + +A change during Node.js process cleanup led to a crash in combination with +specific usage of WASM. This has been fixed by partially reverted said change. +A regression test and a full fix are being worked on and will likely be included +in future 12.x and 13.x releases. + +**Use Largepages Runtime Option Introduced Linking Failure**: + +A Semver-Minor change to introduce `--use-largepages` as a runtime option +introduced a linking failure. This had been fixed in master but regressed as the fix has not yet gone out +in a Current release. The feature has been reverted, but will be able to reland with a fix in a future +Semver-Minor release. + +**Async Hooks was Causing an Exception When Handling Errors**: + +Changes in async hooks internals introduced a case where an internal api call could be called with undefined +causing a process to crash. The change to async hooks was reverted. A regression test and fix has been proposed +and the change could re land in a future Semver-Patch release if the regression is reliably fixed. + +**New Enumerable Read-Only Property on EventEmitter breaks @types/extend** + +A new property for enumerating events was added to the EventEmitter class. This +broke existing code that was using the `@types/extend` module for extending classses +as `@types/extend` was attemping to write over the existing field which the new +change made read-only. As this is the first property on EventEmitter that is +read-only this feature could be considered Semver-Major. The new feature has been +reverted but could re land in a future Semver-Minor release if a non breaking way +of applying it is found. + +**Exceptions in the HTTP parser were not emitting an uncaughtException** + +A refactoring to Node.js interanls resulted in a bug where errors in the HTTP +parser were not being emitted by `process.on('uncaughtException')`. The fix +to this bug has been included in this release. + +### Commits + +* [[`51fdd759b9`](https://github.com/nodejs/node/commit/51fdd759b9)] - **async_hooks**: ensure event after been emitted on runInAsyncScope (legendecas) [#31784](https://github.com/nodejs/node/pull/31784) +* [[`7a1b0ac06f`](https://github.com/nodejs/node/commit/7a1b0ac06f)] - ***Revert*** "**build**: re-introduce --use-largepages as no-op" (Myles Borins) [#31782](https://github.com/nodejs/node/pull/31782) +* [[`a53eeca2a9`](https://github.com/nodejs/node/commit/a53eeca2a9)] - ***Revert*** "**build**: switch realpath to pwd" (Myles Borins) [#31782](https://github.com/nodejs/node/pull/31782) +* [[`6d432994e6`](https://github.com/nodejs/node/commit/6d432994e6)] - ***Revert*** "**build**: warn upon --use-largepages config option" (Myles Borins) [#31782](https://github.com/nodejs/node/pull/31782) +* [[`a5bc00af12`](https://github.com/nodejs/node/commit/a5bc00af12)] - ***Revert*** "**events**: allow monitoring error events" (Myles Borins) +* [[`f0b2d875d9`](https://github.com/nodejs/node/commit/f0b2d875d9)] - **module**: 12.x self resolve flag as experimental modules (Guy Bedford) [#31757](https://github.com/nodejs/node/pull/31757) +* [[`42b68a4e24`](https://github.com/nodejs/node/commit/42b68a4e24)] - **src**: inform callback scopes about exceptions in HTTP parser (Anna Henningsen) [#31801](https://github.com/nodejs/node/pull/31801) +* [[`065a32f064`](https://github.com/nodejs/node/commit/065a32f064)] - ***Revert*** "**src**: make --use-largepages a runtime option" (Myles Borins) [#31782](https://github.com/nodejs/node/pull/31782) +* [[`3d5beebc62`](https://github.com/nodejs/node/commit/3d5beebc62)] - ***Revert*** "**src**: make large\_pages node.cc include conditional" (Myles Borins) [#31782](https://github.com/nodejs/node/pull/31782) +* [[`43d02e20e0`](https://github.com/nodejs/node/commit/43d02e20e0)] - **src**: keep main-thread Isolate attached to platform during Dispose (Anna Henningsen) [#31795](https://github.com/nodejs/node/pull/31795) +* [[`7a5954ef26`](https://github.com/nodejs/node/commit/7a5954ef26)] - **src**: fix -Winconsistent-missing-override warning (Colin Ihrig) [#30549](https://github.com/nodejs/node/pull/30549) + ## 2020-02-11, Version 12.16.0 'Erbium' (LTS), @targos diff --git a/src/node_version.h b/src/node_version.h index ebd3cff606f..229bf5d2def 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -29,7 +29,7 @@ #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) From 8ba7a2f889472d58478bf40b758d336b41af682b Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Tue, 13 Nov 2018 23:25:51 +0100 Subject: [PATCH 024/192] http2: make compat finished match http/1 finished should true directly after end(). PR-URL: https://github.com/nodejs/node/pull/24347 Refs: https://github.com/nodejs/node/issues/24743 Reviewed-By: Matteo Collina Reviewed-By: Ujjwal Sharma Reviewed-By: Anatoli Papirovski Reviewed-By: James M Snell --- lib/internal/http2/compat.js | 11 ++++------- test/parallel/test-http2-compat-serverresponse-end.js | 4 +++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js index 25a80684621..3abe9ba2ac6 100644 --- a/lib/internal/http2/compat.js +++ b/lib/internal/http2/compat.js @@ -471,10 +471,8 @@ class Http2ServerResponse extends Stream { } get finished() { - const stream = this[kStream]; - return stream.destroyed || - stream._writableState.ended || - this[kState].closed; + const state = this[kState]; + return state.ending; } get socket() { @@ -700,12 +698,11 @@ class Http2ServerResponse extends Stream { if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); - const isFinished = this.finished; state.headRequest = stream.headRequest; state.ending = true; if (typeof cb === 'function') { - if (isFinished) + if (stream.writableEnded) this.once('finish', cb); else stream.once('finish', cb); @@ -714,7 +711,7 @@ class Http2ServerResponse extends Stream { if (!stream.headersSent) this.writeHead(this[kState].statusCode); - if (isFinished) + if (this[kState].closed || stream.destroyed) onStreamCloseResponse.call(stream); else stream.end(); diff --git a/test/parallel/test-http2-compat-serverresponse-end.js b/test/parallel/test-http2-compat-serverresponse-end.js index 5bbb24bb2ed..8505d6c4969 100644 --- a/test/parallel/test-http2-compat-serverresponse-end.js +++ b/test/parallel/test-http2-compat-serverresponse-end.js @@ -149,11 +149,13 @@ const { // Http2ServerResponse.end is necessary on HEAD requests in compat // for http1 compatibility const server = createServer(mustCall((request, response) => { - strictEqual(response.finished, true); strictEqual(response.writableEnded, false); + strictEqual(response.finished, false); response.writeHead(HTTP_STATUS_OK, { foo: 'bar' }); + strictEqual(response.finished, false); response.end('data', mustCall()); strictEqual(response.writableEnded, true); + strictEqual(response.finished, true); })); server.listen(0, mustCall(() => { const { port } = server.address(); From 0c3c0e7184c4ca625148c14d61d8e9438a1b69f2 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Mon, 17 Feb 2020 13:02:21 -0800 Subject: [PATCH 025/192] 2020-02-18, Version 13.9.0 (Current) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Notable changes: * async_hooks * add executionAsyncResource (Matteo Collina) #30959 * crypto * add crypto.diffieHellman (Tobias Nießen) #31178 * add DH support to generateKeyPair (Tobias Nießen) #31178 * simplify DH groups (Tobias Nießen) #31178 * add key type 'dh' (Tobias Nießen) #31178 * test * skip keygen tests on arm systems (Tobias Nießen) #31178 * perf_hooks * add property flags to GCPerformanceEntry (Kirill Fomichev) #29547 * process * report ArrayBuffer memory in `memoryUsage()` (Anna Henningsen) #31550 * readline * make tab size configurable (Ruben Bridgewater) #31318 * report * add support for Workers (Anna Henningsen) #31386 * worker * add ability to take heap snapshot from parent thread (Anna Henningsen) #31569 * added new collaborators * add ronag to collaborators (Robert Nagy) #31498 PR-URL: https://github.com/nodejs/node/pull/31837 --- CHANGELOG.md | 3 +- doc/api/assert.md | 2 +- doc/api/async_hooks.md | 2 +- doc/api/cli.md | 4 +- doc/api/crypto.md | 8 +- doc/api/perf_hooks.md | 2 +- doc/api/process.md | 2 +- doc/api/readline.md | 2 +- doc/api/report.md | 2 +- doc/api/worker_threads.md | 2 +- doc/changelogs/CHANGELOG_V13.md | 238 ++++++++++++++++++++++++++++++++ src/node_version.h | 2 +- 12 files changed, 254 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 291f7e1669f..0300c9b1f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ release. -13.8.0
+13.9.0
+13.8.0
13.7.0
13.6.0
13.5.0
diff --git a/doc/api/assert.md b/doc/api/assert.md index 9d513f9a0f8..bb9df480ddf 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -11,7 +11,7 @@ invariants. * Returns: {Object} The resource representing the current execution. diff --git a/doc/api/cli.md b/doc/api/cli.md index 9d62ac9ba31..89d5f62d14f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -158,7 +158,7 @@ Currently, overriding `Error.prepareStackTrace` is ignored when the ### `--experimental-import-meta-resolve` Enable experimental `import.meta.resolve()` support. @@ -788,7 +788,7 @@ i.e. invoking `process.exit()`. ### `--trace-sigint` Prints a stack trace on SIGINT. diff --git a/doc/api/crypto.md b/doc/api/crypto.md index d924c8643cf..cb1d616c03d 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1232,7 +1232,7 @@ passing keys as strings or `Buffer`s due to improved security features. * `options`: {Object} @@ -2107,7 +2107,7 @@ Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'` * {number} diff --git a/doc/api/process.md b/doc/api/process.md index c5c99f2a707..598a75cc9a7 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1510,7 +1510,7 @@ is no entry script. diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index c56411318da..b67d563fa6d 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -683,7 +683,7 @@ inside the worker thread. If `stdout: true` was not passed to the ### `worker.takeHeapSnapshot()` * Returns: {Promise} A promise for a Readable Stream containing diff --git a/doc/changelogs/CHANGELOG_V13.md b/doc/changelogs/CHANGELOG_V13.md index f3234798733..a9dfd87bf10 100644 --- a/doc/changelogs/CHANGELOG_V13.md +++ b/doc/changelogs/CHANGELOG_V13.md @@ -9,6 +9,7 @@ +13.9.0
13.8.0
13.7.0
13.6.0
@@ -38,6 +39,243 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-02-18, Version 13.9.0 (Current), @codebytere + +### Notable changes + +* [[`6be51296e4`](https://github.com/nodejs/node/commit/6be51296e4)] - **(SEMVER-MINOR)** **async_hooks**: add executionAsyncResource (Matteo Collina) [#30959](https://github.com/nodejs/node/pull/30959) +* [[`15b24b71ce`](https://github.com/nodejs/node/commit/15b24b71ce)] - **doc**: add ronag to collaborators (Robert Nagy) [#31498](https://github.com/nodejs/node/pull/31498) +* [[`1bcf2f9423`](https://github.com/nodejs/node/commit/1bcf2f9423)] - **report**: add support for Workers (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`676b84a803`](https://github.com/nodejs/node/commit/676b84a803)] - **(SEMVER-MINOR)** **test**: skip keygen tests on arm systems (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`bf46c304dd`](https://github.com/nodejs/node/commit/bf46c304dd)] - **(SEMVER-MINOR)** **crypto**: add crypto.diffieHellman (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`0d3e095941`](https://github.com/nodejs/node/commit/0d3e095941)] - **(SEMVER-MINOR)** **crypto**: add DH support to generateKeyPair (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`15bd2c9f0c`](https://github.com/nodejs/node/commit/15bd2c9f0c)] - **(SEMVER-MINOR)** **crypto**: simplify DH groups (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`572322fddf`](https://github.com/nodejs/node/commit/572322fddf)] - **(SEMVER-MINOR)** **crypto**: add key type 'dh' (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) + +### Commits + +* [[`2db7593838`](https://github.com/nodejs/node/commit/2db7593838)] - **assert**: align character indicators properly (Ruben Bridgewater) [#31429](https://github.com/nodejs/node/pull/31429) +* [[`a840e9d639`](https://github.com/nodejs/node/commit/a840e9d639)] - **async_hooks**: ensure event after been emitted on runInAsyncScope (legendecas) [#31784](https://github.com/nodejs/node/pull/31784) +* [[`6be51296e4`](https://github.com/nodejs/node/commit/6be51296e4)] - **(SEMVER-MINOR)** **async_hooks**: add executionAsyncResource (Matteo Collina) [#30959](https://github.com/nodejs/node/pull/30959) +* [[`2de085fe93`](https://github.com/nodejs/node/commit/2de085fe93)] - **benchmark**: use let instead of var (Daniele Belardi) [#31592](https://github.com/nodejs/node/pull/31592) +* [[`e37f5100e5`](https://github.com/nodejs/node/commit/e37f5100e5)] - **benchmark**: swap var for let in benchmarks (Alex Ramirez) [#28958](https://github.com/nodejs/node/pull/28958) +* [[`819fb76ba5`](https://github.com/nodejs/node/commit/819fb76ba5)] - ***Revert*** "**benchmark**: refactor helper into a class" (Anna Henningsen) [#31722](https://github.com/nodejs/node/pull/31722) +* [[`8974fa794c`](https://github.com/nodejs/node/commit/8974fa794c)] - ***Revert*** "**benchmark**: add `test` and `all` options and improve errors" (Anna Henningsen) [#31722](https://github.com/nodejs/node/pull/31722) +* [[`30f55cebb6`](https://github.com/nodejs/node/commit/30f55cebb6)] - ***Revert*** "**benchmark**: remove special test entries" (Anna Henningsen) [#31722](https://github.com/nodejs/node/pull/31722) +* [[`1484f5ab6e`](https://github.com/nodejs/node/commit/1484f5ab6e)] - **benchmark**: remove special test entries (Ruben Bridgewater) [#31396](https://github.com/nodejs/node/pull/31396) +* [[`ca343caee3`](https://github.com/nodejs/node/commit/ca343caee3)] - **benchmark**: add `test` and `all` options and improve errors (Ruben Bridgewater) [#31396](https://github.com/nodejs/node/pull/31396) +* [[`9f2c742626`](https://github.com/nodejs/node/commit/9f2c742626)] - **benchmark**: refactor helper into a class (Ruben Bridgewater) [#31396](https://github.com/nodejs/node/pull/31396) +* [[`161db608ae`](https://github.com/nodejs/node/commit/161db608ae)] - **benchmark**: check for and fix multiple end() (Brian White) [#31624](https://github.com/nodejs/node/pull/31624) +* [[`6fe8eda3ca`](https://github.com/nodejs/node/commit/6fe8eda3ca)] - **benchmark**: clean up config resolution in multiple benchmarks (Denys Otrishko) [#31581](https://github.com/nodejs/node/pull/31581) +* [[`ebdcafafeb`](https://github.com/nodejs/node/commit/ebdcafafeb)] - **benchmark**: add MessagePort benchmark (Anna Henningsen) [#31568](https://github.com/nodejs/node/pull/31568) +* [[`eb3c6e9127`](https://github.com/nodejs/node/commit/eb3c6e9127)] - **benchmark**: use let and const instead of var (Daniele Belardi) [#31518](https://github.com/nodejs/node/pull/31518) +* [[`b29badad81`](https://github.com/nodejs/node/commit/b29badad81)] - **benchmark**: fix getStringWidth() benchmark (Rich Trott) [#31476](https://github.com/nodejs/node/pull/31476) +* [[`519134ddb0`](https://github.com/nodejs/node/commit/519134ddb0)] - **buffer**: improve from() performance (Brian White) [#31615](https://github.com/nodejs/node/pull/31615) +* [[`769154de07`](https://github.com/nodejs/node/commit/769154de07)] - **buffer**: improve concat() performance (Brian White) [#31522](https://github.com/nodejs/node/pull/31522) +* [[`9d45393e95`](https://github.com/nodejs/node/commit/9d45393e95)] - **buffer**: improve fill(number) performance (Brian White) [#31489](https://github.com/nodejs/node/pull/31489) +* [[`60a69770f5`](https://github.com/nodejs/node/commit/60a69770f5)] - **build**: add configure option to debug only Node.js part of the binary (Anna Henningsen) [#31644](https://github.com/nodejs/node/pull/31644) +* [[`10f9abe81d`](https://github.com/nodejs/node/commit/10f9abe81d)] - **build**: ignore all the "Debug","Release" folders (ConorDavenport) [#31565](https://github.com/nodejs/node/pull/31565) +* [[`03eade01d7`](https://github.com/nodejs/node/commit/03eade01d7)] - **build**: enable loading internal modules from disk (Gus Caplan) [#31321](https://github.com/nodejs/node/pull/31321) +* [[`a2b7006847`](https://github.com/nodejs/node/commit/a2b7006847)] - **build**: build docs in GitHub Actions CI workflow (Richard Lau) [#31504](https://github.com/nodejs/node/pull/31504) +* [[`2e216aebcb`](https://github.com/nodejs/node/commit/2e216aebcb)] - **build**: do not use setup-node in build workflows (Richard Lau) [#31349](https://github.com/nodejs/node/pull/31349) +* [[`825d089763`](https://github.com/nodejs/node/commit/825d089763)] - **crypto**: fix performance regression (Robert Nagy) [#31742](https://github.com/nodejs/node/pull/31742) +* [[`3c6545f0b4`](https://github.com/nodejs/node/commit/3c6545f0b4)] - **crypto**: improve randomBytes() performance (Brian White) [#31519](https://github.com/nodejs/node/pull/31519) +* [[`f84b34d42c`](https://github.com/nodejs/node/commit/f84b34d42c)] - **crypto**: improve errors in DiffieHellmanGroup (Tobias Nießen) [#31445](https://github.com/nodejs/node/pull/31445) +* [[`4591202e66`](https://github.com/nodejs/node/commit/4591202e66)] - **crypto**: assign and use ERR\_CRYPTO\_UNKNOWN\_CIPHER (Tobias Nießen) [#31437](https://github.com/nodejs/node/pull/31437) +* [[`bf46c304dd`](https://github.com/nodejs/node/commit/bf46c304dd)] - **(SEMVER-MINOR)** **crypto**: add crypto.diffieHellman (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`0d3e095941`](https://github.com/nodejs/node/commit/0d3e095941)] - **(SEMVER-MINOR)** **crypto**: add DH support to generateKeyPair (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`15bd2c9f0c`](https://github.com/nodejs/node/commit/15bd2c9f0c)] - **(SEMVER-MINOR)** **crypto**: simplify DH groups (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`572322fddf`](https://github.com/nodejs/node/commit/572322fddf)] - **(SEMVER-MINOR)** **crypto**: add key type 'dh' (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`0ac124b6b9`](https://github.com/nodejs/node/commit/0ac124b6b9)] - **deps**: upgrade npm to 6.13.7 (Michael Perrotte) [#31558](https://github.com/nodejs/node/pull/31558) +* [[`bf7097c77d`](https://github.com/nodejs/node/commit/bf7097c77d)] - **deps**: switch to chromium's zlib implementation (Brian White) [#31201](https://github.com/nodejs/node/pull/31201) +* [[`2eeaa5ce40`](https://github.com/nodejs/node/commit/2eeaa5ce40)] - **deps**: uvwasi: cherry-pick 7b5b6f9 (cjihrig) [#31495](https://github.com/nodejs/node/pull/31495) +* [[`464f4afa66`](https://github.com/nodejs/node/commit/464f4afa66)] - **deps**: upgrade to libuv 1.34.2 (cjihrig) [#31477](https://github.com/nodejs/node/pull/31477) +* [[`9811ebe0c5`](https://github.com/nodejs/node/commit/9811ebe0c5)] - **deps**: uvwasi: cherry-pick eea4508 (cjihrig) [#31432](https://github.com/nodejs/node/pull/31432) +* [[`2fe0ed3a2e`](https://github.com/nodejs/node/commit/2fe0ed3a2e)] - **deps**: uvwasi: cherry-pick c3bef8e (cjihrig) [#31432](https://github.com/nodejs/node/pull/31432) +* [[`09566be899`](https://github.com/nodejs/node/commit/09566be899)] - **deps**: uvwasi: cherry-pick ea73af5 (cjihrig) [#31432](https://github.com/nodejs/node/pull/31432) +* [[`04f2799ed2`](https://github.com/nodejs/node/commit/04f2799ed2)] - **deps**: update to uvwasi 0.0.5 (cjihrig) [#31432](https://github.com/nodejs/node/pull/31432) +* [[`7c4f1ed030`](https://github.com/nodejs/node/commit/7c4f1ed030)] - **deps**: uvwasi: cherry-pick 941bedf (cjihrig) [#31363](https://github.com/nodejs/node/pull/31363) +* [[`00e38a749a`](https://github.com/nodejs/node/commit/00e38a749a)] - **deps**: port uvwasi@676ba9a to gyp (cjihrig) [#31363](https://github.com/nodejs/node/pull/31363) +* [[`5bd3f6c258`](https://github.com/nodejs/node/commit/5bd3f6c258)] - **deps,test**: update to uvwasi 0.0.4 (cjihrig) [#31363](https://github.com/nodejs/node/pull/31363) +* [[`2cd8461e56`](https://github.com/nodejs/node/commit/2cd8461e56)] - **doc**: add glossary.md (gengjiawen) [#27517](https://github.com/nodejs/node/pull/27517) +* [[`c4613c6b8b`](https://github.com/nodejs/node/commit/c4613c6b8b)] - **doc**: add prerequisites information for Arch (Ujjwal Sharma) [#31669](https://github.com/nodejs/node/pull/31669) +* [[`b35f83e69b`](https://github.com/nodejs/node/commit/b35f83e69b)] - **doc**: fix typo on fs docs (Juan José Arboleda) [#31620](https://github.com/nodejs/node/pull/31620) +* [[`2ff812ca84`](https://github.com/nodejs/node/commit/2ff812ca84)] - **doc**: update contact email for @ryzokuken (Ujjwal Sharma) [#31670](https://github.com/nodejs/node/pull/31670) +* [[`2c83946757`](https://github.com/nodejs/node/commit/2c83946757)] - **doc**: fix default server timeout description for https (Andrey Pechkurov) [#31692](https://github.com/nodejs/node/pull/31692) +* [[`b56a21fdad`](https://github.com/nodejs/node/commit/b56a21fdad)] - **doc**: add directions to mark a release line as lts (Danielle Adams) [#31724](https://github.com/nodejs/node/pull/31724) +* [[`5ae40cd2b2`](https://github.com/nodejs/node/commit/5ae40cd2b2)] - **doc**: expand C++ README with information about exception handling (Anna Henningsen) [#31720](https://github.com/nodejs/node/pull/31720) +* [[`94a0ec1b99`](https://github.com/nodejs/node/commit/94a0ec1b99)] - **doc**: update foundation name in onboarding (Tobias Nießen) [#31719](https://github.com/nodejs/node/pull/31719) +* [[`fda97fa772`](https://github.com/nodejs/node/commit/fda97fa772)] - **doc**: reword possessive form of Node.js in zlib.md (Rich Trott) [#31713](https://github.com/nodejs/node/pull/31713) +* [[`eea58cd3d5`](https://github.com/nodejs/node/commit/eea58cd3d5)] - **doc**: reword possessive form of Node.js in modules.md (Rich Trott) [#31713](https://github.com/nodejs/node/pull/31713) +* [[`d0238190a1`](https://github.com/nodejs/node/commit/d0238190a1)] - **doc**: reword possessive form of Node.js in repl.md (Rich Trott) [#31713](https://github.com/nodejs/node/pull/31713) +* [[`55a25b3bbe`](https://github.com/nodejs/node/commit/55a25b3bbe)] - **doc**: reword section title in addons.md (Rich Trott) [#31713](https://github.com/nodejs/node/pull/31713) +* [[`ba9fae058a`](https://github.com/nodejs/node/commit/ba9fae058a)] - **doc**: revise deepEqual() legacy assertion mode text (Rich Trott) [#31704](https://github.com/nodejs/node/pull/31704) +* [[`f6d78f959f`](https://github.com/nodejs/node/commit/f6d78f959f)] - **doc**: improve strict assertion mode color text (Rich Trott) [#31703](https://github.com/nodejs/node/pull/31703) +* [[`22cf3e3d4e`](https://github.com/nodejs/node/commit/22cf3e3d4e)] - **doc**: consolidate introductory text (Rich Trott) [#31667](https://github.com/nodejs/node/pull/31667) +* [[`1e2327d9e6`](https://github.com/nodejs/node/commit/1e2327d9e6)] - **doc**: simplify async\_hooks overview (Rich Trott) [#31660](https://github.com/nodejs/node/pull/31660) +* [[`77ec381ea2`](https://github.com/nodejs/node/commit/77ec381ea2)] - **doc**: clarify Worker exit/message event ordering (Anna Henningsen) [#31642](https://github.com/nodejs/node/pull/31642) +* [[`4b0085c7e3`](https://github.com/nodejs/node/commit/4b0085c7e3)] - **doc**: update TSC name in "Release Process" (Tobias Nießen) [#31652](https://github.com/nodejs/node/pull/31652) +* [[`2e6c737281`](https://github.com/nodejs/node/commit/2e6c737281)] - **doc**: remove .github/ISSUE\_TEMPLATE.md in favor of the template folder (Joyee Cheung) [#31656](https://github.com/nodejs/node/pull/31656) +* [[`b61b85ccf9`](https://github.com/nodejs/node/commit/b61b85ccf9)] - **doc**: add note in BUILDING.md about running `make distclean` (Swagat Konchada) [#31542](https://github.com/nodejs/node/pull/31542) +* [[`2991e7c0e3`](https://github.com/nodejs/node/commit/2991e7c0e3)] - **doc**: correct getting an ArrayBuffer's length (tsabolov) [#31632](https://github.com/nodejs/node/pull/31632) +* [[`e27f24987e`](https://github.com/nodejs/node/commit/e27f24987e)] - **doc**: ask more questions in the bug report template (Joyee Cheung) [#31611](https://github.com/nodejs/node/pull/31611) +* [[`b50a6cc54d`](https://github.com/nodejs/node/commit/b50a6cc54d)] - **doc**: add example to fs.promises.readdir (Conor ONeill) [#31552](https://github.com/nodejs/node/pull/31552) +* [[`1dbe765b0b`](https://github.com/nodejs/node/commit/1dbe765b0b)] - **doc**: add AsyncResource + Worker pool example (Anna Henningsen) [#31601](https://github.com/nodejs/node/pull/31601) +* [[`f40264980e`](https://github.com/nodejs/node/commit/f40264980e)] - **doc**: fix numbering (Steffen) [#31575](https://github.com/nodejs/node/pull/31575) +* [[`3ba0a22c57`](https://github.com/nodejs/node/commit/3ba0a22c57)] - **doc**: clarify socket.setNoDelay() explanation (Rusty Conover) [#31541](https://github.com/nodejs/node/pull/31541) +* [[`faec87b7f1`](https://github.com/nodejs/node/commit/faec87b7f1)] - **doc**: list largepage values in --help (cjihrig) [#31537](https://github.com/nodejs/node/pull/31537) +* [[`2638110cce`](https://github.com/nodejs/node/commit/2638110cce)] - **doc**: clarify require() OS independence (Denys Otrishko) [#31571](https://github.com/nodejs/node/pull/31571) +* [[`7fe9d5ebd4`](https://github.com/nodejs/node/commit/7fe9d5ebd4)] - **doc**: add protocol option in http2.connect() (Michael Lumish) [#31560](https://github.com/nodejs/node/pull/31560) +* [[`6626c4de3c`](https://github.com/nodejs/node/commit/6626c4de3c)] - **doc**: clarify that `v8.serialize()` is not deterministic (Anna Henningsen) [#31548](https://github.com/nodejs/node/pull/31548) +* [[`cde4b51a92`](https://github.com/nodejs/node/commit/cde4b51a92)] - **doc**: update job reference in COLLABORATOR\_GUIDE.md (Richard Lau) [#31557](https://github.com/nodejs/node/pull/31557) +* [[`4cac2cccd6`](https://github.com/nodejs/node/commit/4cac2cccd6)] - **doc**: simultaneous blog and email of sec announce (Sam Roberts) [#31483](https://github.com/nodejs/node/pull/31483) +* [[`e2b3e4e0e3`](https://github.com/nodejs/node/commit/e2b3e4e0e3)] - **doc**: update collaborator guide citgm instructions (Robert Nagy) [#31549](https://github.com/nodejs/node/pull/31549) +* [[`43186e0046`](https://github.com/nodejs/node/commit/43186e0046)] - **doc**: change error message testing policy (Tobias Nießen) [#31421](https://github.com/nodejs/node/pull/31421) +* [[`a52df55b9a`](https://github.com/nodejs/node/commit/a52df55b9a)] - **doc**: remove redundant properties from headers (XhmikosR) [#31492](https://github.com/nodejs/node/pull/31492) +* [[`04d783ae71`](https://github.com/nodejs/node/commit/04d783ae71)] - **doc**: update maintaining-V8.md (kenzo-spaulding) [#31503](https://github.com/nodejs/node/pull/31503) +* [[`f75fe9ab71`](https://github.com/nodejs/node/commit/f75fe9ab71)] - **doc**: enable visual code indication in headers (Rich Trott) [#31493](https://github.com/nodejs/node/pull/31493) +* [[`8f25e51e4e`](https://github.com/nodejs/node/commit/8f25e51e4e)] - **doc**: clean up and streamline vm.md examples (Denys Otrishko) [#31474](https://github.com/nodejs/node/pull/31474) +* [[`729b96137e`](https://github.com/nodejs/node/commit/729b96137e)] - **doc**: further fix async iterator example (Robert Nagy) [#31367](https://github.com/nodejs/node/pull/31367) +* [[`15b24b71ce`](https://github.com/nodejs/node/commit/15b24b71ce)] - **doc**: add ronag to collaborators (Robert Nagy) [#31498](https://github.com/nodejs/node/pull/31498) +* [[`e9462b4d44`](https://github.com/nodejs/node/commit/e9462b4d44)] - **doc**: fix code display in header glitch (Rich Trott) [#31460](https://github.com/nodejs/node/pull/31460) +* [[`b1c745877b`](https://github.com/nodejs/node/commit/b1c745877b)] - **doc**: fix syntax in N-API documentation (Tobias Nießen) [#31466](https://github.com/nodejs/node/pull/31466) +* [[`67d8967f98`](https://github.com/nodejs/node/commit/67d8967f98)] - **doc**: add explanatory to path.resolve description (Yakov Litvin) [#31430](https://github.com/nodejs/node/pull/31430) +* [[`1099524452`](https://github.com/nodejs/node/commit/1099524452)] - **doc**: document process.std\*.fd (Harshitha KP) [#31395](https://github.com/nodejs/node/pull/31395) +* [[`843c5c6f46`](https://github.com/nodejs/node/commit/843c5c6f46)] - **doc**: fix several child\_process doc typos (cjihrig) [#31393](https://github.com/nodejs/node/pull/31393) +* [[`d77099856a`](https://github.com/nodejs/node/commit/d77099856a)] - **doc**: fix a broken link in fs.md (himself65) [#31373](https://github.com/nodejs/node/pull/31373) +* [[`1e08d3c2f1`](https://github.com/nodejs/node/commit/1e08d3c2f1)] - **doc**: correct added version for --abort-on-uncaught-exception (Anna Henningsen) [#31360](https://github.com/nodejs/node/pull/31360) +* [[`6055134db6`](https://github.com/nodejs/node/commit/6055134db6)] - **doc**: explain `hex` encoding in Buffer API (Harshitha KP) [#31352](https://github.com/nodejs/node/pull/31352) +* [[`bd54abe3f7`](https://github.com/nodejs/node/commit/bd54abe3f7)] - **doc**: explain \_writev() API (Harshitha KP) [#31356](https://github.com/nodejs/node/pull/31356) +* [[`91f5e9b0f7`](https://github.com/nodejs/node/commit/91f5e9b0f7)] - **doc**: document missing properties in child\_process (Harshitha KP) [#31342](https://github.com/nodejs/node/pull/31342) +* [[`6874deef28`](https://github.com/nodejs/node/commit/6874deef28)] - **doc,assert**: rename "mode" to "assertion mode" (Rich Trott) [#31635](https://github.com/nodejs/node/pull/31635) +* [[`788ea36ce0`](https://github.com/nodejs/node/commit/788ea36ce0)] - **doc,net**: reword Unix domain path paragraph in net.md (Rich Trott) [#31684](https://github.com/nodejs/node/pull/31684) +* [[`e3e40a12b0`](https://github.com/nodejs/node/commit/e3e40a12b0)] - **doc,util**: revise util.md introductory paragraph (Rich Trott) [#31685](https://github.com/nodejs/node/pull/31685) +* [[`e46cfaf146`](https://github.com/nodejs/node/commit/e46cfaf146)] - **errors**: make use of "cannot" consistent (Tobias Nießen) [#31420](https://github.com/nodejs/node/pull/31420) +* [[`f6392e9fde`](https://github.com/nodejs/node/commit/f6392e9fde)] - **esm**: import.meta.resolve with nodejs: builtins (Guy Bedford) [#31032](https://github.com/nodejs/node/pull/31032) +* [[`21fc81821f`](https://github.com/nodejs/node/commit/21fc81821f)] - **fs**: set path when mkdir recursive called on file (bcoe) [#31607](https://github.com/nodejs/node/pull/31607) +* [[`8669ecc8a2`](https://github.com/nodejs/node/commit/8669ecc8a2)] - **fs**: bail on permission error in recursive directory creation (bcoe) [#31505](https://github.com/nodejs/node/pull/31505) +* [[`2c2b3ba39c`](https://github.com/nodejs/node/commit/2c2b3ba39c)] - **fs**: do not emit 'close' twice if emitClose enabled (Robert Nagy) [#31383](https://github.com/nodejs/node/pull/31383) +* [[`32ac1be372`](https://github.com/nodejs/node/commit/32ac1be372)] - **fs**: unset FileHandle fd after close (Anna Henningsen) [#31389](https://github.com/nodejs/node/pull/31389) +* [[`9ecae58643`](https://github.com/nodejs/node/commit/9ecae58643)] - **lib**: delete dead code in SourceMap (Justin Ridgewell) [#31512](https://github.com/nodejs/node/pull/31512) +* [[`7ecf842429`](https://github.com/nodejs/node/commit/7ecf842429)] - **lib,src**: switch Buffer::kMaxLength to size\_t (Ben Noordhuis) [#31406](https://github.com/nodejs/node/pull/31406) +* [[`15c8d9ead1`](https://github.com/nodejs/node/commit/15c8d9ead1)] - **meta**: move princejwesley to emeritus (Rich Trott) [#31730](https://github.com/nodejs/node/pull/31730) +* [[`f5ae510e03`](https://github.com/nodejs/node/commit/f5ae510e03)] - **meta**: move vkurchatkin to emeritus (Rich Trott) [#31729](https://github.com/nodejs/node/pull/31729) +* [[`cd520ddfef`](https://github.com/nodejs/node/commit/cd520ddfef)] - **meta**: move calvinmetcalf to emeritus (Rich Trott) [#31736](https://github.com/nodejs/node/pull/31736) +* [[`832255df89`](https://github.com/nodejs/node/commit/832255df89)] - **meta**: fix collaborator list errors in README.md (James M Snell) [#31655](https://github.com/nodejs/node/pull/31655) +* [[`aa266628ba`](https://github.com/nodejs/node/commit/aa266628ba)] - **module**: drop support for extensionless main entry points in esm (Geoffrey Booth) [#31415](https://github.com/nodejs/node/pull/31415) +* [[`ca81af7d73`](https://github.com/nodejs/node/commit/ca81af7d73)] - **module**: correct docs about when extensionless files are supported (Geoffrey Booth) [#31415](https://github.com/nodejs/node/pull/31415) +* [[`6797656d86`](https://github.com/nodejs/node/commit/6797656d86)] - **module**: revert #31021 (Geoffrey Booth) [#31415](https://github.com/nodejs/node/pull/31415) +* [[`ae2141effc`](https://github.com/nodejs/node/commit/ae2141effc)] - **n-api**: free instance data as reference (Gabriel Schulhof) [#31638](https://github.com/nodejs/node/pull/31638) +* [[`c8215699ab`](https://github.com/nodejs/node/commit/c8215699ab)] - **n-api**: rename 'promise' parameter to 'value' (Tobias Nießen) [#31544](https://github.com/nodejs/node/pull/31544) +* [[`5982726ef9`](https://github.com/nodejs/node/commit/5982726ef9)] - **net**: track state of setNoDelay() and prevent unnecessary system calls (Rusty Conover) [#31543](https://github.com/nodejs/node/pull/31543) +* [[`e7fea14c7b`](https://github.com/nodejs/node/commit/e7fea14c7b)] - **(SEMVER-MINOR)** **perf_hooks**: add property flags to GCPerformanceEntry (Kirill Fomichev) [#29547](https://github.com/nodejs/node/pull/29547) +* [[`672315651d`](https://github.com/nodejs/node/commit/672315651d)] - **(SEMVER-MINOR)** **process**: report ArrayBuffer memory in `memoryUsage()` (Anna Henningsen) [#31550](https://github.com/nodejs/node/pull/31550) +* [[`cd754337f8`](https://github.com/nodejs/node/commit/cd754337f8)] - **process**: fix two overflow cases in SourceMap VLQ decoding (Justin Ridgewell) [#31490](https://github.com/nodejs/node/pull/31490) +* [[`98f3028c30`](https://github.com/nodejs/node/commit/98f3028c30)] - **readline**: remove intermediate variable (cjihrig) [#31676](https://github.com/nodejs/node/pull/31676) +* [[`148dfde1d4`](https://github.com/nodejs/node/commit/148dfde1d4)] - **(SEMVER-MINOR)** **readline**: make tab size configurable (Ruben Bridgewater) [#31318](https://github.com/nodejs/node/pull/31318) +* [[`1bcf2f9423`](https://github.com/nodejs/node/commit/1bcf2f9423)] - **report**: add support for Workers (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`7c2d33f38f`](https://github.com/nodejs/node/commit/7c2d33f38f)] - **src**: use hex not decimal in IsArrayIndex (Shelley Vohr) [#31758](https://github.com/nodejs/node/pull/31758) +* [[`a095ef0d52`](https://github.com/nodejs/node/commit/a095ef0d52)] - **src**: keep main-thread Isolate attached to platform during Dispose (Anna Henningsen) [#31795](https://github.com/nodejs/node/pull/31795) +* [[`1dec9d196f`](https://github.com/nodejs/node/commit/1dec9d196f)] - **src**: wrap HostPort in ExclusiveAccess (Ben Noordhuis) [#31717](https://github.com/nodejs/node/pull/31717) +* [[`e23023d685`](https://github.com/nodejs/node/commit/e23023d685)] - **src**: add ExclusiveAccess class (Ben Noordhuis) [#31717](https://github.com/nodejs/node/pull/31717) +* [[`54caf76210`](https://github.com/nodejs/node/commit/54caf76210)] - **src**: allow to reuse env options handling (Denys Otrishko) [#31711](https://github.com/nodejs/node/pull/31711) +* [[`6ad8ca5ecf`](https://github.com/nodejs/node/commit/6ad8ca5ecf)] - **src**: do not unnecessarily re-assign uv handle data (Anna Henningsen) [#31696](https://github.com/nodejs/node/pull/31696) +* [[`2837788849`](https://github.com/nodejs/node/commit/2837788849)] - **src**: fix compile warnings in node\_url.cc (Anna Henningsen) [#31689](https://github.com/nodejs/node/pull/31689) +* [[`1d34ab5e43`](https://github.com/nodejs/node/commit/1d34ab5e43)] - **src**: modernized unique\_ptr construction (Yuhanun Citgez) [#31654](https://github.com/nodejs/node/pull/31654) +* [[`0e44902b85`](https://github.com/nodejs/node/commit/0e44902b85)] - **src**: remove dead code in InternalMakeCallback (Gerhard Stoebich) [#31622](https://github.com/nodejs/node/pull/31622) +* [[`348c7871b6`](https://github.com/nodejs/node/commit/348c7871b6)] - **src**: remove fixed-size GetHumanReadableProcessName (Ben Noordhuis) [#31633](https://github.com/nodejs/node/pull/31633) +* [[`8964077935`](https://github.com/nodejs/node/commit/8964077935)] - **src**: fix OOB reads in process.title getter (Ben Noordhuis) [#31633](https://github.com/nodejs/node/pull/31633) +* [[`af612bcc21`](https://github.com/nodejs/node/commit/af612bcc21)] - **src**: various minor improvements to node\_url (James M Snell) [#31651](https://github.com/nodejs/node/pull/31651) +* [[`f0ffa4cb80`](https://github.com/nodejs/node/commit/f0ffa4cb80)] - **src**: fix inspecting `MessagePort` from `init` async hook (Anna Henningsen) [#31600](https://github.com/nodejs/node/pull/31600) +* [[`425662e2d6`](https://github.com/nodejs/node/commit/425662e2d6)] - **src**: remove unused `Worker::child\_port\_` member (Anna Henningsen) [#31599](https://github.com/nodejs/node/pull/31599) +* [[`43e2c2e643`](https://github.com/nodejs/node/commit/43e2c2e643)] - **src**: change Fill() to use ParseArrayIndex() (ConorDavenport) [#31591](https://github.com/nodejs/node/pull/31591) +* [[`42b835412d`](https://github.com/nodejs/node/commit/42b835412d)] - **src**: remove duplicate field env in CryptoJob class (ConorDavenport) [#31554](https://github.com/nodejs/node/pull/31554) +* [[`9fd1e717e6`](https://github.com/nodejs/node/commit/9fd1e717e6)] - **src**: fix console debug output on Windows (Denys Otrishko) [#31580](https://github.com/nodejs/node/pull/31580) +* [[`277980d288`](https://github.com/nodejs/node/commit/277980d288)] - **src**: use \_\_executable\_start for linux hugepages (Ben Noordhuis) [#31547](https://github.com/nodejs/node/pull/31547) +* [[`6d5c3cd7ac`](https://github.com/nodejs/node/commit/6d5c3cd7ac)] - **src**: remove preview for heap dump utilities (Anna Henningsen) [#31570](https://github.com/nodejs/node/pull/31570) +* [[`c167ae0a87`](https://github.com/nodejs/node/commit/c167ae0a87)] - **src**: fix minor typo in base\_object.h (Daniel Bevenius) [#31535](https://github.com/nodejs/node/pull/31535) +* [[`f04576ede0`](https://github.com/nodejs/node/commit/f04576ede0)] - **src**: fix debug crash handling null strings (Rusty Conover) [#31523](https://github.com/nodejs/node/pull/31523) +* [[`ef4d081660`](https://github.com/nodejs/node/commit/ef4d081660)] - **src**: simplify native immediate queue running (Anna Henningsen) [#31502](https://github.com/nodejs/node/pull/31502) +* [[`bc0c1420f0`](https://github.com/nodejs/node/commit/bc0c1420f0)] - **src**: define noreturn attribute for windows (Alexander Smarus) [#31467](https://github.com/nodejs/node/pull/31467) +* [[`9e9dbd44fe`](https://github.com/nodejs/node/commit/9e9dbd44fe)] - **src**: reduce code duplication in BootstrapNode (Denys Otrishko) [#31465](https://github.com/nodejs/node/pull/31465) +* [[`76aad0e5e1`](https://github.com/nodejs/node/commit/76aad0e5e1)] - **src**: use custom fprintf alike to write errors to stderr (Anna Henningsen) [#31446](https://github.com/nodejs/node/pull/31446) +* [[`a685827a55`](https://github.com/nodejs/node/commit/a685827a55)] - **src**: add C++-style sprintf utility (Anna Henningsen) [#31446](https://github.com/nodejs/node/pull/31446) +* [[`049a1727d4`](https://github.com/nodejs/node/commit/049a1727d4)] - **src**: harden running native `SetImmediate()`s slightly (Anna Henningsen) [#31468](https://github.com/nodejs/node/pull/31468) +* [[`f56de5a3b4`](https://github.com/nodejs/node/commit/f56de5a3b4)] - **src**: move MemoryInfo() for worker code to .cc files (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`0cacc1facf`](https://github.com/nodejs/node/commit/0cacc1facf)] - **src**: add interrupts to Environments/Workers (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`f8c45b277f`](https://github.com/nodejs/node/commit/f8c45b277f)] - **src**: remove AsyncRequest (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`600e96ec04`](https://github.com/nodejs/node/commit/600e96ec04)] - **src**: add a threadsafe variant of SetImmediate() (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`74a7cdbe05`](https://github.com/nodejs/node/commit/74a7cdbe05)] - **src**: exclude C++ SetImmediate() from count (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`53e566bc50`](https://github.com/nodejs/node/commit/53e566bc50)] - **src**: better encapsulate native immediate list (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* [[`b8face28e7`](https://github.com/nodejs/node/commit/b8face28e7)] - **src**: reduce large pages code duplication (Gabriel Schulhof) [#31385](https://github.com/nodejs/node/pull/31385) +* [[`83dd65a469`](https://github.com/nodejs/node/commit/83dd65a469)] - **src**: fix ignore GCC -Wcast-function-type for older compilers (Denys Otrishko) [#31524](https://github.com/nodejs/node/pull/31524) +* [[`13c6965703`](https://github.com/nodejs/node/commit/13c6965703)] - **src**: ignore GCC -Wcast-function-type for v8.h (Daniel Bevenius) [#31475](https://github.com/nodejs/node/pull/31475) +* [[`3dd4089b9a`](https://github.com/nodejs/node/commit/3dd4089b9a)] - **(SEMVER-MINOR)** **src,lib**: make ^C print a JS stack trace (legendecas) [#29207](https://github.com/nodejs/node/pull/29207) +* [[`6d0b2267ce`](https://github.com/nodejs/node/commit/6d0b2267ce)] - **stream**: fix finished w/ 'close' before 'finish' (Robert Nagy) [#31534](https://github.com/nodejs/node/pull/31534) +* [[`80e75ab389`](https://github.com/nodejs/node/commit/80e75ab389)] - **stream**: add regression test for async iteration completion (Matteo Collina) [#31508](https://github.com/nodejs/node/pull/31508) +* [[`538582b43d`](https://github.com/nodejs/node/commit/538582b43d)] - ***Revert*** "**stream**: fix async iterator destroyed error propagation" (Matteo Collina) [#31508](https://github.com/nodejs/node/pull/31508) +* [[`f255053033`](https://github.com/nodejs/node/commit/f255053033)] - **stream**: fix finished writable/readable state (Robert Nagy) [#31527](https://github.com/nodejs/node/pull/31527) +* [[`3046648580`](https://github.com/nodejs/node/commit/3046648580)] - **stream**: implement throw for async iterator (Robert Nagy) [#31316](https://github.com/nodejs/node/pull/31316) +* [[`5a95fa4aeb`](https://github.com/nodejs/node/commit/5a95fa4aeb)] - **stream**: normalize async iterator stream destroy (Robert Nagy) [#31316](https://github.com/nodejs/node/pull/31316) +* [[`20d0a0e9a7`](https://github.com/nodejs/node/commit/20d0a0e9a7)] - **stream**: add async iterator support for v1 streams (Robert Nagy) [#31316](https://github.com/nodejs/node/pull/31316) +* [[`0654e6790d`](https://github.com/nodejs/node/commit/0654e6790d)] - **test**: mark test-fs-stat-bigint flaky on FreeBSD (Rich Trott) [#31728](https://github.com/nodejs/node/pull/31728) +* [[`6dbe6bde56`](https://github.com/nodejs/node/commit/6dbe6bde56)] - **test**: fix flaky parallel/test-repl-history-navigation test (Ruben Bridgewater) [#31708](https://github.com/nodejs/node/pull/31708) +* [[`1dae7dc6bc`](https://github.com/nodejs/node/commit/1dae7dc6bc)] - **test**: improve test-fs-stat-bigint (Rich Trott) [#31726](https://github.com/nodejs/node/pull/31726) +* [[`fa9b59276d`](https://github.com/nodejs/node/commit/fa9b59276d)] - **test**: remove flaky designation for test-fs-stat-bigint (Rich Trott) [#30437](https://github.com/nodejs/node/pull/30437) +* [[`d36ba2b555`](https://github.com/nodejs/node/commit/d36ba2b555)] - **test**: fix flaky test-fs-stat-bigint (Duncan Healy) [#30437](https://github.com/nodejs/node/pull/30437) +* [[`5b3c4b3e7d`](https://github.com/nodejs/node/commit/5b3c4b3e7d)] - ***Revert*** "**test**: refactor all benchmark tests to use the new test option" (Anna Henningsen) [#31722](https://github.com/nodejs/node/pull/31722) +* [[`2c0f3028c9`](https://github.com/nodejs/node/commit/2c0f3028c9)] - **test**: add debugging output to test-net-listen-after-destroy-stdin (Rich Trott) [#31698](https://github.com/nodejs/node/pull/31698) +* [[`2224211609`](https://github.com/nodejs/node/commit/2224211609)] - **test**: improve assertion message in test-dns-any (Rich Trott) [#31697](https://github.com/nodejs/node/pull/31697) +* [[`b0e37b7180`](https://github.com/nodejs/node/commit/b0e37b7180)] - **test**: fix flaky test-trace-sigint-on-idle (Anna Henningsen) [#31645](https://github.com/nodejs/node/pull/31645) +* [[`58f17c0e6b`](https://github.com/nodejs/node/commit/58f17c0e6b)] - **test**: stricter assert color test (Ruben Bridgewater) [#31429](https://github.com/nodejs/node/pull/31429) +* [[`89dcf733c6`](https://github.com/nodejs/node/commit/89dcf733c6)] - **test**: improve logged errors (Ruben Bridgewater) [#31425](https://github.com/nodejs/node/pull/31425) +* [[`4878c7a197`](https://github.com/nodejs/node/commit/4878c7a197)] - **test**: refactor all benchmark tests to use the new test option (Ruben Bridgewater) [#31396](https://github.com/nodejs/node/pull/31396) +* [[`3bcc2da887`](https://github.com/nodejs/node/commit/3bcc2da887)] - **test**: fix test-benchmark-http (Rich Trott) [#31686](https://github.com/nodejs/node/pull/31686) +* [[`6139d4ea3b`](https://github.com/nodejs/node/commit/6139d4ea3b)] - **test**: fix flaky test-inspector-connect-main-thread (Anna Henningsen) [#31637](https://github.com/nodejs/node/pull/31637) +* [[`13c256d31d`](https://github.com/nodejs/node/commit/13c256d31d)] - **test**: add test-dns-promises-lookupService (Rich Trott) [#31640](https://github.com/nodejs/node/pull/31640) +* [[`23fefba84c`](https://github.com/nodejs/node/commit/23fefba84c)] - **test**: fix flaky test-http2-stream-destroy-event-order (Anna Henningsen) [#31610](https://github.com/nodejs/node/pull/31610) +* [[`435b9c977a`](https://github.com/nodejs/node/commit/435b9c977a)] - **test**: abstract common assertions in readline-interface test (Ruben Bridgewater) [#31423](https://github.com/nodejs/node/pull/31423) +* [[`d2a12d3af8`](https://github.com/nodejs/node/commit/d2a12d3af8)] - **test**: refactor test-readline-interface.js (Ruben Bridgewater) [#31423](https://github.com/nodejs/node/pull/31423) +* [[`7c3cc94b9f`](https://github.com/nodejs/node/commit/7c3cc94b9f)] - **test**: unset NODE\_OPTIONS for cctest (Anna Henningsen) [#31594](https://github.com/nodejs/node/pull/31594) +* [[`62d0c6029d`](https://github.com/nodejs/node/commit/62d0c6029d)] - **test**: simplify test-https-simple.js (Sam Roberts) [#31584](https://github.com/nodejs/node/pull/31584) +* [[`49be50051c`](https://github.com/nodejs/node/commit/49be50051c)] - **test**: show child stderr output in largepages test (Ben Noordhuis) [#31612](https://github.com/nodejs/node/pull/31612) +* [[`c3247fedd9`](https://github.com/nodejs/node/commit/c3247fedd9)] - **test**: mark additional tests as flaky on Windows (Anna Henningsen) [#31606](https://github.com/nodejs/node/pull/31606) +* [[`3fdec1c790`](https://github.com/nodejs/node/commit/3fdec1c790)] - **test**: fix flaky test-memory-usage (Anna Henningsen) [#31602](https://github.com/nodejs/node/pull/31602) +* [[`23da559ab2`](https://github.com/nodejs/node/commit/23da559ab2)] - **test**: verify threadId in reports (Dylan Coakley) [#31556](https://github.com/nodejs/node/pull/31556) +* [[`5a12cd636b`](https://github.com/nodejs/node/commit/5a12cd636b)] - **test**: remove --experimental-worker flag comment (Harshitha KP) [#31563](https://github.com/nodejs/node/pull/31563) +* [[`07525c317e`](https://github.com/nodejs/node/commit/07525c317e)] - **test**: make test-http2-buffersize more correct (Anna Henningsen) [#31502](https://github.com/nodejs/node/pull/31502) +* [[`c4a2f94a11`](https://github.com/nodejs/node/commit/c4a2f94a11)] - **test**: cover property n-api null cases (Gabriel Schulhof) [#31488](https://github.com/nodejs/node/pull/31488) +* [[`f2dc694805`](https://github.com/nodejs/node/commit/f2dc694805)] - **test**: fix test-heapdump-worker (Anna Henningsen) [#31494](https://github.com/nodejs/node/pull/31494) +* [[`b25ea9b1dc`](https://github.com/nodejs/node/commit/b25ea9b1dc)] - **test**: add tests for main() argument handling (cjihrig) [#31426](https://github.com/nodejs/node/pull/31426) +* [[`38ea53629b`](https://github.com/nodejs/node/commit/38ea53629b)] - **test**: add wasi test for freopen() (cjihrig) [#31432](https://github.com/nodejs/node/pull/31432) +* [[`c2792aad44`](https://github.com/nodejs/node/commit/c2792aad44)] - **test**: remove bluebird remnants from test fixture (Rich Trott) [#31435](https://github.com/nodejs/node/pull/31435) +* [[`583d1d9f55`](https://github.com/nodejs/node/commit/583d1d9f55)] - **test**: improve wasi stat test (cjihrig) [#31413](https://github.com/nodejs/node/pull/31413) +* [[`676b84a803`](https://github.com/nodejs/node/commit/676b84a803)] - **(SEMVER-MINOR)** **test**: skip keygen tests on arm systems (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* [[`099c921f40`](https://github.com/nodejs/node/commit/099c921f40)] - **test**: add wasi test for symlink() and readlink() (cjihrig) [#31403](https://github.com/nodejs/node/pull/31403) +* [[`6256d0ae92`](https://github.com/nodejs/node/commit/6256d0ae92)] - **test**: update postmortem test with v12 constants (Matheus Marchini) [#31391](https://github.com/nodejs/node/pull/31391) +* [[`0bafb5c8c8`](https://github.com/nodejs/node/commit/0bafb5c8c8)] - **test**: export public symbols in addons tests (Ben Noordhuis) [#28717](https://github.com/nodejs/node/pull/28717) +* [[`6833f62e9d`](https://github.com/nodejs/node/commit/6833f62e9d)] - **test**: add promises metadata to postmortem test (Matheus Marchini) [#31357](https://github.com/nodejs/node/pull/31357) +* [[`41524282b5`](https://github.com/nodejs/node/commit/41524282b5)] - **test,benchmark**: fix test-benchmark-zlib (Rich Trott) [#31538](https://github.com/nodejs/node/pull/31538) +* [[`c34872e464`](https://github.com/nodejs/node/commit/c34872e464)] - **test,dns**: add coverage for dns exception (Rich Trott) [#31678](https://github.com/nodejs/node/pull/31678) +* [[`03aac4e65d`](https://github.com/nodejs/node/commit/03aac4e65d)] - **tls**: simplify errors using ThrowCryptoError (Tobias Nießen) [#31436](https://github.com/nodejs/node/pull/31436) +* [[`95d509e974`](https://github.com/nodejs/node/commit/95d509e974)] - **tools**: update Markdown linter to be cross-platform (Derek Lewis) [#31239](https://github.com/nodejs/node/pull/31239) +* [[`328b8a6444`](https://github.com/nodejs/node/commit/328b8a6444)] - **tools**: unify make-v8.sh for ppc64le and s390x (Richard Lau) [#31628](https://github.com/nodejs/node/pull/31628) +* [[`39c86bbe4c`](https://github.com/nodejs/node/commit/39c86bbe4c)] - **tools**: replace deprecated iteritems() for items() (Giovanny Andres Gongora Granada (Gioyik)) [#31528](https://github.com/nodejs/node/pull/31528) +* [[`be55f3ec4f`](https://github.com/nodejs/node/commit/be55f3ec4f)] - **tty**: do not end in an infinite warning recursion (Ruben Bridgewater) [#31429](https://github.com/nodejs/node/pull/31429) +* [[`a0c1ceddbc`](https://github.com/nodejs/node/commit/a0c1ceddbc)] - **util**: throw if unreachable TypedArray checking code is reached (Rich Trott) [#31737](https://github.com/nodejs/node/pull/31737) +* [[`7b9d6d08f4`](https://github.com/nodejs/node/commit/7b9d6d08f4)] - **util**: add coverage for util.inspect.colors alias setter (Rich Trott) [#31743](https://github.com/nodejs/node/pull/31743) +* [[`9f9edc2c78`](https://github.com/nodejs/node/commit/9f9edc2c78)] - **util**: throw if unreachable code is reached (Rich Trott) [#31712](https://github.com/nodejs/node/pull/31712) +* [[`5e1bee817c`](https://github.com/nodejs/node/commit/5e1bee817c)] - **util**: fix inspection of typed arrays with unusual length (Ruben Bridgewater) [#31458](https://github.com/nodejs/node/pull/31458) +* [[`3da4d5174c`](https://github.com/nodejs/node/commit/3da4d5174c)] - **util**: improve unicode support (Ruben Bridgewater) [#31319](https://github.com/nodejs/node/pull/31319) +* [[`822f2ac640`](https://github.com/nodejs/node/commit/822f2ac640)] - **worker**: add support for .cjs extension (Antoine du HAMEL) [#31662](https://github.com/nodejs/node/pull/31662) +* [[`cd99dc7368`](https://github.com/nodejs/node/commit/cd99dc7368)] - **worker**: properly handle env and NODE\_OPTIONS in workers (Denys Otrishko) [#31711](https://github.com/nodejs/node/pull/31711) +* [[`1592c474da`](https://github.com/nodejs/node/commit/1592c474da)] - **worker**: reset `Isolate` stack limit after entering `Locker` (Anna Henningsen) [#31593](https://github.com/nodejs/node/pull/31593) +* [[`3e5803f91b`](https://github.com/nodejs/node/commit/3e5803f91b)] - **worker**: improve MessagePort performance (Anna Henningsen) [#31605](https://github.com/nodejs/node/pull/31605) +* [[`8d3ffbeb55`](https://github.com/nodejs/node/commit/8d3ffbeb55)] - **(SEMVER-MINOR)** **worker**: add ability to take heap snapshot from parent thread (Anna Henningsen) [#31569](https://github.com/nodejs/node/pull/31569) +* [[`6fdef457c6`](https://github.com/nodejs/node/commit/6fdef457c6)] - **worker**: remove redundant closing of child port (aaccttrr) [#31555](https://github.com/nodejs/node/pull/31555) +* [[`5656ec9f71`](https://github.com/nodejs/node/commit/5656ec9f71)] - **worker**: move JoinThread() back into exit callback (Anna Henningsen) [#31468](https://github.com/nodejs/node/pull/31468) + ## 2020-02-06, Version 13.8.0 (Current), @BethGriggs diff --git a/src/node_version.h b/src/node_version.h index 229bf5d2def..ebd3cff606f 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -29,7 +29,7 @@ #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 1 +#define NODE_VERSION_IS_RELEASE 0 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) From 43fb664701d771c00595fa1c152cfca03b10a4fd Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Tue, 18 Feb 2020 14:52:15 -0500 Subject: [PATCH 026/192] doc: fix missing changelog corrections There was a slight discrepancy between the build of 12.16.1 and what landed on 12.x. This was only a couple spelling errors that didn't get updated between machines I was working on. This change gets the changelog up to date with what went out in the release vs what is current on the 12.x release branch. PR-URL: https://github.com/nodejs/node/pull/31854 Reviewed-By: Shelley Vohr Reviewed-By: Beth Griggs Reviewed-By: Gus Caplan Reviewed-By: Richard Lau --- doc/changelogs/CHANGELOG_V12.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/changelogs/CHANGELOG_V12.md b/doc/changelogs/CHANGELOG_V12.md index 36737e2f4fc..d3341b1eb48 100644 --- a/doc/changelogs/CHANGELOG_V12.md +++ b/doc/changelogs/CHANGELOG_V12.md @@ -86,7 +86,7 @@ Semver-Minor release. Changes in async hooks internals introduced a case where an internal api call could be called with undefined causing a process to crash. The change to async hooks was reverted. A regression test and fix has been proposed -and the change could re land in a future Semver-Patch release if the regression is reliably fixed. +and the change could re-land in a future Semver-Patch release if the regression is reliably fixed. **New Enumerable Read-Only Property on EventEmitter breaks @types/extend** @@ -95,14 +95,14 @@ broke existing code that was using the `@types/extend` module for extending clas as `@types/extend` was attemping to write over the existing field which the new change made read-only. As this is the first property on EventEmitter that is read-only this feature could be considered Semver-Major. The new feature has been -reverted but could re land in a future Semver-Minor release if a non breaking way +reverted but could re-land in a future Semver-Minor release if a non breaking way of applying it is found. **Exceptions in the HTTP parser were not emitting an uncaughtException** A refactoring to Node.js interanls resulted in a bug where errors in the HTTP -parser were not being emitted by `process.on('uncaughtException')`. The fix -to this bug has been included in this release. +parser were not being emitted by `process.on('uncaughtException')` when the `async_hooks` `after` +hook exists. The fix to this bug has been included in this release. ### Commits From 31290824de068eb4b282ab74829450b988ae225b Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Tue, 18 Feb 2020 13:13:59 -0800 Subject: [PATCH 027/192] doc: fix notable changes for v13.9.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/31857 Reviewed-By: Beth Griggs Reviewed-By: Anna Henningsen Reviewed-By: Tobias Nießen Reviewed-By: Myles Borins --- doc/changelogs/CHANGELOG_V13.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/changelogs/CHANGELOG_V13.md b/doc/changelogs/CHANGELOG_V13.md index a9dfd87bf10..3f255fb09d2 100644 --- a/doc/changelogs/CHANGELOG_V13.md +++ b/doc/changelogs/CHANGELOG_V13.md @@ -44,14 +44,27 @@ ### Notable changes -* [[`6be51296e4`](https://github.com/nodejs/node/commit/6be51296e4)] - **(SEMVER-MINOR)** **async_hooks**: add executionAsyncResource (Matteo Collina) [#30959](https://github.com/nodejs/node/pull/30959) -* [[`15b24b71ce`](https://github.com/nodejs/node/commit/15b24b71ce)] - **doc**: add ronag to collaborators (Robert Nagy) [#31498](https://github.com/nodejs/node/pull/31498) -* [[`1bcf2f9423`](https://github.com/nodejs/node/commit/1bcf2f9423)] - **report**: add support for Workers (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) -* [[`676b84a803`](https://github.com/nodejs/node/commit/676b84a803)] - **(SEMVER-MINOR)** **test**: skip keygen tests on arm systems (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) -* [[`bf46c304dd`](https://github.com/nodejs/node/commit/bf46c304dd)] - **(SEMVER-MINOR)** **crypto**: add crypto.diffieHellman (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) -* [[`0d3e095941`](https://github.com/nodejs/node/commit/0d3e095941)] - **(SEMVER-MINOR)** **crypto**: add DH support to generateKeyPair (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) -* [[`15bd2c9f0c`](https://github.com/nodejs/node/commit/15bd2c9f0c)] - **(SEMVER-MINOR)** **crypto**: simplify DH groups (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) -* [[`572322fddf`](https://github.com/nodejs/node/commit/572322fddf)] - **(SEMVER-MINOR)** **crypto**: add key type 'dh' (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* **async_hooks** + * add executionAsyncResource (Matteo Collina) [#30959](https://github.com/nodejs/node/pull/30959) +* **crypto** + * add crypto.diffieHellman (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) + * add DH support to generateKeyPair (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) + * simplify DH groups (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) + * add key type 'dh' (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* **test** + * skip keygen tests on arm systems (Tobias Nießen) [#31178](https://github.com/nodejs/node/pull/31178) +* **perf_hooks** + * add property flags to GCPerformanceEntry (Kirill Fomichev) [#29547](https://github.com/nodejs/node/pull/29547) +* **process** + * report ArrayBuffer memory in `memoryUsage()` (Anna Henningsen) [#31550](https://github.com/nodejs/node/pull/31550) +* **readline** + * make tab size configurable (Ruben Bridgewater) [#31318](https://github.com/nodejs/node/pull/31318) +* **report** + * add support for Workers (Anna Henningsen) [#31386](https://github.com/nodejs/node/pull/31386) +* **worker** + * add ability to take heap snapshot from parent thread (Anna Henningsen) [#31569](https://github.com/nodejs/node/pull/31569) +* **added new collaborators** + * add ronag to collaborators (Robert Nagy) [#31498](https://github.com/nodejs/node/pull/31498) ### Commits From 7c524fb092b143470795c8ca869de4654c728fe1 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sat, 15 Feb 2020 14:13:29 +0100 Subject: [PATCH 028/192] doc: fix Writable.write callback description Clarifies a userland invariant until a better solution can be found. Also moves a misplaced sentence from _write to write. Refs: https://github.com/nodejs/node/pull/31756 Refs: https://github.com/nodejs/node/pull/31765 PR-URL: https://github.com/nodejs/node/pull/31812 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- doc/api/stream.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/stream.md b/doc/api/stream.md index cf3ecd461c1..acb859f3920 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -585,8 +585,8 @@ The `writable.write()` method writes some data to the stream, and calls the supplied `callback` once the data has been fully handled. If an error occurs, the `callback` *may or may not* be called with the error as its first argument. To reliably detect write errors, add a listener for the -`'error'` event. If `callback` is called with an error, it will be called -before the `'error'` event is emitted. +`'error'` event. The `callback` is called asynchronously and before `'error'` is +emitted. The return value is `true` if the internal buffer is less than the `highWaterMark` configured when the stream was created after admitting `chunk`. @@ -1890,8 +1890,8 @@ methods only. The `callback` method must be called to signal either that the write completed successfully or failed with an error. The first argument passed to the `callback` must be the `Error` object if the call failed or `null` if the -write succeeded. The `callback` method will always be called asynchronously and -before `'error'` is emitted. +write succeeded. The `callback` must be called synchronously inside of +`writable._write()` or asynchronously (i.e. different tick). All calls to `writable.write()` that occur between the time `writable._write()` is called and the `callback` is called will cause the written data to be From 822101f570478ffa15e4ed0e00cd0d67b9cc789e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 11 Feb 2020 00:42:17 -1000 Subject: [PATCH 029/192] meta: move eljefedelrodeodeljefe to emeritus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eljefedelrodeodeljefe confirmed in email that moving to emeritus was fine at this time. PR-URL: https://github.com/nodejs/node/pull/31735 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Luigi Pinca Reviewed-By: Ruben Bridgewater Reviewed-By: Gireesh Punathil Reviewed-By: Michaël Zasso --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a06c51f22a4..bff42f1114a 100644 --- a/README.md +++ b/README.md @@ -283,8 +283,6 @@ For information about the governance of the Node.js project, see **Hitesh Kanwathirtha** <digitalinfinity@gmail.com> (he/him) * [edsadr](https://github.com/edsadr) - **Adrian Estrada** <edsadr@gmail.com> (he/him) -* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) - -**Robert Jefe Lindstaedt** <robert.lindstaedt@gmail.com> * [eugeneo](https://github.com/eugeneo) - **Eugene Ostroukhov** <eostroukhov@google.com> * [evanlucas](https://github.com/evanlucas) - @@ -458,6 +456,8 @@ For information about the governance of the Node.js project, see **Chris Dickinson** <christopher.s.dickinson@gmail.com> * [DavidCai1993](https://github.com/DavidCai1993) - **David Cai** <davidcai1993@yahoo.com> (he/him) +* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) - +**Robert Jefe Lindstaedt** <robert.lindstaedt@gmail.com> * [estliberitas](https://github.com/estliberitas) - **Alexander Makarenko** <estliberitas@gmail.com> * [firedfox](https://github.com/firedfox) - From e6c2277241e542cdf86475d71c5fdc581bd72025 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 11 Feb 2020 22:49:24 +0800 Subject: [PATCH 030/192] vm: lazily initialize primordials for vm contexts Lazily initialize primordials when cross-context support for builtins is needed to fix the performance regression in context creation. PR-URL: https://github.com/nodejs/node/pull/31738 Fixes: https://github.com/nodejs/node/issues/29842 Reviewed-By: Gus Caplan Reviewed-By: Anna Henningsen Reviewed-By: David Carlier --- src/api/environment.cc | 84 +++++++++++++++++++++--------------------- src/node_contextify.cc | 7 +++- src/node_internals.h | 1 + 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/api/environment.cc b/src/api/environment.cc index 0e1812c369c..d2eaafeb27a 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -405,7 +405,8 @@ MaybeLocal GetPerContextExports(Local context) { return handle_scope.Escape(existing_value.As()); Local exports = Object::New(isolate); - if (context->Global()->SetPrivate(context, key, exports).IsNothing()) + if (context->Global()->SetPrivate(context, key, exports).IsNothing() || + !InitializePrimordials(context)) return MaybeLocal(); return handle_scope.Escape(exports); } @@ -461,49 +462,50 @@ bool InitializeContextForSnapshot(Local context) { context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, True(isolate)); + return InitializePrimordials(context); +} + +bool InitializePrimordials(Local context) { + // Run per-context JS files. + Isolate* isolate = context->GetIsolate(); + Context::Scope context_scope(context); + Local exports; + + Local primordials_string = + FIXED_ONE_BYTE_STRING(isolate, "primordials"); + Local global_string = FIXED_ONE_BYTE_STRING(isolate, "global"); + Local exports_string = FIXED_ONE_BYTE_STRING(isolate, "exports"); + + // Create primordials first and make it available to per-context scripts. + Local primordials = Object::New(isolate); + if (!primordials->SetPrototype(context, Null(isolate)).FromJust() || + !GetPerContextExports(context).ToLocal(&exports) || + !exports->Set(context, primordials_string, primordials).FromJust()) { + return false; + } - { - // Run per-context JS files. - Context::Scope context_scope(context); - Local exports; - - Local primordials_string = - FIXED_ONE_BYTE_STRING(isolate, "primordials"); - Local global_string = FIXED_ONE_BYTE_STRING(isolate, "global"); - Local exports_string = FIXED_ONE_BYTE_STRING(isolate, "exports"); - - // Create primordials first and make it available to per-context scripts. - Local primordials = Object::New(isolate); - if (!primordials->SetPrototype(context, Null(isolate)).FromJust() || - !GetPerContextExports(context).ToLocal(&exports) || - !exports->Set(context, primordials_string, primordials).FromJust()) { + static const char* context_files[] = {"internal/per_context/primordials", + "internal/per_context/domexception", + "internal/per_context/messageport", + nullptr}; + + for (const char** module = context_files; *module != nullptr; module++) { + std::vector> parameters = { + global_string, exports_string, primordials_string}; + Local arguments[] = {context->Global(), exports, primordials}; + MaybeLocal maybe_fn = + native_module::NativeModuleEnv::LookupAndCompile( + context, *module, ¶meters, nullptr); + if (maybe_fn.IsEmpty()) { return false; } - - static const char* context_files[] = {"internal/per_context/primordials", - "internal/per_context/domexception", - "internal/per_context/messageport", - nullptr}; - - for (const char** module = context_files; *module != nullptr; module++) { - std::vector> parameters = { - global_string, exports_string, primordials_string}; - Local arguments[] = {context->Global(), exports, primordials}; - MaybeLocal maybe_fn = - native_module::NativeModuleEnv::LookupAndCompile( - context, *module, ¶meters, nullptr); - if (maybe_fn.IsEmpty()) { - return false; - } - Local fn = maybe_fn.ToLocalChecked(); - MaybeLocal result = - fn->Call(context, Undefined(isolate), - arraysize(arguments), arguments); - // Execution failed during context creation. - // TODO(joyeecheung): deprecate this signature and return a MaybeLocal. - if (result.IsEmpty()) { - return false; - } + Local fn = maybe_fn.ToLocalChecked(); + MaybeLocal result = + fn->Call(context, Undefined(isolate), arraysize(arguments), arguments); + // Execution failed during context creation. + // TODO(joyeecheung): deprecate this signature and return a MaybeLocal. + if (result.IsEmpty()) { + return false; } } diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 46a1d7c8ef0..5f289aecb34 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -185,8 +185,11 @@ MaybeLocal ContextifyContext::CreateV8Context( object_template->SetHandler(config); object_template->SetHandler(indexed_config); - - Local ctx = NewContext(env->isolate(), object_template); + Local ctx = Context::New(env->isolate(), nullptr, object_template); + if (ctx.IsEmpty()) return MaybeLocal(); + // Only partially initialize the context - the primordials are left out + // and only initialized when necessary. + InitializeContextRuntime(ctx); if (ctx.IsEmpty()) { return MaybeLocal(); diff --git a/src/node_internals.h b/src/node_internals.h index 8d63f023c1f..91ba2a58a75 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -99,6 +99,7 @@ std::string GetProcessTitle(const char* default_title); std::string GetHumanReadableProcessName(); void InitializeContextRuntime(v8::Local); +bool InitializePrimordials(v8::Local context); namespace task_queue { void PromiseRejectCallback(v8::PromiseRejectMessage message); From b8e41774d4287d128a40f7ecfecf170fe16fe9ed Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 28 Jan 2020 08:29:29 -0800 Subject: [PATCH 031/192] fs: add fs/promises alias module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/31553 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Myles Borins Reviewed-By: Rich Trott Reviewed-By: James M Snell Reviewed-By: Tobias Nießen Reviewed-By: Yuta Hiroto --- doc/api/fs.md | 2 +- lib/fs/promises.js | 3 +++ node.gyp | 1 + test/es-module/test-esm-fs-promises.mjs | 5 +++++ test/parallel/test-fs-promises-exists.js | 6 ++++++ 5 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 lib/fs/promises.js create mode 100644 test/es-module/test-esm-fs-promises.mjs create mode 100644 test/parallel/test-fs-promises-exists.js diff --git a/doc/api/fs.md b/doc/api/fs.md index bb32e27fbf2..7c5c3677868 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -4237,7 +4237,7 @@ this API: [`fs.writev()`][]. The `fs.promises` API provides an alternative set of asynchronous file system methods that return `Promise` objects rather than using callbacks. The -API is accessible via `require('fs').promises`. +API is accessible via `require('fs').promises` or `require('fs/promises')`. ### class: `FileHandle` + +The TLS socket must be connected and securily established. Ensure the 'secure' +event is emitted, before you continue. + ### `ERR_TLS_INVALID_PROTOCOL_METHOD` diff --git a/doc/api/tls.md b/doc/api/tls.md index fee6e33d610..3341e6e9ea5 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -1094,6 +1094,39 @@ See [SSL_get_shared_sigalgs](https://www.openssl.org/docs/man1.1.1/man3/SSL_get_shared_sigalgs.html) for more information. +### `tlsSocket.exportKeyingMaterial(length, label[, context])` + + +* `length` {number} number of bytes to retrieve from keying material +* `label` {string} an application specific label, typically this will be a +value from the +[IANA Exporter Label Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#exporter-labels). +* `context` {Buffer} Optionally provide a context. + +* Returns: {Buffer} requested bytes of the keying material + +Keying material is used for validations to prevent different kind of attacks in +network protocols, for example in the specifications of IEEE 802.1X. + +Example + +```js +const keyingMaterial = tlsSocket.exportKeyingMaterial( + 128, + 'client finished'); + +/** + Example return value of keyingMaterial: + +*/ +``` +See the OpenSSL [`SSL_export_keying_material`][] documentation for more +information. + ### `tlsSocket.getTLSTicket()` +`--perf-basic-prof-only-functions`, `--perf-basic-prof`, +`--perf-prof-unwinding-info`, and `--perf-prof` are only available on Linux. + ### `NODE_PATH=path[:…]` + +This class is used to create asynchronous state within callbacks and promise +chains. It allows storing data throughout the lifetime of a web request +or any other asynchronous duration. It is similar to thread-local storage +in other languages. + +The following example builds a logger that will always know the current HTTP +request and uses it to display enhanced logs without needing to explicitly +provide the current HTTP request to it. + +```js +const { AsyncLocalStorage } = require('async_hooks'); +const http = require('http'); + +const kReq = 'CURRENT_REQUEST'; +const asyncLocalStorage = new AsyncLocalStorage(); + +function log(...args) { + const store = asyncLocalStorage.getStore(); + // Make sure the store exists and it contains a request. + if (store && store.has(kReq)) { + const req = store.get(kReq); + // Prints `GET /items ERR could not do something + console.log(req.method, req.url, ...args); + } else { + console.log(...args); + } +} + +http.createServer((request, response) => { + asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set(kReq, request); + someAsyncOperation((err, result) => { + if (err) { + log('ERR', err.message); + } + }); + }); +}) +.listen(8080); +``` + +When having multiple instances of `AsyncLocalStorage`, they are independent +from each other. It is safe to instantiate this class multiple times. + +### `new AsyncLocalStorage()` + + +Creates a new instance of `AsyncLocalStorage`. Store is only provided within a +`run` or a `runSyncAndReturn` method call. + +### `asyncLocalStorage.disable()` + + +This method disables the instance of `AsyncLocalStorage`. All subsequent calls +to `asyncLocalStorage.getStore()` will return `undefined` until +`asyncLocalStorage.run()` or `asyncLocalStorage.runSyncAndReturn()` +is called again. + +When calling `asyncLocalStorage.disable()`, all current contexts linked to the +instance will be exited. + +Calling `asyncLocalStorage.disable()` is required before the +`asyncLocalStorage` can be garbage collected. This does not apply to stores +provided by the `asyncLocalStorage`, as those objects are garbage collected +along with the corresponding async resources. + +This method is to be used when the `asyncLocalStorage` is not in use anymore +in the current process. + +### `asyncLocalStorage.getStore()` + + +* Returns: {Map} + +This method returns the current store. +If this method is called outside of an asynchronous context initialized by +calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will +return `undefined`. + +### `asyncLocalStorage.run(callback[, ...args])` + + +* `callback` {Function} +* `...args` {any} + +Calling `asyncLocalStorage.run(callback)` will create a new asynchronous +context. +Within the callback function and the asynchronous operations from the callback, +`asyncLocalStorage.getStore()` will return an instance of `Map` known as +"the store". This store will be persistent through the following +asynchronous calls. + +The callback will be ran asynchronously. Optionally, arguments can be passed +to the function. They will be passed to the callback function. + +If an error is thrown by the callback function, it will not be caught by +a `try/catch` block as the callback is ran in a new asynchronous resource. +Also, the stacktrace will be impacted by the asynchronous call. + +Example: + +```js +asyncLocalStorage.run(() => { + asyncLocalStorage.getStore(); // Returns a Map + someAsyncOperation(() => { + asyncLocalStorage.getStore(); // Returns the same Map + }); +}); +asyncLocalStorage.getStore(); // Returns undefined +``` + +### `asyncLocalStorage.exit(callback[, ...args])` + + +* `callback` {Function} +* `...args` {any} + +Calling `asyncLocalStorage.exit(callback)` will create a new asynchronous +context. +Within the callback function and the asynchronous operations from the callback, +`asyncLocalStorage.getStore()` will return `undefined`. + +The callback will be ran asynchronously. Optionally, arguments can be passed +to the function. They will be passed to the callback function. + +If an error is thrown by the callback function, it will not be caught by +a `try/catch` block as the callback is ran in a new asynchronous resource. +Also, the stacktrace will be impacted by the asynchronous call. + +Example: + +```js +asyncLocalStorage.run(() => { + asyncLocalStorage.getStore(); // Returns a Map + asyncLocalStorage.exit(() => { + asyncLocalStorage.getStore(); // Returns undefined + }); + asyncLocalStorage.getStore(); // Returns the same Map +}); +``` + +### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])` + + +* `callback` {Function} +* `...args` {any} + +This methods runs a function synchronously within a context and return its +return value. The store is not accessible outside of the callback function or +the asynchronous operations created within the callback. + +Optionally, arguments can be passed to the function. They will be passed to +the callback function. + +If the callback function throws an error, it will be thrown by +`runSyncAndReturn` too. The stacktrace will not be impacted by this call and +the context will be exited. + +Example: + +```js +try { + asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.getStore(); // Returns a Map + throw new Error(); + }); +} catch (e) { + asyncLocalStorage.getStore(); // Returns undefined + // The error will be caught here +} +``` + +### `asyncLocalStorage.exitSyncAndReturn(callback[, ...args])` + + +* `callback` {Function} +* `...args` {any} + +This methods runs a function synchronously outside of a context and return its +return value. The store is not accessible within the callback function or +the asynchronous operations created within the callback. + +Optionally, arguments can be passed to the function. They will be passed to +the callback function. + +If the callback function throws an error, it will be thrown by +`exitSyncAndReturn` too. The stacktrace will not be impacted by this call and +the context will be re-entered. + +Example: + +```js +// Within a call to run or runSyncAndReturn +try { + asyncLocalStorage.getStore(); // Returns a Map + asyncLocalStorage.exitSyncAndReturn(() => { + asyncLocalStorage.getStore(); // Returns undefined + throw new Error(); + }); +} catch (e) { + asyncLocalStorage.getStore(); // Returns the same Map + // The error will be caught here +} +``` + +### Choosing between `run` and `runSyncAndReturn` + +#### When to choose `run` + +`run` is asynchronous. It is called with a callback function that +runs within a new asynchronous call. This is the most explicit behavior as +everything that is executed within the callback of `run` (including further +asynchronous operations) will have access to the store. + +If an instance of `AsyncLocalStorage` is used for error management (for +instance, with `process.setUncaughtExceptionCaptureCallback`), only +exceptions thrown in the scope of the callback function will be associated +with the context. + +This method is the safest as it provides strong scoping and consistent +behavior. + +It cannot be promisified using `util.promisify`. If needed, the `Promise` +constructor can be used: + +```js +new Promise((resolve, reject) => { + asyncLocalStorage.run(() => { + someFunction((err, result) => { + if (err) { + return reject(err); + } + return resolve(result); + }); + }); +}); +``` + +#### When to choose `runSyncAndReturn` + +`runSyncAndReturn` is synchronous. The callback function will be executed +synchronously and its return value will be returned by `runSyncAndReturn`. +The store will only be accessible from within the callback +function and the asynchronous operations created within this scope. +If the callback throws an error, `runSyncAndReturn` will throw it and it will +not be associated with the context. + +This method provides good scoping while being synchronous. + +#### Usage with `async/await` + +If, within an async function, only one `await` call is to run within a context, +the following pattern should be used: + +```js +async function fn() { + await asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.getStore().set('key', value); + return foo(); // The return value of foo will be awaited + }); +} +``` + +In this example, the store is only available in the callback function and the +functions called by `foo`. Outside of `runSyncAndReturn`, calling `getStore` +will return `undefined`. + [`after` callback]: #async_hooks_after_asyncid [`before` callback]: #async_hooks_before_asyncid [`destroy` callback]: #async_hooks_destroy_asyncid diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 3ebc9af473d..23f8ddde671 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -1,9 +1,11 @@ 'use strict'; const { + Map, NumberIsSafeInteger, ReflectApply, Symbol, + } = primordials; const { @@ -209,11 +211,102 @@ class AsyncResource { } } +const storageList = []; +const storageHook = createHook({ + init(asyncId, type, triggerAsyncId, resource) { + const currentResource = executionAsyncResource(); + // Value of currentResource is always a non null object + for (let i = 0; i < storageList.length; ++i) { + storageList[i]._propagate(resource, currentResource); + } + } +}); + +class AsyncLocalStorage { + constructor() { + this.kResourceStore = Symbol('kResourceStore'); + this.enabled = false; + } + + disable() { + if (this.enabled) { + this.enabled = false; + // If this.enabled, the instance must be in storageList + storageList.splice(storageList.indexOf(this), 1); + if (storageList.length === 0) { + storageHook.disable(); + } + } + } + + // Propagate the context from a parent resource to a child one + _propagate(resource, triggerResource) { + const store = triggerResource[this.kResourceStore]; + if (this.enabled) { + resource[this.kResourceStore] = store; + } + } + + _enter() { + if (!this.enabled) { + this.enabled = true; + storageList.push(this); + storageHook.enable(); + } + const resource = executionAsyncResource(); + resource[this.kResourceStore] = new Map(); + } + + _exit() { + const resource = executionAsyncResource(); + if (resource) { + resource[this.kResourceStore] = undefined; + } + } + + runSyncAndReturn(callback, ...args) { + this._enter(); + try { + return callback(...args); + } finally { + this._exit(); + } + } + + exitSyncAndReturn(callback, ...args) { + this.enabled = false; + try { + return callback(...args); + } finally { + this.enabled = true; + } + } + + getStore() { + const resource = executionAsyncResource(); + if (this.enabled) { + return resource[this.kResourceStore]; + } + } + + run(callback, ...args) { + this._enter(); + process.nextTick(callback, ...args); + this._exit(); + } + + exit(callback, ...args) { + this.enabled = false; + process.nextTick(callback, ...args); + this.enabled = true; + } +} // Placing all exports down here because the exported classes won't export // otherwise. module.exports = { // Public API + AsyncLocalStorage, createHook, executionAsyncId, triggerAsyncId, diff --git a/test/async-hooks/test-async-local-storage-args.js b/test/async-hooks/test-async-local-storage-args.js new file mode 100644 index 00000000000..91a3385e6ee --- /dev/null +++ b/test/async-hooks/test-async-local-storage-args.js @@ -0,0 +1,20 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run((runArg) => { + assert.strictEqual(runArg, 1); + asyncLocalStorage.exit((exitArg) => { + assert.strictEqual(exitArg, 2); + }, 2); +}, 1); + +asyncLocalStorage.runSyncAndReturn((runArg) => { + assert.strictEqual(runArg, 'foo'); + asyncLocalStorage.exitSyncAndReturn((exitArg) => { + assert.strictEqual(exitArg, 'bar'); + }, 'bar'); +}, 'foo'); diff --git a/test/async-hooks/test-async-local-storage-async-await.js b/test/async-hooks/test-async-local-storage-async-await.js new file mode 100644 index 00000000000..28c8488da62 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-async-await.js @@ -0,0 +1,19 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +async function test() { + asyncLocalStorage.getStore().set('foo', 'bar'); + await Promise.resolve(); + assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar'); +} + +async function main() { + await asyncLocalStorage.runSyncAndReturn(test); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); +} + +main(); diff --git a/test/async-hooks/test-async-local-storage-async-functions.js b/test/async-hooks/test-async-local-storage-async-functions.js new file mode 100644 index 00000000000..89ac0be62c7 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-async-functions.js @@ -0,0 +1,27 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +async function foo() {} + +const asyncLocalStorage = new AsyncLocalStorage(); + +async function testOut() { + await foo(); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); +} + +async function testAwait() { + await foo(); + assert.notStrictEqual(asyncLocalStorage.getStore(), undefined); + assert.strictEqual(asyncLocalStorage.getStore().get('key'), 'value'); + await asyncLocalStorage.exitSyncAndReturn(testOut); +} + +asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set('key', 'value'); + testAwait(); // should not reject +}); +assert.strictEqual(asyncLocalStorage.getStore(), undefined); diff --git a/test/async-hooks/test-async-local-storage-enable-disable.js b/test/async-hooks/test-async-local-storage-enable-disable.js new file mode 100644 index 00000000000..c30d72eb805 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-enable-disable.js @@ -0,0 +1,21 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.getStore().set('foo', 'bar'); + process.nextTick(() => { + assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar'); + asyncLocalStorage.disable(); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + process.nextTick(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + asyncLocalStorage.runSyncAndReturn(() => { + assert.notStrictEqual(asyncLocalStorage.getStore(), undefined); + }); + }); + }); +}); diff --git a/test/async-hooks/test-async-local-storage-errors-async.js b/test/async-hooks/test-async-local-storage-errors-async.js new file mode 100644 index 00000000000..c782b383e9c --- /dev/null +++ b/test/async-hooks/test-async-local-storage-errors-async.js @@ -0,0 +1,26 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +// case 1 fully async APIS (safe) +const asyncLocalStorage = new AsyncLocalStorage(); + +let i = 0; +process.setUncaughtExceptionCaptureCallback((err) => { + ++i; + assert.strictEqual(err.message, 'err' + i); + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node'); +}); + +asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set('hello', 'node'); + setTimeout(() => { + process.nextTick(() => { + assert.strictEqual(i, 2); + }); + throw new Error('err2'); + }, 0); + throw new Error('err1'); +}); diff --git a/test/async-hooks/test-async-local-storage-errors-sync-ret.js b/test/async-hooks/test-async-local-storage-errors-sync-ret.js new file mode 100644 index 00000000000..f112df2b99d --- /dev/null +++ b/test/async-hooks/test-async-local-storage-errors-sync-ret.js @@ -0,0 +1,31 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +// case 2 using *AndReturn calls (dual behaviors) +const asyncLocalStorage = new AsyncLocalStorage(); + +let i = 0; +process.setUncaughtExceptionCaptureCallback((err) => { + ++i; + assert.strictEqual(err.message, 'err2'); + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node'); +}); + +try { + asyncLocalStorage.runSyncAndReturn(() => { + const store = asyncLocalStorage.getStore(); + store.set('hello', 'node'); + setTimeout(() => { + process.nextTick(() => { + assert.strictEqual(i, 1); + }); + throw new Error('err2'); + }, 0); + throw new Error('err1'); + }); +} catch (e) { + assert.strictEqual(e.message, 'err1'); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); +} diff --git a/test/async-hooks/test-async-local-storage-http.js b/test/async-hooks/test-async-local-storage-http.js new file mode 100644 index 00000000000..9f107148402 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-http.js @@ -0,0 +1,21 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); +const http = require('http'); + +const asyncLocalStorage = new AsyncLocalStorage(); +const server = http.createServer((req, res) => { + res.end('ok'); +}); + +server.listen(0, () => { + asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set('hello', 'world'); + http.get({ host: 'localhost', port: server.address().port }, () => { + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); + server.close(); + }); + }); +}); diff --git a/test/async-hooks/test-async-local-storage-nested.js b/test/async-hooks/test-async-local-storage-nested.js new file mode 100644 index 00000000000..38330fff607 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-nested.js @@ -0,0 +1,22 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +setTimeout(() => { + asyncLocalStorage.run(() => { + const asyncLocalStorage2 = new AsyncLocalStorage(); + asyncLocalStorage2.run(() => { + const store = asyncLocalStorage.getStore(); + const store2 = asyncLocalStorage2.getStore(); + store.set('hello', 'world'); + store2.set('hello', 'foo'); + setTimeout(() => { + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); + assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); + }, 200); + }); + }); +}, 100); diff --git a/test/async-hooks/test-async-local-storage-no-mix-contexts.js b/test/async-hooks/test-async-local-storage-no-mix-contexts.js new file mode 100644 index 00000000000..561df546d4a --- /dev/null +++ b/test/async-hooks/test-async-local-storage-no-mix-contexts.js @@ -0,0 +1,38 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); +const asyncLocalStorage2 = new AsyncLocalStorage(); + +setTimeout(() => { + asyncLocalStorage.run(() => { + asyncLocalStorage2.run(() => { + const store = asyncLocalStorage.getStore(); + const store2 = asyncLocalStorage2.getStore(); + store.set('hello', 'world'); + store2.set('hello', 'foo'); + setTimeout(() => { + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); + assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); + asyncLocalStorage.exit(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); + }); + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); + assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); + }, 200); + }); + }); +}, 100); + +setTimeout(() => { + asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set('hello', 'earth'); + setTimeout(() => { + assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'earth'); + }, 100); + }); +}, 100); diff --git a/test/async-hooks/test-async-local-storage-promises.js b/test/async-hooks/test-async-local-storage-promises.js new file mode 100644 index 00000000000..3b05d0f1981 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-promises.js @@ -0,0 +1,28 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +async function main() { + const asyncLocalStorage = new AsyncLocalStorage(); + const err = new Error(); + const next = () => Promise.resolve() + .then(() => { + assert.strictEqual(asyncLocalStorage.getStore().get('a'), 1); + throw err; + }); + await new Promise((resolve, reject) => { + asyncLocalStorage.run(() => { + const store = asyncLocalStorage.getStore(); + store.set('a', 1); + next().then(resolve, reject); + }); + }) + .catch((e) => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + assert.strictEqual(e, err); + }); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); +} + +main(); From db28739aed31b2ad6e85c497e3a836a29d13021c Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Fri, 21 Feb 2020 12:50:34 +0100 Subject: [PATCH 048/192] stream: fix broken pipeline error propagation If the destination was an async function any error thrown from that function would be swallowed. PR-URL: https://github.com/nodejs/node/pull/31835 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Matteo Collina Reviewed-By: Denys Otrishko --- lib/internal/streams/pipeline.js | 16 ++++++------ .../parallel/test-stream-pipeline-uncaught.js | 25 +++++++++++++++++++ test/parallel/test-stream-pipeline.js | 6 +---- 3 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 test/parallel/test-stream-pipeline-uncaught.js diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index 1ead5cdf9f3..d855c2a08af 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -132,9 +132,10 @@ function pipeline(...streams) { } let error; + let value; const destroys = []; - function finish(err, val, final) { + function finish(err, final) { if (!error && err) { error = err; } @@ -146,13 +147,13 @@ function pipeline(...streams) { } if (final) { - callback(error, val); + callback(error, value); } } function wrap(stream, reading, writing, final) { destroys.push(destroyer(stream, reading, writing, (err) => { - finish(err, null, final); + finish(err, final); })); } @@ -198,11 +199,10 @@ function pipeline(...streams) { if (isPromise(ret)) { ret .then((val) => { + value = val; pt.end(val); - finish(null, val, true); - }) - .catch((err) => { - finish(err, null, true); + }, (err) => { + pt.destroy(err); }); } else if (isIterable(ret, true)) { pump(ret, pt, finish); @@ -212,7 +212,7 @@ function pipeline(...streams) { } ret = pt; - wrap(ret, true, false, true); + wrap(ret, false, true, true); } } else if (isStream(stream)) { if (isReadable(ret)) { diff --git a/test/parallel/test-stream-pipeline-uncaught.js b/test/parallel/test-stream-pipeline-uncaught.js new file mode 100644 index 00000000000..90d141ec44f --- /dev/null +++ b/test/parallel/test-stream-pipeline-uncaught.js @@ -0,0 +1,25 @@ +'use strict'; + +const common = require('../common'); +const { + pipeline, + PassThrough +} = require('stream'); +const assert = require('assert'); + +process.on('uncaughtException', common.mustCall((err) => { + assert.strictEqual(err.message, 'error'); +})); + +// Ensure that pipeline that ends with Promise +// still propagates error to uncaughtException. +const s = new PassThrough(); +s.end('data'); +pipeline(s, async function(source) { + for await (const chunk of source) { + chunk; + } +}, common.mustCall((err) => { + assert.ifError(err); + throw new Error('error'); +})); diff --git a/test/parallel/test-stream-pipeline.js b/test/parallel/test-stream-pipeline.js index 19fc246e2bf..b3d4064c6a9 100644 --- a/test/parallel/test-stream-pipeline.js +++ b/test/parallel/test-stream-pipeline.js @@ -613,11 +613,9 @@ const { promisify } = require('util'); yield 'hello'; yield 'world'; }, async function*(source) { - const ret = []; for await (const chunk of source) { - ret.push(chunk.toUpperCase()); + yield chunk.toUpperCase(); } - yield ret; }, async function(source) { let ret = ''; for await (const chunk of source) { @@ -754,7 +752,6 @@ const { promisify } = require('util'); }, common.mustCall((err) => { assert.strictEqual(err, undefined); assert.strictEqual(ret, 'asd'); - assert.strictEqual(s.destroyed, true); })); } @@ -775,7 +772,6 @@ const { promisify } = require('util'); }, common.mustCall((err) => { assert.strictEqual(err, undefined); assert.strictEqual(ret, 'asd'); - assert.strictEqual(s.destroyed, true); })); } From be2f3a3bf88fd728799d1917f7d82428f53183f0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 9 Jan 2020 01:14:44 -0600 Subject: [PATCH 049/192] doc: update assert.rejects() docs with a validation function example Spawned from my own struggle to use in https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1702#note_268452483 PR-URL: https://github.com/nodejs/node/pull/31271 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig --- doc/api/assert.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/api/assert.md b/doc/api/assert.md index bb9df480ddf..732b4ba8e02 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -1134,6 +1134,21 @@ if the `asyncFn` fails to reject. })(); ``` +```js +(async () => { + await assert.rejects( + async () => { + throw new TypeError('Wrong value'); + }, + (err) => { + assert.strictEqual(err.name, 'TypeError'); + assert.strictEqual(err.message, 'Wrong value'); + return true; + } + ); +})(); +``` + ```js assert.rejects( Promise.reject(new Error('Wrong value')), From 2035e3d6cb3071dfbe142f8351cde3d883626a38 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 6 Dec 2019 13:45:40 +0100 Subject: [PATCH 050/192] src: move BaseObject subclass dtors/ctors out of node_crypto.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Originally landed in the QUIC repo Move constructor and destructors for subclasses of `BaseObject` from node_crypto.h to node_crypto.cc. This removes the need to include base_object-inl.h when using node_crypto.h in some cases. Original review metadata: ``` PR-URL: https://github.com/nodejs/quic/pull/220 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell ``` PR-URL: https://github.com/nodejs/node/pull/31872 Reviewed-By: Sam Roberts Reviewed-By: David Carlier Reviewed-By: Colin Ihrig Reviewed-By: Denys Otrishko Reviewed-By: Tobias Nießen --- src/node_crypto.cc | 79 +++++++++++++++++++++++++++++++++++++ src/node_crypto.h | 98 +++++++++------------------------------------- 2 files changed, 97 insertions(+), 80 deletions(-) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index e129c7f3f59..a8a067086f6 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -531,6 +531,24 @@ void SecureContext::Initialize(Environment* env, Local target) { env->set_secure_context_constructor_template(t); } +SecureContext::SecureContext(Environment* env, v8::Local wrap) + : BaseObject(env, wrap) { + MakeWeak(); + env->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); +} + +inline void SecureContext::Reset() { + if (ctx_ != nullptr) { + env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); + } + ctx_.reset(); + cert_.reset(); + issuer_.reset(); +} + +SecureContext::~SecureContext() { + Reset(); +} void SecureContext::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -3854,6 +3872,15 @@ KeyType KeyObject::GetKeyType() const { return this->key_type_; } +KeyObject::KeyObject(Environment* env, + v8::Local wrap, + KeyType key_type) + : BaseObject(env, wrap), + key_type_(key_type), + symmetric_key_(nullptr, nullptr) { + MakeWeak(); +} + void KeyObject::Init(const FunctionCallbackInfo& args) { KeyObject* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); @@ -3998,6 +4025,17 @@ MaybeLocal KeyObject::ExportPrivateKey( return WritePrivateKey(env(), asymmetric_key_.get(), config); } +CipherBase::CipherBase(Environment* env, + v8::Local wrap, + CipherKind kind) + : BaseObject(env, wrap), + ctx_(nullptr), + kind_(kind), + auth_tag_state_(kAuthTagUnknown), + auth_tag_len_(kNoAuthTagLength), + pending_auth_failed_(false) { + MakeWeak(); +} void CipherBase::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -4620,6 +4658,11 @@ void CipherBase::Final(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked()); } +Hmac::Hmac(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + ctx_(nullptr) { + MakeWeak(); +} void Hmac::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -4739,6 +4782,13 @@ void Hmac::HmacDigest(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(rc.ToLocalChecked()); } +Hash::Hash(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + mdctx_(nullptr), + has_md_(false), + md_value_(nullptr) { + MakeWeak(); +} void Hash::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -4753,6 +4803,10 @@ void Hash::Initialize(Environment* env, Local target) { t->GetFunction(env->context()).ToLocalChecked()).Check(); } +Hash::~Hash() { + if (md_value_ != nullptr) + OPENSSL_clear_free(md_value_, md_len_); +} void Hash::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -4977,6 +5031,10 @@ void CheckThrow(Environment* env, SignBase::Error error) { } } +SignBase::SignBase(Environment* env, v8::Local wrap) + : BaseObject(env, wrap) { +} + void SignBase::CheckThrow(SignBase::Error error) { node::crypto::CheckThrow(env(), error); } @@ -5000,6 +5058,9 @@ static bool ApplyRSAOptions(const ManagedEVPPKey& pkey, } +Sign::Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { + MakeWeak(); +} void Sign::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -5320,6 +5381,11 @@ void SignOneShot(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked()); } +Verify::Verify(Environment* env, v8::Local wrap) : + SignBase(env, wrap) { + MakeWeak(); +} + void Verify::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -5623,6 +5689,10 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked()); } +DiffieHellman::DiffieHellman(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), verifyError_(0) { + MakeWeak(); +} void DiffieHellman::Initialize(Environment* env, Local target) { auto make = [&] (Local name, FunctionCallback callback) { @@ -5992,6 +6062,15 @@ void ECDH::Initialize(Environment* env, Local target) { t->GetFunction(env->context()).ToLocalChecked()).Check(); } +ECDH::ECDH(Environment* env, v8::Local wrap, ECKeyPointer&& key) + : BaseObject(env, wrap), + key_(std::move(key)), + group_(EC_KEY_get0_group(key_.get())) { + MakeWeak(); + CHECK_NOT_NULL(group_); +} + +ECDH::~ECDH() {} void ECDH::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/node_crypto.h b/src/node_crypto.h index b1270789b18..655605290b0 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -84,11 +84,9 @@ extern void UseExtraCaCerts(const std::string& file); void InitCryptoOnce(); -class SecureContext : public BaseObject { +class SecureContext final : public BaseObject { public: - ~SecureContext() override { - Reset(); - } + ~SecureContext() override; static void Initialize(Environment* env, v8::Local target); @@ -177,20 +175,8 @@ class SecureContext : public BaseObject { HMAC_CTX* hctx, int enc); - SecureContext(Environment* env, v8::Local wrap) - : BaseObject(env, wrap) { - MakeWeak(); - env->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); - } - - inline void Reset() { - if (ctx_ != nullptr) { - env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); - } - ctx_.reset(); - cert_.reset(); - issuer_.reset(); - } + SecureContext(Environment* env, v8::Local wrap); + void Reset(); }; // SSLWrap implicitly depends on the inheriting class' handle having an @@ -463,14 +449,7 @@ class KeyObject : public BaseObject { v8::MaybeLocal ExportPrivateKey( const PrivateKeyEncodingConfig& config) const; - KeyObject(Environment* env, - v8::Local wrap, - KeyType key_type) - : BaseObject(env, wrap), - key_type_(key_type), - symmetric_key_(nullptr, nullptr) { - MakeWeak(); - } + KeyObject(Environment* env, v8::Local wrap, KeyType key_type); private: const KeyType key_type_; @@ -544,17 +523,7 @@ class CipherBase : public BaseObject { static void SetAuthTag(const v8::FunctionCallbackInfo& args); static void SetAAD(const v8::FunctionCallbackInfo& args); - CipherBase(Environment* env, - v8::Local wrap, - CipherKind kind) - : BaseObject(env, wrap), - ctx_(nullptr), - kind_(kind), - auth_tag_state_(kAuthTagUnknown), - auth_tag_len_(kNoAuthTagLength), - pending_auth_failed_(false) { - MakeWeak(); - } + CipherBase(Environment* env, v8::Local wrap, CipherKind kind); private: DeleteFnPtr ctx_; @@ -584,18 +553,16 @@ class Hmac : public BaseObject { static void HmacUpdate(const v8::FunctionCallbackInfo& args); static void HmacDigest(const v8::FunctionCallbackInfo& args); - Hmac(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - ctx_(nullptr) { - MakeWeak(); - } + Hmac(Environment* env, v8::Local wrap); private: DeleteFnPtr ctx_; }; -class Hash : public BaseObject { +class Hash final : public BaseObject { public: + ~Hash() override; + static void Initialize(Environment* env, v8::Local target); // TODO(joyeecheung): track the memory used by OpenSSL types @@ -611,18 +578,7 @@ class Hash : public BaseObject { static void HashUpdate(const v8::FunctionCallbackInfo& args); static void HashDigest(const v8::FunctionCallbackInfo& args); - Hash(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - mdctx_(nullptr), - has_md_(false), - md_value_(nullptr) { - MakeWeak(); - } - - ~Hash() override { - if (md_value_ != nullptr) - OPENSSL_clear_free(md_value_, md_len_); - } + Hash(Environment* env, v8::Local wrap); private: EVPMDPointer mdctx_; @@ -644,9 +600,7 @@ class SignBase : public BaseObject { kSignMalformedSignature } Error; - SignBase(Environment* env, v8::Local wrap) - : BaseObject(env, wrap) { - } + SignBase(Environment* env, v8::Local wrap); Error Init(const char* sign_type); Error Update(const char* data, int len); @@ -692,9 +646,7 @@ class Sign : public SignBase { static void SignUpdate(const v8::FunctionCallbackInfo& args); static void SignFinal(const v8::FunctionCallbackInfo& args); - Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { - MakeWeak(); - } + Sign(Environment* env, v8::Local wrap); }; class Verify : public SignBase { @@ -713,9 +665,7 @@ class Verify : public SignBase { static void VerifyUpdate(const v8::FunctionCallbackInfo& args); static void VerifyFinal(const v8::FunctionCallbackInfo& args); - Verify(Environment* env, v8::Local wrap) : SignBase(env, wrap) { - MakeWeak(); - } + Verify(Environment* env, v8::Local wrap); }; class PublicKeyCipher { @@ -772,11 +722,7 @@ class DiffieHellman : public BaseObject { static void VerifyErrorGetter( const v8::FunctionCallbackInfo& args); - DiffieHellman(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - verifyError_(0) { - MakeWeak(); - } + DiffieHellman(Environment* env, v8::Local wrap); // TODO(joyeecheung): track the memory used by OpenSSL types SET_NO_MEMORY_INFO() @@ -795,11 +741,9 @@ class DiffieHellman : public BaseObject { DHPointer dh_; }; -class ECDH : public BaseObject { +class ECDH final : public BaseObject { public: - ~ECDH() override { - group_ = nullptr; - } + ~ECDH() override; static void Initialize(Environment* env, v8::Local target); static ECPointPointer BufferToPoint(Environment* env, @@ -812,13 +756,7 @@ class ECDH : public BaseObject { SET_SELF_SIZE(ECDH) protected: - ECDH(Environment* env, v8::Local wrap, ECKeyPointer&& key) - : BaseObject(env, wrap), - key_(std::move(key)), - group_(EC_KEY_get0_group(key_.get())) { - MakeWeak(); - CHECK_NOT_NULL(group_); - } + ECDH(Environment* env, v8::Local wrap, ECKeyPointer&& key); static void New(const v8::FunctionCallbackInfo& args); static void GenerateKeys(const v8::FunctionCallbackInfo& args); From e68d4c6f5f6086c2816ed6d870e23e824c661f92 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 1 Oct 2019 21:58:12 +0200 Subject: [PATCH 051/192] src: allow unique_ptrs with custom deleter in memory tracker Originally landed in the QUIC repo Original review metadata: ``` PR-URL: https://github.com/nodejs/quic/pull/145 Reviewed-By: James M Snell ``` PR-URL: https://github.com/nodejs/node/pull/31870 Reviewed-By: Anna Henningsen Reviewed-By: David Carlier Reviewed-By: Joyee Cheung Reviewed-By: Denys Otrishko Reviewed-By: Colin Ihrig --- src/memory_tracker-inl.h | 4 ++-- src/memory_tracker.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h index 1a28e2dd792..9e6201442ab 100644 --- a/src/memory_tracker-inl.h +++ b/src/memory_tracker-inl.h @@ -107,9 +107,9 @@ void MemoryTracker::TrackField(const char* edge_name, } } -template +template void MemoryTracker::TrackField(const char* edge_name, - const std::unique_ptr& value, + const std::unique_ptr& value, const char* node_name) { if (value.get() == nullptr) { return; diff --git a/src/memory_tracker.h b/src/memory_tracker.h index 616976ab2af..4a66e9ce74c 100644 --- a/src/memory_tracker.h +++ b/src/memory_tracker.h @@ -140,9 +140,9 @@ class MemoryTracker { const char* node_name = nullptr); // Shortcut to extract the underlying object out of the smart pointer - template + template inline void TrackField(const char* edge_name, - const std::unique_ptr& value, + const std::unique_ptr& value, const char* node_name = nullptr); template From d3715c76b5c287d900d28f472da0186322ace811 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 2 Jan 2020 13:13:19 -0800 Subject: [PATCH 052/192] http: move OutboundMessage.prototype.flush to EOL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API was deprecated long ago. Move to end of life and remove. PR-URL: https://github.com/nodejs/node/pull/31164 Reviewed-By: Luigi Pinca Reviewed-By: Ruben Bridgewater Reviewed-By: Sam Roberts Reviewed-By: Tobias Nießen Reviewed-By: Matteo Collina Reviewed-By: Michael Dawson --- doc/api/deprecations.md | 7 ++++-- lib/_http_outgoing.js | 4 ---- test/parallel/test-http-flush.js | 37 -------------------------------- 3 files changed, 5 insertions(+), 43 deletions(-) delete mode 100644 test/parallel/test-http-flush.js diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 0deb79a05fe..075a620b403 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -44,6 +44,9 @@ However, the deprecation identifier will not be modified. ### DEP0001: `http.OutgoingMessage.prototype.flush` -Type: Runtime +Type: End-of-Life -The `OutgoingMessage.prototype.flush()` method is deprecated. Use +`OutgoingMessage.prototype.flush()` has been removed. Use `OutgoingMessage.prototype.flushHeaders()` instead. diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index b8ee0b67689..0230f428311 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -900,10 +900,6 @@ OutgoingMessage.prototype.flushHeaders = function flushHeaders() { this._send(''); }; -OutgoingMessage.prototype.flush = internalUtil.deprecate(function() { - this.flushHeaders(); -}, 'OutgoingMessage.flush is deprecated. Use flushHeaders instead.', 'DEP0001'); - OutgoingMessage.prototype.pipe = function pipe() { // OutgoingMessage should be write-only. Piping from it is disabled. this.emit('error', new ERR_STREAM_CANNOT_PIPE()); diff --git a/test/parallel/test-http-flush.js b/test/parallel/test-http-flush.js deleted file mode 100644 index 24f43d5efec..00000000000 --- a/test/parallel/test-http-flush.js +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; -require('../common'); -const http = require('http'); - -http.createServer(function(req, res) { - res.end('ok'); - this.close(); -}).listen(0, '127.0.0.1', function() { - const req = http.request({ - method: 'POST', - host: '127.0.0.1', - port: this.address().port, - }); - req.flush(); // Flush the request headers. - req.flush(); // Should be idempotent. -}); From 021542080a020d1d67fa47e25338973b78d543e6 Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Tue, 18 Feb 2020 15:03:18 -0500 Subject: [PATCH 053/192] doc: update releases guide re pushing tags Currently we specify pushing the tag before updating any of the branches. This creates a potential of creating and pushing a tag on an out of sync branch, and only really when attempting to merge the release commit that things have gone out of sync. Changing the order here would minimize the possibility of this error PR-URL: https://github.com/nodejs/node/pull/31855 Reviewed-By: Richard Lau Reviewed-By: Shelley Vohr Reviewed-By: Beth Griggs Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/releases.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/doc/releases.md b/doc/releases.md index 76f6ad57412..37018e1b3fd 100644 --- a/doc/releases.md +++ b/doc/releases.md @@ -498,17 +498,6 @@ $ git secure-tag -sm "YYYY-MM-DD Node.js vx.y.z ( -``` - -*Note*: Please do not push the tag unless you are ready to complete the -remainder of the release steps. - ### 12. Set Up For the Next Release On release proposal branch, edit `src/node_version.h` again and: @@ -547,7 +536,20 @@ cherry-pick the "Working on vx.y.z" commit to `master`. Run `make lint` before pushing to `master`, to make sure the Changelog formatting passes the lint rules on `master`. -### 13. Promote and Sign the Release Builds +### 13. Push the release tag + +Push the tag to the repo before you promote the builds. If you haven't pushed +your tag first, then build promotion won't work properly. Push the tag using the +following command: + +```console +$ git push +``` + +*Note*: Please do not push the tag unless you are ready to complete the +remainder of the release steps. + +### 14. Promote and Sign the Release Builds **The same individual who signed the release tag must be the one to promote the builds as the `SHASUMS256.txt` file needs to be signed with the @@ -620,7 +622,7 @@ be prompted to re-sign `SHASUMS256.txt`. **It is possible to only sign a release by running `./tools/release.sh -s vX.Y.Z`.** -### 14. Check the Release +### 15. Check the Release Your release should be available at `https://nodejs.org/dist/vx.y.z/` and . Check that the appropriate files are in @@ -629,7 +631,7 @@ have the right internal version strings. Check that the API docs are available at . Check that the release catalog files are correct at and . -### 15. Create a Blog Post +### 16. Create a Blog Post There is an automatic build that is kicked off when you promote new builds, so within a few minutes nodejs.org will be listing your new version as the latest @@ -662,7 +664,7 @@ This script will use the promoted builds and changelog to generate the post. Run * Changes to `master` on the [nodejs.org repository][] will trigger a new build of nodejs.org so your changes should appear a few minutes after pushing. -### 16. Create the release on GitHub +### 17. Create the release on GitHub * Go to the [New release page](https://github.com/nodejs/node/releases/new). * Select the tag version you pushed earlier. @@ -670,11 +672,11 @@ This script will use the promoted builds and changelog to generate the post. Run * For the description, copy the rest of the changelog entry. * Click on the "Publish release" button. -### 17. Cleanup +### 18. Cleanup Close your release proposal PR and delete the proposal branch. -### 18. Announce +### 19. Announce The nodejs.org website will automatically rebuild and include the new version. To announce the build on Twitter through the official @nodejs account, email @@ -691,7 +693,7 @@ announcements. Ping the IRC ops and the other [Partner Communities][] liaisons. -### 19. Celebrate +### 20. Celebrate _In whatever form you do this..._ From b70dd9d662aaf3e652219e65d8e51762842d8b80 Mon Sep 17 00:00:00 2001 From: Harshitha KP Date: Fri, 21 Feb 2020 05:38:53 -0500 Subject: [PATCH 054/192] src: elevate v8 namespaces PR-URL: https://github.com/nodejs/node/pull/31901 Reviewed-By: Richard Lau Reviewed-By: Michael Dawson Reviewed-By: James M Snell Reviewed-By: Gus Caplan Reviewed-By: Anna Henningsen --- src/module_wrap.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 436a6e98e73..350e395ebf4 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -25,6 +25,7 @@ using node::url::URL_FLAGS_FAILED; using v8::Array; using v8::ArrayBufferView; using v8::Context; +using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -45,6 +46,7 @@ using v8::PrimitiveArray; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; +using v8::ScriptOrModule; using v8::String; using v8::UnboundModuleScript; using v8::Undefined; @@ -627,7 +629,7 @@ Maybe GetPackageConfig(Environment* env, std::string pkg_src = source.FromJust(); Isolate* isolate = env->isolate(); - v8::HandleScope handle_scope(isolate); + HandleScope handle_scope(isolate); Local pkg_json; { @@ -899,7 +901,7 @@ void ThrowExportsInvalid(Environment* env, const URL& base) { Local target_string; if (target->IsObject()) { - if (!v8::JSON::Stringify(env->context(), target.As(), + if (!v8::JSON::Stringify(env->context(), target.As(), v8::String::Empty(env->isolate())).ToLocal(&target_string)) return; } else { @@ -977,7 +979,7 @@ Maybe ResolveExportsTarget(Environment* env, Isolate* isolate = env->isolate(); Local context = env->context(); if (target->IsString()) { - Utf8Value target_utf8(isolate, target.As()); + Utf8Value target_utf8(isolate, target.As()); std::string target_str(*target_utf8, target_utf8.length()); Maybe resolved = ResolveExportsTargetString(env, target_str, subpath, pkg_subpath, pjson_url, base); @@ -1440,12 +1442,12 @@ void ModuleWrap::GetPackageType(const FunctionCallbackInfo& args) { static MaybeLocal ImportModuleDynamically( Local context, - Local referrer, + Local referrer, Local specifier) { Isolate* iso = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. - v8::EscapableHandleScope handle_scope(iso); + EscapableHandleScope handle_scope(iso); Local import_callback = env->host_import_module_dynamically_callback(); From 5f0181a079f4dd328fb01a75daff0a128c33fc70 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Fri, 14 Feb 2020 11:47:20 -0800 Subject: [PATCH 055/192] deps: move zlib maintenance info to guides deps/zlib/README.md is not part of the upstream zlib, it is a Node.js specific addition describing how to maintain zlib and should be in doc/guides/. PR-URL: https://github.com/nodejs/node/pull/31800 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Beth Griggs --- deps/zlib/README.md => doc/guides/maintaining-zlib.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename deps/zlib/README.md => doc/guides/maintaining-zlib.md (100%) diff --git a/deps/zlib/README.md b/doc/guides/maintaining-zlib.md similarity index 100% rename from deps/zlib/README.md rename to doc/guides/maintaining-zlib.md From 0d95eda4996e5e895b80cd68052332a102de9fd3 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Fri, 14 Feb 2020 13:00:14 -0800 Subject: [PATCH 056/192] doc: describe how to update zlib See: - https://github.com/nodejs/node/pull/31201 PR-URL: https://github.com/nodejs/node/pull/31800 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Beth Griggs --- doc/guides/maintaining-zlib.md | 36 ++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/doc/guides/maintaining-zlib.md b/doc/guides/maintaining-zlib.md index a12f0a7876a..c293fdf5d40 100644 --- a/doc/guides/maintaining-zlib.md +++ b/doc/guides/maintaining-zlib.md @@ -1,6 +1,34 @@ -This copy of zlib comes from the Chromium team's zlib fork which incorporated performance improvements not currently available in standard zlib. +# Maintaining zlib -To update this code: +This copy of zlib comes from the Chromium team's zlib fork which incorporated +performance improvements not currently available in standard zlib. -* Clone https://chromium.googlesource.com/chromium/src/third_party/zlib -* Comment out the `#include "chromeconf.h"` in zconf.h to maintain full compatibility with node addons +## Updating zlib + +Update zlib: +```shell +git clone https://chromium.googlesource.com/chromium/src/third_party/zlib +cp deps/zlib/zlib.gyp deps/zlib/win32/zlib.def deps +rm -rf deps/zlib zlib/.git +mv zlib deps/ +mv deps/zlib.gyp deps/zlib/ +mkdir deps/zlib/win32 +mv deps/zlib.def deps/zlib/win32 +sed -i -- 's_^#include "chromeconf.h"_//#include "chromeconf.h"_' deps/zlib/zconf.h +``` + +Check that Node.js still builds and tests. + +It may be necessary to update deps/zlib/zlib.gyp if any significant changes have +occurred upstream. + +## Commiting zlib + +Add zlib: `git add --all deps/zlib` + +Commit the changes with a message like +```text +deps: update zlib to upstream d7f3ca9 + +Updated as described in doc/guides/maintaining-zlib.md. +``` From 9a1719b541b53eae844d801a74a8b6328ad1c9fd Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Fri, 14 Feb 2020 13:10:57 -0800 Subject: [PATCH 057/192] deps: update zlib to upstream d7f3ca9 Updated as described in doc/guides/maintaining-zlib.md. PR-URL: https://github.com/nodejs/node/pull/31800 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Beth Griggs --- deps/zlib/google/test/data/create_test_zip.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 deps/zlib/google/test/data/create_test_zip.sh diff --git a/deps/zlib/google/test/data/create_test_zip.sh b/deps/zlib/google/test/data/create_test_zip.sh old mode 100644 new mode 100755 From 0dff851336ea2a3801667e5390d5821db746baae Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 21 Feb 2020 08:26:29 -0800 Subject: [PATCH 058/192] src: include large pages source unconditionally Restrict the usage of the C preprocessor directive enabling large pages support to the large pages implementation. This cleans up the code in src/node.cc. PR-URL: https://github.com/nodejs/node/pull/31904 Reviewed-By: Ben Noordhuis Reviewed-By: David Carlier Reviewed-By: Denys Otrishko --- node.gyp | 6 +-- src/large_pages/node_large_page.cc | 69 +++++++++++++++++++++++------- src/large_pages/node_large_page.h | 3 +- src/node.cc | 20 ++------- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/node.gyp b/node.gyp index ab3953b9bdf..1e928262ac8 100644 --- a/node.gyp +++ b/node.gyp @@ -620,6 +620,8 @@ 'src/histogram.h', 'src/histogram-inl.h', 'src/js_stream.h', + 'src/large_pages/node_large_page.cc', + 'src/large_pages/node_large_page.h' 'src/memory_tracker.h', 'src/memory_tracker-inl.h', 'src/module_wrap.h', @@ -851,10 +853,6 @@ 'target_arch=="x64" and ' 'node_target_type=="executable"', { 'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ], - 'sources': [ - 'src/large_pages/node_large_page.cc', - 'src/large_pages/node_large_page.h' - ], }], [ 'use_openssl_def==1', { # TODO(bnoordhuis) Make all platforms export the same list of symbols. diff --git a/src/large_pages/node_large_page.cc b/src/large_pages/node_large_page.cc index ce58e32e719..a72cb65bb65 100644 --- a/src/large_pages/node_large_page.cc +++ b/src/large_pages/node_large_page.cc @@ -21,6 +21,11 @@ // SPDX-License-Identifier: MIT #include "node_large_page.h" + +#include // NOLINT(build/include) + +// Besides returning ENOTSUP at runtime we do nothing if this define is missing. +#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES #include "util.h" #include "uv.h" @@ -35,7 +40,6 @@ #endif #include // readlink -#include // NOLINT(build/include) #include // PATH_MAX #include #include @@ -71,7 +75,11 @@ extern char __executable_start; } // extern "C" #endif // defined(__linux__) +#endif // defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES namespace node { +#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES + +namespace { struct text_region { char* from; @@ -103,7 +111,7 @@ inline uintptr_t hugepage_align_down(uintptr_t addr) { // 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon // This is also handling the case where the first line is not the binary. -static struct text_region FindNodeTextRegion() { +struct text_region FindNodeTextRegion() { struct text_region nregion; nregion.found_text_region = false; #if defined(__linux__) @@ -263,7 +271,7 @@ static struct text_region FindNodeTextRegion() { } #if defined(__linux__) -static bool IsTransparentHugePagesEnabled() { +bool IsTransparentHugePagesEnabled() { std::ifstream ifs; ifs.open("/sys/kernel/mm/transparent_hugepage/enabled"); @@ -294,6 +302,8 @@ static bool IsSuperPagesEnabled() { } #endif +} // End of anonymous namespace + // Moving the text region to large pages. We need to be very careful. // 1: This function itself should not be moved. // We use a gcc attributes @@ -408,14 +418,26 @@ MoveTextRegionToLargePages(const text_region& r) { if (-1 == munmap(nmem, size)) PrintSystemError(errno); return ret; } +#endif // defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES // This is the primary API called from main. int MapStaticCodeToLargePages() { +#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES + bool have_thp = false; +#if defined(__linux__) + have_thp = IsTransparentHugePagesEnabled(); +#elif defined(__FreeBSD__) + have_thp = IsSuperPagesEnabled(); +#elif defined(__APPLE__) + // pse-36 flag is present in recent mac x64 products. + have_thp = true; +#endif + if (!have_thp) + return EACCES; + struct text_region r = FindNodeTextRegion(); - if (r.found_text_region == false) { - PrintWarning("failed to find text region"); - return -1; - } + if (r.found_text_region == false) + return ENOENT; #if defined(__FreeBSD__) if (r.from < reinterpret_cast(&MoveTextRegionToLargePages)) @@ -423,17 +445,32 @@ int MapStaticCodeToLargePages() { #endif return MoveTextRegionToLargePages(r); +#else + return ENOTSUP; +#endif } -bool IsLargePagesEnabled() { -#if defined(__linux__) - return IsTransparentHugePagesEnabled(); -#elif defined(__FreeBSD__) - return IsSuperPagesEnabled(); -#elif defined(__APPLE__) - // pse-36 flag is present in recent mac x64 products. - return true; -#endif +const char* LargePagesError(int status) { + switch (status) { + case ENOTSUP: + return "Mapping to large pages is not supported."; + + case EACCES: + return "Large pages are not enabled."; + + case ENOENT: + return "failed to find text region"; + + case -1: + return "Mapping code to large pages failed. Reverting to default page " + "size."; + + case 0: + return "OK"; + + default: + return "Unknown error"; + } } } // namespace node diff --git a/src/large_pages/node_large_page.h b/src/large_pages/node_large_page.h index bce505585cf..622cf09ede4 100644 --- a/src/large_pages/node_large_page.h +++ b/src/large_pages/node_large_page.h @@ -25,10 +25,9 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - namespace node { -bool IsLargePagesEnabled(); int MapStaticCodeToLargePages(); +const char* LargePagesError(int status); } // namespace node #endif // NODE_WANT_INTERNALS diff --git a/src/node.cc b/src/node.cc index a0398b1a4f8..aec70381c58 100644 --- a/src/node.cc +++ b/src/node.cc @@ -65,9 +65,7 @@ #include "inspector/worker_inspector.h" // ParentInspectorHandle #endif -#ifdef NODE_ENABLE_LARGE_CODE_PAGES #include "large_pages/node_large_page.h" -#endif #ifdef NODE_REPORT #include "node_report.h" @@ -936,25 +934,13 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) { } } -#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES if (per_process::cli_options->use_largepages == "on" || per_process::cli_options->use_largepages == "silent") { - if (node::IsLargePagesEnabled()) { - if (node::MapStaticCodeToLargePages() != 0 && - per_process::cli_options->use_largepages != "silent") { - fprintf(stderr, - "Mapping code to large pages failed. Reverting to default page " - "size.\n"); - } - } else if (per_process::cli_options->use_largepages != "silent") { - fprintf(stderr, "Large pages are not enabled.\n"); + int result = node::MapStaticCodeToLargePages(); + if (per_process::cli_options->use_largepages == "on" && result != 0) { + fprintf(stderr, "%s\n", node::LargePagesError(result)); } } -#else - if (per_process::cli_options->use_largepages == "on") { - fprintf(stderr, "Mapping to large pages is not supported.\n"); - } -#endif // NODE_ENABLE_LARGE_CODE_PAGES if (per_process::cli_options->print_version) { printf("%s\n", NODE_VERSION); From 940325042bef787e84917a27f9435d423b3f7f6d Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 21 Feb 2020 07:27:29 +0100 Subject: [PATCH 059/192] test: add secp224k1 check in crypto-dh-stateless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a check to test-crypto-dh-statless to avoid an error if the curve secp224k1 is not present. This could occur if node was configured with shared-openssl. The use case for this was building on RHEL 8.1 which only has the following curves: $ openssl ecparam -list_curves secp224r1 : NIST/SECG curve over a 224 bit prime field secp256k1 : SECG curve over a 256 bit prime field secp384r1 : NIST/SECG curve over a 384 bit prime field secp521r1 : NIST/SECG curve over a 521 bit prime field prime256v1: X9.62/SECG curve over a 256 bit prime field PR-URL: https://github.com/nodejs/node/pull/31715 Reviewed-By: Ben Noordhuis Reviewed-By: Tobias Nießen Reviewed-By: Michael Dawson Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- test/parallel/test-crypto-dh-stateless.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-crypto-dh-stateless.js b/test/parallel/test-crypto-dh-stateless.js index f00ee997cfc..b01cea76b22 100644 --- a/test/parallel/test-crypto-dh-stateless.js +++ b/test/parallel/test-crypto-dh-stateless.js @@ -196,9 +196,10 @@ for (const [params1, params2] of [ test(crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' }), crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' })); +const not256k1 = crypto.getCurves().find((c) => /^sec.*(224|384|512)/.test(c)); assert.throws(() => { test(crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' }), - crypto.generateKeyPairSync('ec', { namedCurve: 'secp224k1' })); + crypto.generateKeyPairSync('ec', { namedCurve: not256k1 })); }, { name: 'Error', code: 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' From 1f209129c7e6c3ec6628809821fc9a36deae7ec8 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Mon, 17 Feb 2020 21:24:25 +0100 Subject: [PATCH 060/192] stream: throw invalid argument errors Logic errors that do not depend on stream state should throw instead of invoke callback and emit error. PR-URL: https://github.com/nodejs/node/pull/31831 Refs: https://github.com/nodejs/node/pull/31818 Reviewed-By: Luigi Pinca Reviewed-By: Matteo Collina Reviewed-By: Rich Trott --- lib/_stream_writable.js | 18 +++++---- test/parallel/test-fs-write-stream.js | 11 +++--- test/parallel/test-net-socket-write-error.js | 12 +++--- test/parallel/test-net-write-arguments.js | 20 +++++----- .../test-stream-writable-invalid-chunk.js | 13 ++++--- test/parallel/test-stream-writable-null.js | 33 ++++++---------- .../test-stream-writable-write-error.js | 38 +++++++++++-------- test/parallel/test-stream2-writable.js | 12 +++--- test/parallel/test-zlib-invalid-input.js | 9 +++-- test/parallel/test-zlib-object-write.js | 14 ++++--- 10 files changed, 92 insertions(+), 88 deletions(-) diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index fe8f273a022..af199956062 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -273,13 +273,8 @@ Writable.prototype.write = function(chunk, encoding, cb) { cb = nop; } - let err; - if (state.ending) { - err = new ERR_STREAM_WRITE_AFTER_END(); - } else if (state.destroyed) { - err = new ERR_STREAM_DESTROYED('write'); - } else if (chunk === null) { - err = new ERR_STREAM_NULL_VALUES(); + if (chunk === null) { + throw new ERR_STREAM_NULL_VALUES(); } else if (!state.objectMode) { if (typeof chunk === 'string') { if (state.decodeStrings !== false) { @@ -292,11 +287,18 @@ Writable.prototype.write = function(chunk, encoding, cb) { chunk = Stream._uint8ArrayToBuffer(chunk); encoding = 'buffer'; } else { - err = new ERR_INVALID_ARG_TYPE( + throw new ERR_INVALID_ARG_TYPE( 'chunk', ['string', 'Buffer', 'Uint8Array'], chunk); } } + let err; + if (state.ending) { + err = new ERR_STREAM_WRITE_AFTER_END(); + } else if (state.destroyed) { + err = new ERR_STREAM_DESTROYED('write'); + } + if (err) { process.nextTick(cb, err); errorOrDestroy(this, err, true); diff --git a/test/parallel/test-fs-write-stream.js b/test/parallel/test-fs-write-stream.js index f84b727c866..9f422a64437 100644 --- a/test/parallel/test-fs-write-stream.js +++ b/test/parallel/test-fs-write-stream.js @@ -56,13 +56,12 @@ tmpdir.refresh(); // Throws if data is not of type Buffer. { const stream = fs.createWriteStream(file); - stream.on('error', common.expectsError({ + stream.on('error', common.mustNotCall()); + assert.throws(() => { + stream.write(42); + }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' - })); - stream.write(42, null, common.expectsError({ - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError' - })); + }); stream.destroy(); } diff --git a/test/parallel/test-net-socket-write-error.js b/test/parallel/test-net-socket-write-error.js index ab748480ea3..e68db68c0d4 100644 --- a/test/parallel/test-net-socket-write-error.js +++ b/test/parallel/test-net-socket-write-error.js @@ -2,19 +2,19 @@ const common = require('../common'); const net = require('net'); +const assert = require('assert'); const server = net.createServer().listen(0, connectToServer); function connectToServer() { const client = net.createConnection(this.address().port, () => { - client.write(1337, common.expectsError({ + client.on('error', common.mustNotCall()); + assert.throws(() => { + client.write(1337); + }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' - })); - client.on('error', common.expectsError({ - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError' - })); + }); client.destroy(); }) diff --git a/test/parallel/test-net-write-arguments.js b/test/parallel/test-net-write-arguments.js index 407871afe6d..2b81ed7d6a3 100644 --- a/test/parallel/test-net-write-arguments.js +++ b/test/parallel/test-net-write-arguments.js @@ -1,20 +1,18 @@ 'use strict'; const common = require('../common'); const net = require('net'); - +const assert = require('assert'); const socket = net.Stream({ highWaterMark: 0 }); // Make sure that anything besides a buffer or a string throws. -socket.write(null, common.expectsError({ +socket.on('error', common.mustNotCall()); +assert.throws(() => { + socket.write(null); +}, { code: 'ERR_STREAM_NULL_VALUES', name: 'TypeError', message: 'May not write null values to stream' -})); -socket.on('error', common.expectsError({ - code: 'ERR_STREAM_NULL_VALUES', - name: 'TypeError', - message: 'May not write null values to stream' -})); +}); [ true, @@ -29,10 +27,12 @@ socket.on('error', common.expectsError({ ].forEach((value) => { // We need to check the callback since 'error' will only // be emitted once per instance. - socket.write(value, common.expectsError({ + assert.throws(() => { + socket.write(value); + }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', message: 'The "chunk" argument must be of type string or an instance of ' + `Buffer or Uint8Array.${common.invalidArgTypeHelper(value)}` - })); + }); }); diff --git a/test/parallel/test-stream-writable-invalid-chunk.js b/test/parallel/test-stream-writable-invalid-chunk.js index 09ee5877c8d..09032c07c59 100644 --- a/test/parallel/test-stream-writable-invalid-chunk.js +++ b/test/parallel/test-stream-writable-invalid-chunk.js @@ -2,20 +2,21 @@ const common = require('../common'); const stream = require('stream'); +const assert = require('assert'); function testWriteType(val, objectMode, code) { const writable = new stream.Writable({ objectMode, write: () => {} }); - if (!code) { - writable.on('error', common.mustNotCall()); + writable.on('error', common.mustNotCall()); + if (code) { + assert.throws(() => { + writable.write(val); + }, { code }); } else { - writable.on('error', common.expectsError({ - code: code, - })); + writable.write(val); } - writable.write(val); } testWriteType([], false, 'ERR_INVALID_ARG_TYPE'); diff --git a/test/parallel/test-stream-writable-null.js b/test/parallel/test-stream-writable-null.js index f26fc62328c..99419f1cf9a 100644 --- a/test/parallel/test-stream-writable-null.js +++ b/test/parallel/test-stream-writable-null.js @@ -16,31 +16,22 @@ class MyWritable extends stream.Writable { { const m = new MyWritable({ objectMode: true }); - m.write(null, (err) => assert.ok(err)); - m.on('error', common.expectsError({ - code: 'ERR_STREAM_NULL_VALUES', - name: 'TypeError', - message: 'May not write null values to stream' - })); -} - -{ // Should not throw. - const m = new MyWritable({ objectMode: true }).on('error', assert); - m.write(null, assert); + m.on('error', common.mustNotCall()); + assert.throws(() => { + m.write(null); + }, { + code: 'ERR_STREAM_NULL_VALUES' + }); } { const m = new MyWritable(); - m.write(false, (err) => assert.ok(err)); - m.on('error', common.expectsError({ - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError' - })); -} - -{ // Should not throw. - const m = new MyWritable().on('error', assert); - m.write(false, assert); + m.on('error', common.mustNotCall()); + assert.throws(() => { + m.write(false); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); } { // Should not throw. diff --git a/test/parallel/test-stream-writable-write-error.js b/test/parallel/test-stream-writable-write-error.js index e23b24a19df..0e3f763a3ca 100644 --- a/test/parallel/test-stream-writable-write-error.js +++ b/test/parallel/test-stream-writable-write-error.js @@ -4,19 +4,27 @@ const assert = require('assert'); const { Writable } = require('stream'); -function expectError(w, arg, code) { - let errorCalled = false; - let ticked = false; - w.write(arg, common.mustCall((err) => { - assert.strictEqual(ticked, true); - assert.strictEqual(errorCalled, false); - assert.strictEqual(err.code, code); - })); - ticked = true; - w.on('error', common.mustCall((err) => { - errorCalled = true; - assert.strictEqual(err.code, code); - })); +function expectError(w, arg, code, sync) { + if (sync) { + if (code) { + assert.throws(() => w.write(arg), { code }); + } else { + w.write(arg); + } + } else { + let errorCalled = false; + let ticked = false; + w.write(arg, common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(errorCalled, false); + assert.strictEqual(err.code, code); + })); + ticked = true; + w.on('error', common.mustCall((err) => { + errorCalled = true; + assert.strictEqual(err.code, code); + })); + } } function test(autoDestroy) { @@ -43,7 +51,7 @@ function test(autoDestroy) { autoDestroy, _write() {} }); - expectError(w, null, 'ERR_STREAM_NULL_VALUES'); + expectError(w, null, 'ERR_STREAM_NULL_VALUES', true); } { @@ -51,7 +59,7 @@ function test(autoDestroy) { autoDestroy, _write() {} }); - expectError(w, {}, 'ERR_INVALID_ARG_TYPE'); + expectError(w, {}, 'ERR_INVALID_ARG_TYPE', true); } } diff --git a/test/parallel/test-stream2-writable.js b/test/parallel/test-stream2-writable.js index 0d9bc03fae7..03835bb1bd0 100644 --- a/test/parallel/test-stream2-writable.js +++ b/test/parallel/test-stream2-writable.js @@ -422,12 +422,12 @@ const helloWorldBuffer = Buffer.from('hello world'); { // Verify that error is only emitted once when failing in write. const w = new W(); - w.on('error', common.mustCall((err) => { - assert.strictEqual(w._writableState.errorEmitted, true); - assert.strictEqual(err.code, 'ERR_STREAM_NULL_VALUES'); - })); - w.write(null); - w.destroy(new Error()); + w.on('error', common.mustNotCall()); + assert.throws(() => { + w.write(null); + }, { + code: 'ERR_STREAM_NULL_VALUES' + }); } { diff --git a/test/parallel/test-zlib-invalid-input.js b/test/parallel/test-zlib-invalid-input.js index 68fa3825b91..eb651be00fb 100644 --- a/test/parallel/test-zlib-invalid-input.js +++ b/test/parallel/test-zlib-invalid-input.js @@ -43,10 +43,11 @@ const unzips = [ ]; nonStringInputs.forEach(common.mustCall((input) => { - // zlib.gunzip should not throw an error when called with bad input. - zlib.gunzip(input, (err, buffer) => { - // zlib.gunzip should pass the error to the callback. - assert.ok(err); + assert.throws(() => { + zlib.gunzip(input); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' }); }, nonStringInputs.length)); diff --git a/test/parallel/test-zlib-object-write.js b/test/parallel/test-zlib-object-write.js index df533d77b3f..2be5edab897 100644 --- a/test/parallel/test-zlib-object-write.js +++ b/test/parallel/test-zlib-object-write.js @@ -1,12 +1,14 @@ 'use strict'; const common = require('../common'); +const assert = require('assert'); const { Gunzip } = require('zlib'); const gunzip = new Gunzip({ objectMode: true }); -gunzip.write({}, common.expectsError({ - name: 'TypeError' -})); -gunzip.on('error', common.expectsError({ - name: 'TypeError' -})); +gunzip.on('error', common.mustNotCall()); +assert.throws(() => { + gunzip.write({}); +}, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' +}); From 914d800a23f1c7877770d62bd4061957c402c8ca Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 18 Feb 2020 23:01:21 +0800 Subject: [PATCH 061/192] tools: add NODE_TEST_NO_INTERNET to the doc builder At the moment the doc builder tries to access the internet for CHANGELOG information and only falls back to local sources after the connection fails or a 5 second timeout. This means that the doc building could take at least 7 minutes on a machine with hijacked connection to Github for useless network attempts. This patch adds a NODE_TEST_NO_INTERNET environment variable to directly bypass these attempts so that docs can be built in reasonable time on a machine like that. PR-URL: https://github.com/nodejs/node/pull/31849 Fixes: https://github.com/nodejs/node/issues/29918 Reviewed-By: Matheus Marchini Reviewed-By: Richard Lau Reviewed-By: Ruben Bridgewater Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- tools/doc/versions.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tools/doc/versions.js b/tools/doc/versions.js index 7a4e2c3ff76..782ce90ee2c 100644 --- a/tools/doc/versions.js +++ b/tools/doc/versions.js @@ -31,6 +31,8 @@ const getUrl = (url) => { }); }; +const kNoInternet = !!process.env.NODE_TEST_NO_INTERNET; + module.exports = { async versions() { if (_versions) { @@ -42,16 +44,20 @@ module.exports = { const url = 'https://raw.githubusercontent.com/nodejs/node/master/CHANGELOG.md'; let changelog; - try { - changelog = await getUrl(url); - } catch (e) { - // Fail if this is a release build, otherwise fallback to local files. - if (isRelease()) { - throw e; - } else { - const file = path.join(srcRoot, 'CHANGELOG.md'); - console.warn(`Unable to retrieve ${url}. Falling back to ${file}.`); - changelog = readFileSync(file, { encoding: 'utf8' }); + const file = path.join(srcRoot, 'CHANGELOG.md'); + if (kNoInternet) { + changelog = readFileSync(file, { encoding: 'utf8' }); + } else { + try { + changelog = await getUrl(url); + } catch (e) { + // Fail if this is a release build, otherwise fallback to local files. + if (isRelease()) { + throw e; + } else { + console.warn(`Unable to retrieve ${url}. Falling back to ${file}.`); + changelog = readFileSync(file, { encoding: 'utf8' }); + } } } const ltsRE = /Long Term Support/i; From 24aa9bda4636741e52002c9e9cbf5e0042349b8d Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sun, 23 Feb 2020 11:54:39 -0800 Subject: [PATCH 062/192] doc: updated YAML version representation in readline.md All other versions in YAML throughout the docs start with _v_. Fix two cases in `readline.md` that did not. PR-URL: https://github.com/nodejs/node/pull/31924 Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- doc/api/readline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/readline.md b/doc/api/readline.md index 1fb00b9c312..f36f0db9db7 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -352,7 +352,7 @@ async function processLineByLine() { ### `rl.line` * {string|undefined} @@ -387,7 +387,7 @@ process.stdin.on('keypress', (c, k) => { ### `rl.cursor` * {number|undefined} From fb7304503f8503597d630855461cab34836897ba Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 8 Feb 2020 01:49:43 +0800 Subject: [PATCH 063/192] vm: implement vm.measureMemory() for per-context memory measurement This patch implements `vm.measureMemory()` with the new `v8::Isolate::MeasureMemory()` API to measure per-context memory usage. This should be experimental, since detailed memory measurement requires further integration with the V8 API that should be available in a future V8 update. PR-URL: https://github.com/nodejs/node/pull/31824 Refs: https://github.com/ulan/performance-measure-memory Reviewed-By: Ben Noordhuis Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Denys Otrishko Reviewed-By: Colin Ihrig --- doc/api/errors.md | 8 +++ doc/api/vm.md | 50 ++++++++++++++++++ lib/internal/errors.js | 1 + lib/vm.js | 37 ++++++++++++- src/node_contextify.cc | 44 ++++++++++++++++ test/parallel/test-vm-measure-memory.js | 70 +++++++++++++++++++++++++ 6 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-vm-measure-memory.js diff --git a/doc/api/errors.md b/doc/api/errors.md index be7fa82a9f3..e3eb498e96a 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -710,6 +710,14 @@ STDERR/STDOUT, and the data's length is longer than the `maxBuffer` option. `Console` was instantiated without `stdout` stream, or `Console` has a non-writable `stdout` or `stderr` stream. + +### `ERR_CONTEXT_NOT_INITIALIZED` + +The vm context passed into the API is not yet initialized. This could happen +when an error occurs (and is caught) during the creation of the +context, for example, when the allocation fails or the maximum call stack +size is reached when the context is created. + ### `ERR_CONSTRUCT_CALL_REQUIRED` diff --git a/doc/api/vm.md b/doc/api/vm.md index bd64b23484e..ed676414471 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -295,6 +295,56 @@ console.log(globalVar); // 1000 ``` +## `vm.measureMemory([options])` + + + +> Stability: 1 - Experimental + +Measure the memory known to V8 and used by the current execution context +or a specified context. + +* `options` {Object} Optional. + * `mode` {string} Either `'summary'` or `'detailed'`. + **Default:** `'summary'` + * `context` {Object} Optional. A [contextified][] object returned + by `vm.createContext()`. If not specified, measure the memory + usage of the current context where `vm.measureMemory()` is invoked. +* Returns: {Promise} If the memory is successfully measured the promise will + resolve with an object containing information about the memory usage. + +The format of the object that the returned Promise may resolve with is +specific to the V8 engine and may change from one version of V8 to the next. + +The returned result is different from the statistics returned by +`v8.getHeapSpaceStatistics()` in that `vm.measureMemory()` measures +the memory reachable by V8 from a specific context, while +`v8.getHeapSpaceStatistics()` measures the memory used by an instance +of V8 engine, which can switch among multiple contexts that reference +objects in the heap of one engine. + +```js +const vm = require('vm'); +// Measure the memory used by the current context and return the result +// in summary. +vm.measureMemory({ mode: 'summary' }) + // Is the same as vm.measureMemory() + .then((result) => { + // The current format is: + // { total: { jsMemoryEstimate: 2211728, jsMemoryRange: [ 0, 2211728 ] } } + console.log(result); + }); + +const context = vm.createContext({}); +vm.measureMemory({ mode: 'detailed' }, context) + .then((result) => { + // At the moment the detailed format is the same as the summary one. + console.log(result); + }); +``` + ## Class: `vm.Module` -* `authority` {string|URL} +* `authority` {string|URL} The remote HTTP/2 server to connect to. This must + be in the form of a minimal, valid URL with the `http://` or `https://` + prefix, host name, and IP port (if a non-default port is used). Userinfo + (user ID and password), path, querystring, and fragment details in the + URL will be ignored. * `options` {Object} * `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size for deflating header fields. **Default:** `4Kib`. From c6ed01cf6d0d54f8de862fd41413e5cbac2a8ba4 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 5 Feb 2020 18:37:48 -0800 Subject: [PATCH 073/192] doc: update zlib doc Just some general improvements to zlib docs and examples Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/31665 Reviewed-By: Anna Henningsen Reviewed-By: Luigi Pinca --- doc/api/zlib.md | 190 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 144 insertions(+), 46 deletions(-) diff --git a/doc/api/zlib.md b/doc/api/zlib.md index e059cc407df..04465c11352 100644 --- a/doc/api/zlib.md +++ b/doc/api/zlib.md @@ -4,60 +4,121 @@ > Stability: 2 - Stable -The `zlib` module provides compression functionality implemented using Gzip and -Deflate/Inflate, as well as Brotli. It can be accessed using: +The `zlib` module provides compression functionality implemented using Gzip, +Deflate/Inflate, and Brotli. + +To access it: ```js const zlib = require('zlib'); ``` +Compression and decompression are built around the Node.js [Streams API][]. + Compressing or decompressing a stream (such as a file) can be accomplished by -piping the source stream data through a `zlib` stream into a destination stream: +piping the source stream through a `zlib` `Transform` stream into a destination +stream: ```js -const gzip = zlib.createGzip(); -const fs = require('fs'); -const inp = fs.createReadStream('input.txt'); -const out = fs.createWriteStream('input.txt.gz'); - -inp.pipe(gzip) - .on('error', () => { - // handle error - }) - .pipe(out) - .on('error', () => { - // handle error +const { createGzip } = require('zlib'); +const { pipeline } = require('stream'); +const { + createReadStream, + createWriteStream +} = require('fs'); + +const gzip = createGzip(); +const source = createReadStream('input.txt'); +const destination = createWriteStream('input.txt.gz'); + +pipeline(source, gzip, destination, (err) => { + if (err) { + console.error('An error occurred:', err); + process.exitCode = 1; + } +}); + +// Or, Promisified + +const { promisify } = require('util'); +const pipe = promisify(pipeline); + +async function do_gzip(input, output) { + const gzip = createGzip(); + const source = createReadStream(input); + const destination = createWriteStream(output); + await pipe(source, gzip, destination); +} + +do_gzip('input.txt', 'input.txt.gz') + .catch((err) => { + console.error('An error occurred:', err); + process.exitCode = 1; }); ``` It is also possible to compress or decompress data in a single step: ```js +const { deflate, unzip } = require('zlib'); + const input = '.................................'; -zlib.deflate(input, (err, buffer) => { - if (!err) { - console.log(buffer.toString('base64')); - } else { - // handle error +deflate(input, (err, buffer) => { + if (err) { + console.error('An error occurred:', err); + process.exitCode = 1; } + console.log(buffer.toString('base64')); }); const buffer = Buffer.from('eJzT0yMAAGTvBe8=', 'base64'); -zlib.unzip(buffer, (err, buffer) => { - if (!err) { - console.log(buffer.toString()); - } else { - // handle error +unzip(buffer, (err, buffer) => { + if (err) { + console.error('An error occurred:', err); + process.exitCode = 1; } + console.log(buffer.toString()); }); + +// Or, Promisified + +const { promisify } = require('util'); +const do_unzip = promisify(unzip); + +do_unzip(buffer) + .then((buf) => console.log(buf.toString())) + .catch((err) => { + console.error('An error occurred:', err); + process.exitCode = 1; + }); ``` -## Threadpool Usage +## Threadpool Usage and Performance Considerations + +All `zlib` APIs, except those that are explicitly synchronous, use the Node.js +internal threadpool. This can lead to surprising effects and performance +limitations in some applications. -All zlib APIs, except those that are explicitly synchronous, use libuv's -threadpool. This can lead to surprising effects in some applications, such as -subpar performance (which can be mitigated by adjusting the [pool size][]) -and/or unrecoverable and catastrophic memory fragmentation. +Creating and using a large number of zlib objects simultaneously can cause +significant memory fragmentation. + +```js +const zlib = require('zlib'); + +const payload = Buffer.from('This is some data'); + +// WARNING: DO NOT DO THIS! +for (let i = 0; i < 30000; ++i) { + zlib.deflate(payload, (err, buffer) => {}); +} +``` + +In the preceding example, 30,000 deflate instances are created concurrently. +Because of how some operating systems handle memory allocation and +deallocation, this may lead to to significant memory fragmentation. + +It is strongly recommended that the results of compression +operations be cached to avoid duplication of effort. ## Compressing HTTP requests and responses @@ -80,6 +141,8 @@ tradeoffs involved in `zlib` usage. const zlib = require('zlib'); const http = require('http'); const fs = require('fs'); +const { pipeline } = require('stream'); + const request = http.get({ host: 'example.com', path: '/', port: 80, @@ -87,19 +150,26 @@ const request = http.get({ host: 'example.com', request.on('response', (response) => { const output = fs.createWriteStream('example.com_index.html'); + const onError = (err) => { + if (err) { + console.error('An error occurred:', err); + process.exitCode = 1; + } + }; + switch (response.headers['content-encoding']) { case 'br': - response.pipe(zlib.createBrotliDecompress()).pipe(output); + pipeline(response, zlib.createBrotliDecompress(), output, onError); break; // Or, just use zlib.createUnzip() to handle both of the following cases: case 'gzip': - response.pipe(zlib.createGunzip()).pipe(output); + pipeline(response, zlib.createGunzip(), output, onError); break; case 'deflate': - response.pipe(zlib.createInflate()).pipe(output); + pipeline(response, zlib.createInflate(), outout, onError); break; default: - response.pipe(output); + pipeline(response, output, onError); break; } }); @@ -112,6 +182,8 @@ request.on('response', (response) => { const zlib = require('zlib'); const http = require('http'); const fs = require('fs'); +const { pipeline } = require('stream'); + http.createServer((request, response) => { const raw = fs.createReadStream('index.html'); // Store both a compressed and an uncompressed version of the resource. @@ -121,20 +193,32 @@ http.createServer((request, response) => { acceptEncoding = ''; } + const onError = (err) => { + if (err) { + // If an error occurs, there's not much we can do because + // the server has already sent the 200 response code and + // some amount of data has already been sent to the client. + // The best we can do is terminate the response immediately + // and log the error. + response.end(); + console.error('An error occurred:', err); + } + }; + // Note: This is not a conformant accept-encoding parser. // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 if (/\bdeflate\b/.test(acceptEncoding)) { response.writeHead(200, { 'Content-Encoding': 'deflate' }); - raw.pipe(zlib.createDeflate()).pipe(response); + pipeline(raw, zlib.createDeflate(), response, onError); } else if (/\bgzip\b/.test(acceptEncoding)) { response.writeHead(200, { 'Content-Encoding': 'gzip' }); - raw.pipe(zlib.createGzip()).pipe(response); + pipeline(raw, zlib.createGzip(), response, onError); } else if (/\bbr\b/.test(acceptEncoding)) { response.writeHead(200, { 'Content-Encoding': 'br' }); - raw.pipe(zlib.createBrotliCompress()).pipe(response); + pipeline(raw, zlib.createBrotliCompress(), response, onError); } else { response.writeHead(200, {}); - raw.pipe(response); + pipeline(raw, response, onError); } }).listen(1337); ``` @@ -154,11 +238,11 @@ zlib.unzip( // For Brotli, the equivalent is zlib.constants.BROTLI_OPERATION_FLUSH. { finishFlush: zlib.constants.Z_SYNC_FLUSH }, (err, buffer) => { - if (!err) { - console.log(buffer.toString()); - } else { - // handle error + if (err) { + console.error('An error occurred:', err); + process.exitCode = 1; } + console.log(buffer.toString()); }); ``` @@ -234,14 +318,28 @@ HTTP response to the client: ```js const zlib = require('zlib'); const http = require('http'); +const { pipeline } = require('stream'); http.createServer((request, response) => { // For the sake of simplicity, the Accept-Encoding checks are omitted. response.writeHead(200, { 'content-encoding': 'gzip' }); const output = zlib.createGzip(); - output.pipe(response); + let i; + + pipeline(output, response, (err) => { + if (err) { + // If an error occurs, there's not much we can do because + // the server has already sent the 200 response code and + // some amount of data has already been sent to the client. + // The best we can do is terminate the response immediately + // and log the error. + clearInterval(i); + response.end(); + console.error('An error occurred:', err); + } + }); - setInterval(() => { + i = setInterval(() => { output.write(`The current time is ${Date()}\n`, () => { // The data has been passed to zlib, but the compression algorithm may // have decided to buffer the data for more efficient compression. @@ -399,7 +497,7 @@ changes: -Each zlib-based class takes an `options` object. All options are optional. +Each zlib-based class takes an `options` object. No options are required. Some options are only relevant when compressing and are ignored by the decompression classes. @@ -1058,6 +1156,6 @@ Decompress a chunk of data with [`Unzip`][]. [Brotli parameters]: #zlib_brotli_constants [Memory Usage Tuning]: #zlib_memory_usage_tuning [RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt -[pool size]: cli.html#cli_uv_threadpool_size_size +[Streams API]: stream.md [zlib documentation]: https://zlib.net/manual.html#Constants [zlib.createGzip example]: #zlib_zlib From 20a51b9dff69c3cc9c38e1e57c389db16ea02807 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Mon, 24 Feb 2020 18:50:13 -0800 Subject: [PATCH 074/192] repl: eager-evaluate input in parens PR-URL: https://github.com/nodejs/node/pull/31943 Reviewed-By: Gus Caplan Reviewed-By: James M Snell Reviewed-By: Ruben Bridgewater --- lib/repl.js | 12 ++++++------ test/parallel/test-repl-preview.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index a87e3c01c9b..00820953b55 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -326,12 +326,12 @@ function REPLServer(prompt, let awaitPromise = false; const input = code; - if (/^\s*{/.test(code) && /}\s*$/.test(code)) { - // It's confusing for `{ a : 1 }` to be interpreted as a block - // statement rather than an object literal. So, we first try - // to wrap it in parentheses, so that it will be interpreted as - // an expression. Note that if the above condition changes, - // lib/internal/repl/utils.js needs to be changed to match. + // It's confusing for `{ a : 1 }` to be interpreted as a block + // statement rather than an object literal. So, we first try + // to wrap it in parentheses, so that it will be interpreted as + // an expression. Note that if the above condition changes, + // lib/internal/repl/utils.js needs to be changed to match. + if (/^\s*{/.test(code) && !/;\s*$/.test(code)) { code = `(${code.trim()})\n`; wrappedCmd = true; } diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 82f1a9e507e..b36b99cca7c 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -88,6 +88,23 @@ async function tests(options) { '\x1B[36m[Function: koo]\x1B[39m', '\x1B[1G\x1B[0Jrepl > \x1B[8G'], ['a', [1, 2], undefined], + [" { b: 1 }['b'] === 1", [2, 6], '\x1B[33mtrue\x1B[39m', + " { b: 1 }['b']", + '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', + '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', + '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mtrue\x1B[39m', + '\x1B[1G\x1B[0Jrepl > \x1B[8G' + ], + ["{ b: 1 }['b'] === 1;", [2, 7], '\x1B[33mfalse\x1B[39m', + "{ b: 1 }['b']", + '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', + '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', + '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', + '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mfalse\x1B[39m', + '\x1B[1G\x1B[0Jrepl > \x1B[8G' + ], ['{ a: true }', [2, 3], '{ a: \x1B[33mtrue\x1B[39m }', '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\r', '{ a: \x1B[33mtrue\x1B[39m }', From 468bfd3487c5fba678bb6455add59e774ffbcd82 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Wed, 26 Feb 2020 10:31:29 +0200 Subject: [PATCH 075/192] test: increase timeout in vm-timeout-escape-queuemicrotask It looks like under high load the loop isn't even started and therefore successfully finishes without 'escaping'. After increasing the timeout during parallel run of the test failure rate decreased from 15/1000 to 0/1000. PR-URL: https://github.com/nodejs/node/pull/31966 Refs: https://github.com/nodejs/node/issues/25529 Reviewed-By: Luigi Pinca Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Rich Trott --- test/known_issues/test-vm-timeout-escape-queuemicrotask.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/known_issues/test-vm-timeout-escape-queuemicrotask.js b/test/known_issues/test-vm-timeout-escape-queuemicrotask.js index df0531bae1d..0d3a0b0c5c5 100644 --- a/test/known_issues/test-vm-timeout-escape-queuemicrotask.js +++ b/test/known_issues/test-vm-timeout-escape-queuemicrotask.js @@ -12,8 +12,8 @@ const NS_PER_MS = 1000000n; const hrtime = process.hrtime.bigint; -const loopDuration = common.platformTimeout(100n); -const timeout = common.platformTimeout(10); +const loopDuration = common.platformTimeout(1000n); +const timeout = common.platformTimeout(100); function loop() { const start = hrtime(); From 2ec9b58a4107bb915ada2e4fdf01cb41e9dff67c Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Mon, 24 Feb 2020 14:54:51 +0200 Subject: [PATCH 076/192] test: fix usage of invalid common properties PR-URL: https://github.com/nodejs/node/pull/31933 Reviewed-By: Luigi Pinca Reviewed-By: Shelley Vohr Reviewed-By: Yongsheng Zhang Reviewed-By: Rich Trott Reviewed-By: Anna Henningsen --- test/common/index.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/common/index.mjs b/test/common/index.mjs index a5774fc008a..96e6699e3c6 100644 --- a/test/common/index.mjs +++ b/test/common/index.mjs @@ -37,7 +37,6 @@ const { mustNotCall, printSkipMessage, skip, - ArrayStream, nodeProcessAborted, isAlive, expectWarning, @@ -83,7 +82,6 @@ export { mustNotCall, printSkipMessage, skip, - ArrayStream, nodeProcessAborted, isAlive, expectWarning, From 5c86475e201709ea2e15b3002c547abfbd3e823b Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Mon, 24 Feb 2020 14:55:14 +0200 Subject: [PATCH 077/192] test: validate common property usage `common` contains multiple 'check'(boolean) properties that will be false if mistyped and may lead to errors. This makes sure that the used property exists in the `common`. PR-URL: https://github.com/nodejs/node/pull/31933 Reviewed-By: Luigi Pinca Reviewed-By: Shelley Vohr Reviewed-By: Yongsheng Zhang Reviewed-By: Rich Trott Reviewed-By: Anna Henningsen --- test/common/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/common/index.js b/test/common/index.js index 1e66e32c9c1..653de4685ca 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -672,7 +672,7 @@ function invalidArgTypeHelper(input) { return ` Received type ${typeof input} (${inspected})`; } -module.exports = { +const common = { allowGlobals, buildType, canCreateSymLink, @@ -815,3 +815,12 @@ module.exports = { } }; + +const validProperties = new Set(Object.keys(common)); +module.exports = new Proxy(common, { + get(obj, prop) { + if (!validProperties.has(prop)) + throw new Error(`Using invalid common property: '${prop}'`); + return obj[prop]; + } +}); From a29b6cd921fddeaed07b3a68b4c4f75a26b3e61f Mon Sep 17 00:00:00 2001 From: cjihrig Date: Tue, 25 Feb 2020 21:38:34 -0500 Subject: [PATCH 078/192] build: add missing comma in node.gyp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a missing comma for consistency with the surrounding lines. PR-URL: https://github.com/nodejs/node/pull/31959 Reviewed-By: Jiawen Geng Reviewed-By: Anna Henningsen Reviewed-By: Richard Lau Reviewed-By: Ben Noordhuis Reviewed-By: Luigi Pinca Reviewed-By: Сковорода Никита Андреевич --- node.gyp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.gyp b/node.gyp index 1e928262ac8..9c632d38385 100644 --- a/node.gyp +++ b/node.gyp @@ -621,7 +621,7 @@ 'src/histogram-inl.h', 'src/js_stream.h', 'src/large_pages/node_large_page.cc', - 'src/large_pages/node_large_page.h' + 'src/large_pages/node_large_page.h', 'src/memory_tracker.h', 'src/memory_tracker-inl.h', 'src/module_wrap.h', From 18ddb1da38ad6eb78c357f182b5379a8ab3332ce Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Mon, 24 Feb 2020 19:14:15 -0800 Subject: [PATCH 079/192] src: move InternalCallbackScope to StartExecution PR-URL: https://github.com/nodejs/node/pull/31944 Reviewed-By: Anna Henningsen Reviewed-By: Stephen Belanger Reviewed-By: Joyee Cheung --- src/node.cc | 6 ++++++ src/node_main_instance.cc | 9 +-------- src/node_worker.cc | 5 ----- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/node.cc b/src/node.cc index aec70381c58..6b95796e126 100644 --- a/src/node.cc +++ b/src/node.cc @@ -395,6 +395,12 @@ MaybeLocal StartExecution(Environment* env, const char* main_script_id) { ->GetFunction(env->context()) .ToLocalChecked()}; + InternalCallbackScope callback_scope( + env, + Object::New(env->isolate()), + { 1, 0 }, + InternalCallbackScope::kSkipAsyncHooks); + return scope.EscapeMaybe( ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments)); } diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index d53eaa7329b..6f240d7e809 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -122,14 +122,7 @@ int NodeMainInstance::Run() { Context::Scope context_scope(env->context()); if (exit_code == 0) { - { - InternalCallbackScope callback_scope( - env.get(), - Object::New(isolate_), - { 1, 0 }, - InternalCallbackScope::kSkipAsyncHooks); - LoadEnvironment(env.get()); - } + LoadEnvironment(env.get()); env->set_trace_sync_io(env->options()->trace_sync_io); diff --git a/src/node_worker.cc b/src/node_worker.cc index b7532f8242a..d6e0ebb36f4 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -341,11 +341,6 @@ void Worker::Run() { env_->InitializeInspector(std::move(inspector_parent_handle_)); #endif HandleScope handle_scope(isolate_); - InternalCallbackScope callback_scope( - env_.get(), - Object::New(isolate_), - { 1, 0 }, - InternalCallbackScope::kSkipAsyncHooks); if (!env_->RunBootstrapping().IsEmpty()) { CreateEnvMessagePort(env_.get()); From 6510a741c4ed04288d56486a1384b1fbecd08eaf Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Mon, 24 Feb 2020 13:00:59 +0300 Subject: [PATCH 080/192] async_hooks: add store arg in AsyncLocalStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces store as the first argument in AsyncLocalStorage's run methods. The change is motivated by the following expectation: most users are going to use a custom object as the store and an extra Map created by the previous implementation is an overhead for their use case. Important note. This is a backwards incompatible change. It was discussed and agreed an incompatible change is ok since the API is still experimental and the modified methods were only added within the last week so usage will be minimal to none. PR-URL: https://github.com/nodejs/node/pull/31930 Reviewed-By: Stephen Belanger Reviewed-By: Vladimir de Turckheim Reviewed-By: Matteo Collina Reviewed-By: Michaël Zasso Reviewed-By: Michael Dawson --- .../async_hooks/async-resource-vs-destroy.js | 6 +-- doc/api/async_hooks.md | 46 ++++++++++--------- lib/async_hooks.js | 14 +++--- .../test-async-local-storage-args.js | 4 +- .../test-async-local-storage-async-await.js | 2 +- ...est-async-local-storage-async-functions.js | 2 +- ...test-async-local-storage-enable-disable.js | 4 +- .../test-async-local-storage-errors-async.js | 2 +- ...est-async-local-storage-errors-sync-ret.js | 2 +- .../test-async-local-storage-http.js | 2 +- .../test-async-local-storage-misc-stores.js | 24 ++++++++++ .../test-async-local-storage-nested.js | 4 +- ...est-async-local-storage-no-mix-contexts.js | 6 +-- .../test-async-local-storage-promises.js | 2 +- 14 files changed, 73 insertions(+), 47 deletions(-) create mode 100644 test/async-hooks/test-async-local-storage-misc-stores.js diff --git a/benchmark/async_hooks/async-resource-vs-destroy.js b/benchmark/async_hooks/async-resource-vs-destroy.js index 84e17ed56d8..c9b9a81c5b7 100644 --- a/benchmark/async_hooks/async-resource-vs-destroy.js +++ b/benchmark/async_hooks/async-resource-vs-destroy.js @@ -106,7 +106,7 @@ function buildDestroy(getServe) { function buildAsyncLocalStorage(getServe) { const asyncLocalStorage = new AsyncLocalStorage(); const server = createServer((req, res) => { - asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.runSyncAndReturn({}, () => { getServe(getCLS, setCLS)(req, res); }); }); @@ -118,12 +118,12 @@ function buildAsyncLocalStorage(getServe) { function getCLS() { const store = asyncLocalStorage.getStore(); - return store.get('store'); + return store.state; } function setCLS(state) { const store = asyncLocalStorage.getStore(); - store.set('store', state); + store.state = state; } function close() { diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 529c6cc5a83..965c64218fd 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -893,7 +893,7 @@ function log(...args) { } http.createServer((request, response) => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set(kReq, request); someAsyncOperation((err, result) => { @@ -943,27 +943,27 @@ in the current process. added: REPLACEME --> -* Returns: {Map} +* Returns: {any} This method returns the current store. If this method is called outside of an asynchronous context initialized by calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will return `undefined`. -### `asyncLocalStorage.run(callback[, ...args])` +### `asyncLocalStorage.run(store, callback[, ...args])` +* `store` {any} * `callback` {Function} * `...args` {any} Calling `asyncLocalStorage.run(callback)` will create a new asynchronous -context. -Within the callback function and the asynchronous operations from the callback, -`asyncLocalStorage.getStore()` will return an instance of `Map` known as -"the store". This store will be persistent through the following -asynchronous calls. +context. Within the callback function and the asynchronous operations from +the callback, `asyncLocalStorage.getStore()` will return the object or +the primitive value passed into the `store` argument (known as "the store"). +This store will be persistent through the following asynchronous calls. The callback will be ran asynchronously. Optionally, arguments can be passed to the function. They will be passed to the callback function. @@ -975,10 +975,11 @@ Also, the stacktrace will be impacted by the asynchronous call. Example: ```js -asyncLocalStorage.run(() => { - asyncLocalStorage.getStore(); // Returns a Map +const store = { id: 1 }; +asyncLocalStorage.run(store, () => { + asyncLocalStorage.getStore(); // Returns the store object someAsyncOperation(() => { - asyncLocalStorage.getStore(); // Returns the same Map + asyncLocalStorage.getStore(); // Returns the same object }); }); asyncLocalStorage.getStore(); // Returns undefined @@ -1007,20 +1008,21 @@ Also, the stacktrace will be impacted by the asynchronous call. Example: ```js -asyncLocalStorage.run(() => { - asyncLocalStorage.getStore(); // Returns a Map +asyncLocalStorage.run('store value', () => { + asyncLocalStorage.getStore(); // Returns 'store value' asyncLocalStorage.exit(() => { asyncLocalStorage.getStore(); // Returns undefined }); - asyncLocalStorage.getStore(); // Returns the same Map + asyncLocalStorage.getStore(); // Returns 'store value' }); ``` -### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])` +### `asyncLocalStorage.runSyncAndReturn(store, callback[, ...args])` +* `store` {any} * `callback` {Function} * `...args` {any} @@ -1038,9 +1040,10 @@ the context will be exited. Example: ```js +const store = { id: 2 }; try { - asyncLocalStorage.runSyncAndReturn(() => { - asyncLocalStorage.getStore(); // Returns a Map + asyncLocalStorage.runSyncAndReturn(store, () => { + asyncLocalStorage.getStore(); // Returns the store object throw new Error(); }); } catch (e) { @@ -1073,13 +1076,13 @@ Example: ```js // Within a call to run or runSyncAndReturn try { - asyncLocalStorage.getStore(); // Returns a Map + asyncLocalStorage.getStore(); // Returns the store object or value asyncLocalStorage.exitSyncAndReturn(() => { asyncLocalStorage.getStore(); // Returns undefined throw new Error(); }); } catch (e) { - asyncLocalStorage.getStore(); // Returns the same Map + asyncLocalStorage.getStore(); // Returns the same object or value // The error will be caught here } ``` @@ -1105,8 +1108,9 @@ It cannot be promisified using `util.promisify`. If needed, the `Promise` constructor can be used: ```js +const store = new Map(); // initialize the store new Promise((resolve, reject) => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(store, () => { someFunction((err, result) => { if (err) { return reject(err); @@ -1135,7 +1139,7 @@ the following pattern should be used: ```js async function fn() { - await asyncLocalStorage.runSyncAndReturn(() => { + await asyncLocalStorage.runSyncAndReturn(new Map(), () => { asyncLocalStorage.getStore().set('key', value); return foo(); // The return value of foo will be awaited }); diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 23f8ddde671..3797baf1832 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -1,11 +1,9 @@ 'use strict'; const { - Map, NumberIsSafeInteger, ReflectApply, Symbol, - } = primordials; const { @@ -247,14 +245,14 @@ class AsyncLocalStorage { } } - _enter() { + _enter(store) { if (!this.enabled) { this.enabled = true; storageList.push(this); storageHook.enable(); } const resource = executionAsyncResource(); - resource[this.kResourceStore] = new Map(); + resource[this.kResourceStore] = store; } _exit() { @@ -264,8 +262,8 @@ class AsyncLocalStorage { } } - runSyncAndReturn(callback, ...args) { - this._enter(); + runSyncAndReturn(store, callback, ...args) { + this._enter(store); try { return callback(...args); } finally { @@ -289,8 +287,8 @@ class AsyncLocalStorage { } } - run(callback, ...args) { - this._enter(); + run(store, callback, ...args) { + this._enter(store); process.nextTick(callback, ...args); this._exit(); } diff --git a/test/async-hooks/test-async-local-storage-args.js b/test/async-hooks/test-async-local-storage-args.js index 91a3385e6ee..04316dff59d 100644 --- a/test/async-hooks/test-async-local-storage-args.js +++ b/test/async-hooks/test-async-local-storage-args.js @@ -5,14 +5,14 @@ const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); -asyncLocalStorage.run((runArg) => { +asyncLocalStorage.run({}, (runArg) => { assert.strictEqual(runArg, 1); asyncLocalStorage.exit((exitArg) => { assert.strictEqual(exitArg, 2); }, 2); }, 1); -asyncLocalStorage.runSyncAndReturn((runArg) => { +asyncLocalStorage.runSyncAndReturn({}, (runArg) => { assert.strictEqual(runArg, 'foo'); asyncLocalStorage.exitSyncAndReturn((exitArg) => { assert.strictEqual(exitArg, 'bar'); diff --git a/test/async-hooks/test-async-local-storage-async-await.js b/test/async-hooks/test-async-local-storage-async-await.js index 28c8488da62..a03f803186b 100644 --- a/test/async-hooks/test-async-local-storage-async-await.js +++ b/test/async-hooks/test-async-local-storage-async-await.js @@ -12,7 +12,7 @@ async function test() { } async function main() { - await asyncLocalStorage.runSyncAndReturn(test); + await asyncLocalStorage.runSyncAndReturn(new Map(), test); assert.strictEqual(asyncLocalStorage.getStore(), undefined); } diff --git a/test/async-hooks/test-async-local-storage-async-functions.js b/test/async-hooks/test-async-local-storage-async-functions.js index 89ac0be62c7..a0852bc1098 100644 --- a/test/async-hooks/test-async-local-storage-async-functions.js +++ b/test/async-hooks/test-async-local-storage-async-functions.js @@ -19,7 +19,7 @@ async function testAwait() { await asyncLocalStorage.exitSyncAndReturn(testOut); } -asyncLocalStorage.run(() => { +asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('key', 'value'); testAwait(); // should not reject diff --git a/test/async-hooks/test-async-local-storage-enable-disable.js b/test/async-hooks/test-async-local-storage-enable-disable.js index c30d72eb805..bbba8cde58d 100644 --- a/test/async-hooks/test-async-local-storage-enable-disable.js +++ b/test/async-hooks/test-async-local-storage-enable-disable.js @@ -5,7 +5,7 @@ const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); -asyncLocalStorage.runSyncAndReturn(() => { +asyncLocalStorage.runSyncAndReturn(new Map(), () => { asyncLocalStorage.getStore().set('foo', 'bar'); process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar'); @@ -13,7 +13,7 @@ asyncLocalStorage.runSyncAndReturn(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); - asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.runSyncAndReturn(new Map(), () => { assert.notStrictEqual(asyncLocalStorage.getStore(), undefined); }); }); diff --git a/test/async-hooks/test-async-local-storage-errors-async.js b/test/async-hooks/test-async-local-storage-errors-async.js index c782b383e9c..b6f0b4fa742 100644 --- a/test/async-hooks/test-async-local-storage-errors-async.js +++ b/test/async-hooks/test-async-local-storage-errors-async.js @@ -13,7 +13,7 @@ process.setUncaughtExceptionCaptureCallback((err) => { assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node'); }); -asyncLocalStorage.run(() => { +asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('hello', 'node'); setTimeout(() => { diff --git a/test/async-hooks/test-async-local-storage-errors-sync-ret.js b/test/async-hooks/test-async-local-storage-errors-sync-ret.js index f112df2b99d..3b5c57a7347 100644 --- a/test/async-hooks/test-async-local-storage-errors-sync-ret.js +++ b/test/async-hooks/test-async-local-storage-errors-sync-ret.js @@ -14,7 +14,7 @@ process.setUncaughtExceptionCaptureCallback((err) => { }); try { - asyncLocalStorage.runSyncAndReturn(() => { + asyncLocalStorage.runSyncAndReturn(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('hello', 'node'); setTimeout(() => { diff --git a/test/async-hooks/test-async-local-storage-http.js b/test/async-hooks/test-async-local-storage-http.js index 9f107148402..c7514d8280d 100644 --- a/test/async-hooks/test-async-local-storage-http.js +++ b/test/async-hooks/test-async-local-storage-http.js @@ -10,7 +10,7 @@ const server = http.createServer((req, res) => { }); server.listen(0, () => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('hello', 'world'); http.get({ host: 'localhost', port: server.address().port }, () => { diff --git a/test/async-hooks/test-async-local-storage-misc-stores.js b/test/async-hooks/test-async-local-storage-misc-stores.js new file mode 100644 index 00000000000..56873008dd6 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-misc-stores.js @@ -0,0 +1,24 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run(42, () => { + assert.strictEqual(asyncLocalStorage.getStore(), 42); +}); + +const runStore = { foo: 'bar' }; +asyncLocalStorage.run(runStore, () => { + assert.strictEqual(asyncLocalStorage.getStore(), runStore); +}); + +asyncLocalStorage.runSyncAndReturn('hello node', () => { + assert.strictEqual(asyncLocalStorage.getStore(), 'hello node'); +}); + +const runSyncStore = { hello: 'node' }; +asyncLocalStorage.runSyncAndReturn(runSyncStore, () => { + assert.strictEqual(asyncLocalStorage.getStore(), runSyncStore); +}); diff --git a/test/async-hooks/test-async-local-storage-nested.js b/test/async-hooks/test-async-local-storage-nested.js index 38330fff607..1409a8ebc82 100644 --- a/test/async-hooks/test-async-local-storage-nested.js +++ b/test/async-hooks/test-async-local-storage-nested.js @@ -6,9 +6,9 @@ const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); setTimeout(() => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(new Map(), () => { const asyncLocalStorage2 = new AsyncLocalStorage(); - asyncLocalStorage2.run(() => { + asyncLocalStorage2.run(new Map(), () => { const store = asyncLocalStorage.getStore(); const store2 = asyncLocalStorage2.getStore(); store.set('hello', 'world'); diff --git a/test/async-hooks/test-async-local-storage-no-mix-contexts.js b/test/async-hooks/test-async-local-storage-no-mix-contexts.js index 561df546d4a..3a6b352c94c 100644 --- a/test/async-hooks/test-async-local-storage-no-mix-contexts.js +++ b/test/async-hooks/test-async-local-storage-no-mix-contexts.js @@ -7,8 +7,8 @@ const asyncLocalStorage = new AsyncLocalStorage(); const asyncLocalStorage2 = new AsyncLocalStorage(); setTimeout(() => { - asyncLocalStorage.run(() => { - asyncLocalStorage2.run(() => { + asyncLocalStorage.run(new Map(), () => { + asyncLocalStorage2.run(new Map(), () => { const store = asyncLocalStorage.getStore(); const store2 = asyncLocalStorage2.getStore(); store.set('hello', 'world'); @@ -28,7 +28,7 @@ setTimeout(() => { }, 100); setTimeout(() => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('hello', 'earth'); setTimeout(() => { diff --git a/test/async-hooks/test-async-local-storage-promises.js b/test/async-hooks/test-async-local-storage-promises.js index 3b05d0f1981..0e4968534bc 100644 --- a/test/async-hooks/test-async-local-storage-promises.js +++ b/test/async-hooks/test-async-local-storage-promises.js @@ -12,7 +12,7 @@ async function main() { throw err; }); await new Promise((resolve, reject) => { - asyncLocalStorage.run(() => { + asyncLocalStorage.run(new Map(), () => { const store = asyncLocalStorage.getStore(); store.set('a', 1); next().then(resolve, reject); From 65e18a8e9f912dfa04a804124b6a19514bb45165 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Fri, 21 Feb 2020 17:30:02 -0800 Subject: [PATCH 081/192] src: don't run bootstrapper in CreateEnvironment PR-URL: https://github.com/nodejs/node/pull/31910 Reviewed-By: Anna Henningsen Reviewed-By: Joyee Cheung --- src/api/environment.cc | 17 +--------------- test/cctest/test_environment.cc | 35 +++++++++++++++++---------------- 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/api/environment.cc b/src/api/environment.cc index d2eaafeb27a..456854a318e 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -347,23 +347,8 @@ Environment* CreateEnvironment(IsolateData* isolate_data, Environment::kOwnsProcessState | Environment::kOwnsInspector)); env->InitializeLibuv(per_process::v8_is_profiling); - if (env->RunBootstrapping().IsEmpty()) { + if (env->RunBootstrapping().IsEmpty()) return nullptr; - } - - std::vector> parameters = { - env->require_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; - std::vector> arguments = { - env->native_module_require(), - env->NewFunctionTemplate(MarkBootstrapComplete) - ->GetFunction(env->context()) - .ToLocalChecked()}; - if (ExecuteBootstrapper( - env, "internal/bootstrap/environment", ¶meters, &arguments) - .IsEmpty()) { - return nullptr; - } return env; } diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 132f7b44f7d..90c5cff5e09 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -32,23 +32,24 @@ class EnvironmentTest : public EnvironmentTestFixture { } }; -TEST_F(EnvironmentTest, PreExeuctionPreparation) { - const v8::HandleScope handle_scope(isolate_); - const Argv argv; - Env env {handle_scope, argv}; - - v8::Local context = isolate_->GetCurrentContext(); - - const char* run_script = "process.argv0"; - v8::Local script = v8::Script::Compile( - context, - v8::String::NewFromOneByte(isolate_, - reinterpret_cast(run_script), - v8::NewStringType::kNormal).ToLocalChecked()) - .ToLocalChecked(); - v8::Local result = script->Run(context).ToLocalChecked(); - CHECK(result->IsString()); -} +// TODO(codebytere): re-enable this test. +// TEST_F(EnvironmentTest, PreExeuctionPreparation) { +// const v8::HandleScope handle_scope(isolate_); +// const Argv argv; +// Env env {handle_scope, argv}; + +// v8::Local context = isolate_->GetCurrentContext(); + +// const char* run_script = "process.argv0"; +// v8::Local script = v8::Script::Compile( +// context, +// v8::String::NewFromOneByte(isolate_, +// reinterpret_cast(run_script), +// v8::NewStringType::kNormal).ToLocalChecked()) +// .ToLocalChecked(); +// v8::Local result = script->Run(context).ToLocalChecked(); +// CHECK(result->IsString()); +// } TEST_F(EnvironmentTest, AtExitWithEnvironment) { const v8::HandleScope handle_scope(isolate_); From a777cfa843d5b996e45c4f8188733b637594378b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 19 Feb 2020 17:06:29 +0100 Subject: [PATCH 082/192] doc: remove repetition Merge two similar sentences into one. PR-URL: https://github.com/nodejs/node/pull/31868 Reviewed-By: Gireesh Punathil Reviewed-By: Colin Ihrig Reviewed-By: Robert Nagy Reviewed-By: James M Snell Reviewed-By: Michael Dawson --- doc/api/stream.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/stream.md b/doc/api/stream.md index 3d37f2b3003..efced396c88 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -1887,11 +1887,11 @@ This function MUST NOT be called by application code directly. It should be implemented by child classes, and called by the internal `Writable` class methods only. -The `callback` method must be called to signal either that the write completed -successfully or failed with an error. The first argument passed to the -`callback` must be the `Error` object if the call failed or `null` if the -write succeeded. The `callback` must be called synchronously inside of -`writable._write()` or asynchronously (i.e. different tick). +The `callback` function must be called synchronously inside of +`writable._write()` or asynchronously (i.e. different tick) to signal either +that the write completed successfully or failed with an error. +The first argument passed to the `callback` must be the `Error` object if the +call failed or `null` if the write succeeded. All calls to `writable.write()` that occur between the time `writable._write()` is called and the `callback` is called will cause the written data to be From fb26b136238240eeac1b3ede098e097a343aef0b Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 23 Feb 2020 19:19:15 -0800 Subject: [PATCH 083/192] module: port source map sort logic from chromium Digging in to the delta between V8's source map library, and chromium's the most significant difference that jumped out at me was that we were failing to sort generated columns. Since negative offsets are not restricted in the spec, this can lead to bugs. fixes: #31286 PR-URL: https://github.com/nodejs/node/pull/31927 Fixes: https://github.com/nodejs/node/issues/31286 Reviewed-By: Joyee Cheung Reviewed-By: Rich Trott --- lib/internal/source_map/source_map.js | 21 +++++++++++++++++++-- test/parallel/test-source-map-api.js | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index c440dffdf81..acff068be2a 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -152,10 +152,12 @@ class SourceMap { * @param {SourceMapV3} mappingPayload */ #parseMappingPayload = () => { - if (this.#payload.sections) + if (this.#payload.sections) { this.#parseSections(this.#payload.sections); - else + } else { this.#parseMap(this.#payload, 0, 0); + } + this.#mappings.sort(compareSourceMapEntry); } /** @@ -321,6 +323,21 @@ function cloneSourceMapV3(payload) { return payload; } +/** + * @param {Array} entry1 source map entry [lineNumber, columnNumber, sourceURL, + * sourceLineNumber, sourceColumnNumber] + * @param {Array} entry2 source map entry. + * @return {number} + */ +function compareSourceMapEntry(entry1, entry2) { + const [lineNumber1, columnNumber1] = entry1; + const [lineNumber2, columnNumber2] = entry2; + if (lineNumber1 !== lineNumber2) { + return lineNumber1 - lineNumber2; + } + return columnNumber1 - columnNumber2; +} + module.exports = { SourceMap }; diff --git a/test/parallel/test-source-map-api.js b/test/parallel/test-source-map-api.js index 2bfbc08809e..60bbb661e1c 100644 --- a/test/parallel/test-source-map-api.js +++ b/test/parallel/test-source-map-api.js @@ -124,3 +124,28 @@ const { readFileSync } = require('fs'); assert.strictEqual(originalColumn, knownDecodings[column]); } } + +// Test that generated columns are sorted when a negative offset is +// observed, see: https://github.com/mozilla/source-map/pull/92 +{ + function makeMinimalMap(generatedColumns, originalColumns) { + return { + sources: ['test.js'], + // Mapping from the 0th line, ${g}th column of the output file to the 0th + // source file, 0th line, ${column}th column. + mappings: generatedColumns.map((g, i) => `${g}AA${originalColumns[i]}`) + .join(',') + }; + } + // U = 10 + // F = -2 + // A = 0 + // E = 2 + const sourceMap = new SourceMap(makeMinimalMap( + ['U', 'F', 'F'], + ['A', 'E', 'E'] + )); + assert.strictEqual(sourceMap.findEntry(0, 6).originalColumn, 4); + assert.strictEqual(sourceMap.findEntry(0, 8).originalColumn, 2); + assert.strictEqual(sourceMap.findEntry(0, 10).originalColumn, 0); +} From 1b2e2944bc069abd1dd7cbf90d3badad4289235d Mon Sep 17 00:00:00 2001 From: cjihrig Date: Tue, 25 Feb 2020 20:18:35 -0500 Subject: [PATCH 084/192] dgram: don't hide implicit bind errors When dgram socket implicit binding fails, an attempt is made to clean up the send queue. This was originally implemented using an 'error' handler that performed cleanup and then emitted a fake error, which concealed the original error. This was done to prevent cases where the same error was emitted twice. Now that the errorMonitor event is available, use that to perform the cleanup without impacting the actual error handling. PR-URL: https://github.com/nodejs/node/pull/31958 Refs: https://github.com/nodejs/help/issues/2484 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- lib/dgram.js | 6 +-- .../test-dgram-implicit-bind-failure.js | 47 ++++++------------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index 26d1e1d11a0..15421de4649 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -45,7 +45,6 @@ const { ERR_SOCKET_BAD_BUFFER_SIZE, ERR_SOCKET_BAD_PORT, ERR_SOCKET_BUFFER_SIZE, - ERR_SOCKET_CANNOT_SEND, ERR_SOCKET_DGRAM_IS_CONNECTED, ERR_SOCKET_DGRAM_NOT_CONNECTED, ERR_SOCKET_DGRAM_NOT_RUNNING, @@ -506,7 +505,7 @@ function enqueue(self, toEnqueue) { // event handler that flushes the send queue after binding is done. if (state.queue === undefined) { state.queue = []; - self.once('error', onListenError); + self.once(EventEmitter.errorMonitor, onListenError); self.once('listening', onListenSuccess); } state.queue.push(toEnqueue); @@ -514,7 +513,7 @@ function enqueue(self, toEnqueue) { function onListenSuccess() { - this.removeListener('error', onListenError); + this.removeListener(EventEmitter.errorMonitor, onListenError); clearQueue.call(this); } @@ -522,7 +521,6 @@ function onListenSuccess() { function onListenError(err) { this.removeListener('listening', onListenSuccess); this[kStateSymbol].queue = undefined; - this.emit('error', new ERR_SOCKET_CANNOT_SEND()); } diff --git a/test/sequential/test-dgram-implicit-bind-failure.js b/test/sequential/test-dgram-implicit-bind-failure.js index d77db12618f..89da00d5766 100644 --- a/test/sequential/test-dgram-implicit-bind-failure.js +++ b/test/sequential/test-dgram-implicit-bind-failure.js @@ -2,48 +2,31 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); +const EventEmitter = require('events'); const dgram = require('dgram'); const dns = require('dns'); const { kStateSymbol } = require('internal/dgram'); +const mockError = new Error('fake DNS'); // Monkey patch dns.lookup() so that it always fails. dns.lookup = function(address, family, callback) { - process.nextTick(() => { callback(new Error('fake DNS')); }); + process.nextTick(() => { callback(mockError); }); }; const socket = dgram.createSocket('udp4'); -let dnsFailures = 0; -let sendFailures = 0; -process.on('exit', () => { - assert.strictEqual(dnsFailures, 3); - assert.strictEqual(sendFailures, 3); -}); - -socket.on('error', (err) => { - if (/^Error: fake DNS$/.test(err)) { - // The DNS lookup should fail since it is monkey patched. At that point in - // time, the send queue should be populated with the send() operation. There - // should also be two listeners - this function and the dgram internal one - // time error handler. - dnsFailures++; - assert(Array.isArray(socket[kStateSymbol].queue)); - assert.strictEqual(socket[kStateSymbol].queue.length, 1); - assert.strictEqual(socket.listenerCount('error'), 2); - return; - } - - if (err.code === 'ERR_SOCKET_CANNOT_SEND') { - // On error, the queue should be destroyed and this function should be - // the only listener. - sendFailures++; - assert.strictEqual(socket[kStateSymbol].queue, undefined); - assert.strictEqual(socket.listenerCount('error'), 1); - return; - } - - assert.fail(`Unexpected error: ${err}`); -}); +socket.on(EventEmitter.errorMonitor, common.mustCall((err) => { + // The DNS lookup should fail since it is monkey patched. At that point in + // time, the send queue should be populated with the send() operation. + assert.strictEqual(err, mockError); + assert(Array.isArray(socket[kStateSymbol].queue)); + assert.strictEqual(socket[kStateSymbol].queue.length, 1); +}, 3)); + +socket.on('error', common.mustCall((err) => { + assert.strictEqual(err, mockError); + assert.strictEqual(socket[kStateSymbol].queue, undefined); +}, 3)); // Initiate a few send() operations, which will fail. socket.send('foobar', common.PORT, 'localhost'); From 331d63624007be4bf49d6d161bdef2b5e540affa Mon Sep 17 00:00:00 2001 From: cjihrig Date: Tue, 25 Feb 2020 20:51:40 -0500 Subject: [PATCH 085/192] errors: remove unused ERR_SOCKET_CANNOT_SEND error This error is no longer used within core. This commit removes it. PR-URL: https://github.com/nodejs/node/pull/31958 Refs: https://github.com/nodejs/help/issues/2484 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- doc/api/errors.md | 14 +++++++++----- lib/internal/errors.js | 1 - 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index e3eb498e96a..11d0036386c 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1727,11 +1727,6 @@ value. While using [`dgram.createSocket()`][], the size of the receive or send `Buffer` could not be determined. - -### `ERR_SOCKET_CANNOT_SEND` - -Data could be sent on a socket. - ### `ERR_SOCKET_CLOSED` @@ -2293,6 +2288,15 @@ removed: v10.0.0 The `repl` module was unable to parse data from the REPL history file. + +### `ERR_SOCKET_CANNOT_SEND` + + +Data could be sent on a socket. + ### `ERR_STDERR_CLOSE` -> Stability: 1 - Experimental. The feature is not subject to Semantic Versioning -> rules. Non-backward compatible changes or removal may occur in any future -> release. Use of the feature is not recommended in production environments. +> Stability: 1 - Experimental. The feature is not subject to +> [Semantic Versioning][] rules. Non-backward compatible changes or removal may +> occur in any future release. Use of the feature is not recommended in +> production environments. @@ -58,6 +59,7 @@ to the corresponding man pages which describe how the system call works. Most Unix system calls have Windows analogues. Still, behavior differences may be unavoidable. +[Semantic Versioning]: https://semver.org/ [the contributing guide]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md [the issue tracker]: https://github.com/nodejs/node/issues/new [V8 JavaScript engine]: https://v8.dev/ From fb74e98243e3fabd5fa45587cf1c68948673662e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Fri, 28 Feb 2020 20:37:07 -0800 Subject: [PATCH 100/192] test: fix flaky test-dns-any.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove google.com from domains tested with ANY queries. Fixes: https://github.com/nodejs/node/issues/31721 PR-URL: https://github.com/nodejs/node/pull/32017 Reviewed-By: Anna Henningsen Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca --- test/internet/test-dns-any.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/test/internet/test-dns-any.js b/test/internet/test-dns-any.js index 3e8eb07e7e6..d60f00f0980 100644 --- a/test/internet/test-dns-any.js +++ b/test/internet/test-dns-any.js @@ -115,28 +115,6 @@ function processResult(res) { return types; } -TEST(async function test_google(done) { - function validateResult(res) { - const types = processResult(res); - assert.ok( - types.A && types.AAAA && types.MX && types.NS && types.TXT && types.SOA, - `Missing record type, found ${Object.keys(types)}`); - } - - validateResult(await dnsPromises.resolve('google.com', 'ANY')); - - const req = dns.resolve( - 'google.com', - 'ANY', - common.mustCall(function(err, ret) { - assert.ifError(err); - validateResult(ret); - done(); - })); - - checkWrap(req); -}); - TEST(async function test_sip2sip_for_naptr(done) { function validateResult(res) { const types = processResult(res); From 4c35b62f64a72eb5234ba805c19add36e46b1e9e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Fri, 28 Feb 2020 22:12:47 -0800 Subject: [PATCH 101/192] doc: revise --zero-fill-buffers text in buffer.md There was an unclear sentence fragment that needed fixing, so I edited the entire paragraph for clarity. I also removed irrelevant information about behavior before Node.js 8.0.0. That version of Node.js is no longer supported and these docs will never apply to 8.0.0. (At the time of this writing, 10.x is the oldest supported line, and so changes to the docs will never be backported farther than the 10.x docs.) PR-URL: https://github.com/nodejs/node/pull/32019 Reviewed-By: Anna Henningsen Reviewed-By: David Carlier Reviewed-By: Ruben Bridgewater --- doc/api/buffer.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 0d7dee9df45..ed8da05bc47 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -123,15 +123,12 @@ added: v5.10.0 --> Node.js can be started using the `--zero-fill-buffers` command line option to -cause all newly allocated `Buffer` instances to be zero-filled upon creation by -default. Before Node.js 8.0.0, this included buffers allocated by `new -Buffer(size)`. Since Node.js 8.0.0, buffers allocated with `new` are always -zero-filled, whether this option is used or not. -[`Buffer.allocUnsafe()`][], [`Buffer.allocUnsafeSlow()`][], and `new -SlowBuffer(size)`. Use of this flag can have a significant negative impact on -performance. Use of the `--zero-fill-buffers` option is recommended only when -necessary to enforce that newly allocated `Buffer` instances cannot contain old -data that is potentially sensitive. +cause all newly-allocated `Buffer` instances to be zero-filled upon creation by +default. Without the option, buffers created with [`Buffer.allocUnsafe()`][], +[`Buffer.allocUnsafeSlow()`][], and `new SlowBuffer(size)` are not zero-filled. +Use of this flag can have a significant negative impact on performance. Use the +`--zero-fill-buffers` option only when necessary to enforce that newly allocated +`Buffer` instances cannot contain old data that is potentially sensitive. ```console $ node --zero-fill-buffers From 68e36ade3de2205c583f2cc6a2d2ec192b75cc95 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Fri, 28 Feb 2020 10:07:47 +0300 Subject: [PATCH 102/192] test: improve disable AsyncLocalStorage test PR-URL: https://github.com/nodejs/node/pull/31998 Reviewed-By: Anna Henningsen Reviewed-By: Vladimir de Turckheim Reviewed-By: James M Snell --- test/async-hooks/test-async-local-storage-enable-disable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/async-hooks/test-async-local-storage-enable-disable.js b/test/async-hooks/test-async-local-storage-enable-disable.js index bbba8cde58d..22a3f5f6c8c 100644 --- a/test/async-hooks/test-async-local-storage-enable-disable.js +++ b/test/async-hooks/test-async-local-storage-enable-disable.js @@ -9,6 +9,9 @@ asyncLocalStorage.runSyncAndReturn(new Map(), () => { asyncLocalStorage.getStore().set('foo', 'bar'); process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar'); + process.nextTick(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); asyncLocalStorage.disable(); assert.strictEqual(asyncLocalStorage.getStore(), undefined); process.nextTick(() => { From 0fac393d263fc7e2f4f054c9d4aab0c1c3cf00c8 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 25 Feb 2020 14:37:33 -0800 Subject: [PATCH 103/192] src: improve handling of internal field counting Change suggested by bnoordhuis. Improve handing of internal field counting by using enums. Helps protect against future possible breakage if field indexes are ever changed or added to. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/31960 Reviewed-By: Anna Henningsen Reviewed-By: Franziska Hinkelmann Reviewed-By: Ben Noordhuis Reviewed-By: Matteo Collina --- src/async_wrap.cc | 22 ++++++++++++---------- src/base_object-inl.h | 14 +++++++++----- src/base_object.h | 2 ++ src/cares_wrap.cc | 3 ++- src/fs_event_wrap.cc | 3 ++- src/heap_utils.cc | 2 +- src/inspector_js_api.cc | 3 ++- src/js_stream.cc | 2 +- src/module_wrap.cc | 3 ++- src/node_contextify.cc | 14 +++++++++----- src/node_contextify.h | 1 + src/node_crypto.cc | 26 +++++++++++++++++--------- src/node_dir.cc | 2 +- src/node_dir.h | 2 -- src/node_file.cc | 12 +++++++----- src/node_http2.cc | 9 +++++---- src/node_http_parser.cc | 2 +- src/node_i18n.cc | 2 +- src/node_messaging.cc | 3 ++- src/node_perf.cc | 3 ++- src/node_serdes.cc | 6 ++++-- src/node_stat_watcher.cc | 3 ++- src/node_trace_events.cc | 3 ++- src/node_util.cc | 3 ++- src/node_wasi.cc | 2 +- src/node_watchdog.cc | 3 ++- src/node_worker.cc | 6 ++++-- src/node_zlib.cc | 3 ++- src/pipe_wrap.cc | 2 +- src/process_wrap.cc | 3 ++- src/signal_wrap.cc | 3 ++- src/stream_base-inl.h | 22 ++++++++++++++-------- src/stream_base.cc | 10 +++++++--- src/stream_base.h | 22 +++++++++++++++++----- src/stream_pipe.cc | 3 ++- src/stream_wrap.cc | 8 ++++---- src/tcp_wrap.cc | 3 +-- src/tls_wrap.cc | 3 +-- src/tty_wrap.cc | 3 +-- src/udp_wrap.cc | 2 +- 40 files changed, 151 insertions(+), 92 deletions(-) diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 315d4177c8c..892e33c624b 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -176,6 +176,10 @@ void AsyncWrap::EmitAfter(Environment* env, double async_id) { class PromiseWrap : public AsyncWrap { public: + enum InternalFields { + kIsChainedPromiseField = AsyncWrap::kInternalFieldCount, + kInternalFieldCount + }; PromiseWrap(Environment* env, Local object, bool silent) : AsyncWrap(env, object, PROVIDER_PROMISE, kInvalidAsyncId, silent) { MakeWeak(); @@ -185,9 +189,6 @@ class PromiseWrap : public AsyncWrap { SET_MEMORY_INFO_NAME(PromiseWrap) SET_SELF_SIZE(PromiseWrap) - static constexpr int kIsChainedPromiseField = 1; - static constexpr int kInternalFieldCount = 2; - static PromiseWrap* New(Environment* env, Local promise, PromiseWrap* parent_wrap, @@ -214,15 +215,16 @@ PromiseWrap* PromiseWrap::New(Environment* env, void PromiseWrap::getIsChainedPromise(Local property, const PropertyCallbackInfo& info) { info.GetReturnValue().Set( - info.Holder()->GetInternalField(kIsChainedPromiseField)); + info.Holder()->GetInternalField(PromiseWrap::kIsChainedPromiseField)); } static PromiseWrap* extractPromiseWrap(Local promise) { - Local resource_object_value = promise->GetInternalField(0); - if (resource_object_value->IsObject()) { - return Unwrap(resource_object_value.As()); - } - return nullptr; + // This check is imperfect. If the internal field is set, it should + // be an object. If it's not, we just ignore it. Ideally v8 would + // have had GetInternalField returning a MaybeLocal but this works + // for now. + Local obj = promise->GetInternalField(0); + return obj->IsObject() ? Unwrap(obj.As()) : nullptr; } static void PromiseHook(PromiseHookType type, Local promise, @@ -560,7 +562,7 @@ void AsyncWrap::Initialize(Local target, function_template->SetClassName(class_name); function_template->Inherit(AsyncWrap::GetConstructorTemplate(env)); auto instance_template = function_template->InstanceTemplate(); - instance_template->SetInternalFieldCount(1); + instance_template->SetInternalFieldCount(AsyncWrap::kInternalFieldCount); auto function = function_template->GetFunction(env->context()).ToLocalChecked(); target->Set(env->context(), class_name, function).Check(); diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 8b2b30021a8..c9b4e1491fc 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -43,7 +43,9 @@ BaseObject::BaseObject(Environment* env, v8::Local object) : persistent_handle_(env->isolate(), object), env_(env) { CHECK_EQ(false, object.IsEmpty()); CHECK_GT(object->InternalFieldCount(), 0); - object->SetAlignedPointerInInternalField(0, static_cast(this)); + object->SetAlignedPointerInInternalField( + BaseObject::kSlot, + static_cast(this)); env->AddCleanupHook(DeleteMe, static_cast(this)); env->modify_base_object_count(1); } @@ -67,7 +69,7 @@ BaseObject::~BaseObject() { { v8::HandleScope handle_scope(env()->isolate()); - object()->SetAlignedPointerInInternalField(0, nullptr); + object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr); } } @@ -100,7 +102,8 @@ Environment* BaseObject::env() const { BaseObject* BaseObject::FromJSObject(v8::Local obj) { CHECK_GT(obj->InternalFieldCount(), 0); - return static_cast(obj->GetAlignedPointerFromInternalField(0)); + return static_cast( + obj->GetAlignedPointerFromInternalField(BaseObject::kSlot)); } @@ -148,11 +151,12 @@ BaseObject::MakeLazilyInitializedJSTemplate(Environment* env) { auto constructor = [](const v8::FunctionCallbackInfo& args) { DCHECK(args.IsConstructCall()); DCHECK_GT(args.This()->InternalFieldCount(), 0); - args.This()->SetAlignedPointerInInternalField(0, nullptr); + args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr); }; v8::Local t = env->NewFunctionTemplate(constructor); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + BaseObject::kInternalFieldCount); return t; } diff --git a/src/base_object.h b/src/base_object.h index e7ef029995f..2c67445c31f 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -36,6 +36,8 @@ class BaseObjectPtrImpl; class BaseObject : public MemoryRetainer { public: + enum InternalFields { kSlot, kInternalFieldCount }; + // Associates this object with `object`. It uses the 0th internal field for // that, and in particular aborts if there is no such field. inline BaseObject(Environment* env, v8::Local object); diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 8eeaeddf830..8abf662caa3 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -2223,7 +2223,8 @@ void Initialize(Local target, Local channel_wrap = env->NewFunctionTemplate(ChannelWrap::New); - channel_wrap->InstanceTemplate()->SetInternalFieldCount(1); + channel_wrap->InstanceTemplate()->SetInternalFieldCount( + ChannelWrap::kInternalFieldCount); channel_wrap->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(channel_wrap, "queryAny", Query); diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index d38556b1bf8..858455bff2d 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -97,7 +97,8 @@ void FSEventWrap::Initialize(Local target, auto fsevent_string = FIXED_ONE_BYTE_STRING(env->isolate(), "FSEvent"); Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + FSEventWrap::kInternalFieldCount); t->SetClassName(fsevent_string); t->Inherit(AsyncWrap::GetConstructorTemplate(env)); diff --git a/src/heap_utils.cc b/src/heap_utils.cc index 68cd532c230..c21ff8c8006 100644 --- a/src/heap_utils.cc +++ b/src/heap_utils.cc @@ -341,7 +341,7 @@ BaseObjectPtr CreateHeapSnapshotStream( Local os = FunctionTemplate::New(env->isolate()); os->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local ost = os->InstanceTemplate(); - ost->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + ost->SetInternalFieldCount(StreamBase::kInternalFieldCount); os->SetClassName( FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream")); StreamBase::AddMethods(env, os); diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 703c9ff598f..ed3b36ad5ca 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -105,7 +105,8 @@ class JSBindingsConnection : public AsyncWrap { Local class_name = ConnectionType::GetClassName(env); Local tmpl = env->NewFunctionTemplate(JSBindingsConnection::New); - tmpl->InstanceTemplate()->SetInternalFieldCount(1); + tmpl->InstanceTemplate()->SetInternalFieldCount( + JSBindingsConnection::kInternalFieldCount); tmpl->SetClassName(class_name); tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch); diff --git a/src/js_stream.cc b/src/js_stream.cc index 64941b1c4e4..e4da0ce747e 100644 --- a/src/js_stream.cc +++ b/src/js_stream.cc @@ -204,7 +204,7 @@ void JSStream::Initialize(Local target, FIXED_ONE_BYTE_STRING(env->isolate(), "JSStream"); t->SetClassName(jsStreamString); t->InstanceTemplate() - ->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + ->SetInternalFieldCount(StreamBase::kInternalFieldCount); t->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(t, "finishWrite", Finish); diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 5d758bac620..2b1708088bf 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -1636,7 +1636,8 @@ void ModuleWrap::Initialize(Local target, Local tpl = env->NewFunctionTemplate(New); tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap")); - tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->InstanceTemplate()->SetInternalFieldCount( + ModuleWrap::kInternalFieldCount); env->SetProtoMethod(tpl, "link", Link); env->SetProtoMethod(tpl, "instantiate", Instantiate); diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 6de27188be6..e6e2d123c04 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -145,7 +145,7 @@ MaybeLocal ContextifyContext::CreateDataWrapper(Environment* env) { return MaybeLocal(); } - wrapper->SetAlignedPointerInInternalField(0, this); + wrapper->SetAlignedPointerInInternalField(ContextifyContext::kSlot, this); return wrapper; } @@ -232,7 +232,8 @@ MaybeLocal ContextifyContext::CreateV8Context( void ContextifyContext::Init(Environment* env, Local target) { Local function_template = FunctionTemplate::New(env->isolate()); - function_template->InstanceTemplate()->SetInternalFieldCount(1); + function_template->InstanceTemplate()->SetInternalFieldCount( + ContextifyContext::kInternalFieldCount); env->set_script_data_constructor_function( function_template->GetFunction(env->context()).ToLocalChecked()); @@ -331,7 +332,8 @@ template ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo& args) { Local data = args.Data(); return static_cast( - data.As()->GetAlignedPointerFromInternalField(0)); + data.As()->GetAlignedPointerFromInternalField( + ContextifyContext::kSlot)); } // static @@ -628,7 +630,8 @@ void ContextifyScript::Init(Environment* env, Local target) { FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); Local script_tmpl = env->NewFunctionTemplate(New); - script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + script_tmpl->InstanceTemplate()->SetInternalFieldCount( + ContextifyScript::kInternalFieldCount); script_tmpl->SetClassName(class_name); env->SetProtoMethod(script_tmpl, "createCachedData", CreateCachedData); env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); @@ -1251,7 +1254,8 @@ void Initialize(Local target, { Local tpl = FunctionTemplate::New(env->isolate()); tpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "CompiledFnEntry")); - tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->InstanceTemplate()->SetInternalFieldCount( + CompiledFnEntry::kInternalFieldCount); env->set_compiled_fn_entry_template(tpl->InstanceTemplate()); } diff --git a/src/node_contextify.h b/src/node_contextify.h index cf1e8475075..f04ea86f41a 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -19,6 +19,7 @@ struct ContextOptions { class ContextifyContext { public: + enum InternalFields { kSlot, kInternalFieldCount }; ContextifyContext(Environment* env, v8::Local sandbox_obj, const ContextOptions& options); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 08982cfcfa1..0b48821cf6c 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -464,7 +464,8 @@ static T* MallocOpenSSL(size_t count) { void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + SecureContext::kInternalFieldCount); Local secureContextString = FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"); t->SetClassName(secureContextString); @@ -3803,7 +3804,8 @@ EVP_PKEY* ManagedEVPPKey::get() const { Local KeyObject::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + KeyObject::kInternalFieldCount); env->SetProtoMethod(t, "init", Init); env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize", @@ -4036,7 +4038,8 @@ CipherBase::CipherBase(Environment* env, void CipherBase::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + CipherBase::kInternalFieldCount); env->SetProtoMethod(t, "init", Init); env->SetProtoMethod(t, "initiv", InitIv); @@ -4663,7 +4666,8 @@ Hmac::Hmac(Environment* env, v8::Local wrap) void Hmac::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + Hmac::kInternalFieldCount); env->SetProtoMethod(t, "init", HmacInit); env->SetProtoMethod(t, "update", HmacUpdate); @@ -4789,7 +4793,8 @@ Hash::Hash(Environment* env, v8::Local wrap) void Hash::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + Hash::kInternalFieldCount); env->SetProtoMethod(t, "update", HashUpdate); env->SetProtoMethod(t, "digest", HashDigest); @@ -5061,7 +5066,8 @@ Sign::Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { void Sign::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + SignBase::kInternalFieldCount); env->SetProtoMethod(t, "init", SignInit); env->SetProtoMethod(t, "update", SignUpdate); @@ -5385,7 +5391,8 @@ Verify::Verify(Environment* env, v8::Local wrap) : void Verify::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + SignBase::kInternalFieldCount); env->SetProtoMethod(t, "init", VerifyInit); env->SetProtoMethod(t, "update", VerifyUpdate); @@ -5697,7 +5704,8 @@ void DiffieHellman::Initialize(Environment* env, Local target) { const PropertyAttribute attributes = static_cast(ReadOnly | DontDelete); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + DiffieHellman::kInternalFieldCount); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); @@ -6036,7 +6044,7 @@ void ECDH::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); diff --git a/src/node_dir.cc b/src/node_dir.cc index ec53d8216bc..9923f042779 100644 --- a/src/node_dir.cc +++ b/src/node_dir.cc @@ -358,7 +358,7 @@ void Initialize(Local target, env->SetProtoMethod(dir, "read", DirHandle::Read); env->SetProtoMethod(dir, "close", DirHandle::Close); Local dirt = dir->InstanceTemplate(); - dirt->SetInternalFieldCount(DirHandle::kDirHandleFieldCount); + dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount); Local handleString = FIXED_ONE_BYTE_STRING(isolate, "DirHandle"); dir->SetClassName(handleString); diff --git a/src/node_dir.h b/src/node_dir.h index b55245d5b89..5fcc36326b7 100644 --- a/src/node_dir.h +++ b/src/node_dir.h @@ -12,8 +12,6 @@ namespace fs_dir { // Needed to propagate `uv_dir_t`. class DirHandle : public AsyncWrap { public: - static constexpr int kDirHandleFieldCount = 1; - static DirHandle* New(Environment* env, uv_dir_t* dir); ~DirHandle() override; diff --git a/src/node_file.cc b/src/node_file.cc index 4d4960fc583..121fd35f573 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -2290,7 +2290,8 @@ void Initialize(Local target, // Create FunctionTemplate for FSReqCallback Local fst = env->NewFunctionTemplate(NewFSReqCallback); - fst->InstanceTemplate()->SetInternalFieldCount(1); + fst->InstanceTemplate()->SetInternalFieldCount( + FSReqBase::kInternalFieldCount); fst->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local wrapString = FIXED_ONE_BYTE_STRING(isolate, "FSReqCallback"); @@ -2303,7 +2304,8 @@ void Initialize(Local target, // Create FunctionTemplate for FileHandleReadWrap. There’s no need // to do anything in the constructor, so we only store the instance template. Local fh_rw = FunctionTemplate::New(isolate); - fh_rw->InstanceTemplate()->SetInternalFieldCount(1); + fh_rw->InstanceTemplate()->SetInternalFieldCount( + FSReqBase::kInternalFieldCount); fh_rw->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local fhWrapString = FIXED_ONE_BYTE_STRING(isolate, "FileHandleReqWrap"); @@ -2318,7 +2320,7 @@ void Initialize(Local target, FIXED_ONE_BYTE_STRING(isolate, "FSReqPromise"); fpt->SetClassName(promiseString); Local fpo = fpt->InstanceTemplate(); - fpo->SetInternalFieldCount(1); + fpo->SetInternalFieldCount(FSReqBase::kInternalFieldCount); env->set_fsreqpromise_constructor_template(fpo); // Create FunctionTemplate for FileHandle @@ -2327,7 +2329,7 @@ void Initialize(Local target, env->SetProtoMethod(fd, "close", FileHandle::Close); env->SetProtoMethod(fd, "releaseFD", FileHandle::ReleaseFD); Local fdt = fd->InstanceTemplate(); - fdt->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + fdt->SetInternalFieldCount(StreamBase::kInternalFieldCount); Local handleString = FIXED_ONE_BYTE_STRING(isolate, "FileHandle"); fd->SetClassName(handleString); @@ -2344,7 +2346,7 @@ void Initialize(Local target, "FileHandleCloseReq")); fdclose->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local fdcloset = fdclose->InstanceTemplate(); - fdcloset->SetInternalFieldCount(1); + fdcloset->SetInternalFieldCount(FSReqBase::kInternalFieldCount); env->set_fdclose_constructor_template(fdcloset); Local use_promises_symbol = diff --git a/src/node_http2.cc b/src/node_http2.cc index c21adcfeb56..075d1b505e5 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -3025,14 +3025,14 @@ void Initialize(Local target, ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping")); ping->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local pingt = ping->InstanceTemplate(); - pingt->SetInternalFieldCount(1); + pingt->SetInternalFieldCount(Http2Session::Http2Ping::kInternalFieldCount); env->set_http2ping_constructor_template(pingt); Local setting = FunctionTemplate::New(env->isolate()); setting->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Setting")); setting->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local settingt = setting->InstanceTemplate(); - settingt->SetInternalFieldCount(1); + settingt->SetInternalFieldCount(AsyncWrap::kInternalFieldCount); env->set_http2settings_constructor_template(settingt); Local stream = FunctionTemplate::New(env->isolate()); @@ -3049,7 +3049,7 @@ void Initialize(Local target, stream->Inherit(AsyncWrap::GetConstructorTemplate(env)); StreamBase::AddMethods(env, stream); Local streamt = stream->InstanceTemplate(); - streamt->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount); env->set_http2stream_constructor_template(streamt); target->Set(context, FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"), @@ -3058,7 +3058,8 @@ void Initialize(Local target, Local session = env->NewFunctionTemplate(Http2Session::New); session->SetClassName(http2SessionClassName); - session->InstanceTemplate()->SetInternalFieldCount(1); + session->InstanceTemplate()->SetInternalFieldCount( + Http2Session::kInternalFieldCount); session->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(session, "origin", Http2Session::Origin); env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc); diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 40ece82b625..75d7e89a91c 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -873,7 +873,7 @@ void InitializeHttpParser(Local target, void* priv) { Environment* env = Environment::GetCurrent(context); Local t = env->NewFunctionTemplate(Parser::New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount(Parser::kInternalFieldCount); t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser")); t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "REQUEST"), diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 46c6ef39f86..a32ddf2066b 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -172,7 +172,7 @@ class ConverterObject : public BaseObject, Converter { HandleScope scope(env->isolate()); Local t = ObjectTemplate::New(env->isolate()); - t->SetInternalFieldCount(1); + t->SetInternalFieldCount(ConverterObject::kInternalFieldCount); Local obj; if (!t->NewInstance(env->context()).ToLocal(&obj)) return; diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 248e0f041de..c52a0698744 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -935,7 +935,8 @@ Local GetMessagePortConstructorTemplate(Environment* env) { { Local m = env->NewFunctionTemplate(MessagePort::New); m->SetClassName(env->message_port_constructor_string()); - m->InstanceTemplate()->SetInternalFieldCount(1); + m->InstanceTemplate()->SetInternalFieldCount( + MessagePort::kInternalFieldCount); m->Inherit(HandleWrap::GetConstructorTemplate(env)); env->SetProtoMethod(m, "postMessage", MessagePort::PostMessage); diff --git a/src/node_perf.cc b/src/node_perf.cc index 68b015f33ed..21766d3b89a 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -640,7 +640,8 @@ void Initialize(Local target, Local eldh = env->NewFunctionTemplate(ELDHistogramNew); eldh->SetClassName(eldh_classname); - eldh->InstanceTemplate()->SetInternalFieldCount(1); + eldh->InstanceTemplate()->SetInternalFieldCount( + ELDHistogram::kInternalFieldCount); env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds); env->SetProtoMethod(eldh, "min", ELDHistogramMin); env->SetProtoMethod(eldh, "max", ELDHistogramMax); diff --git a/src/node_serdes.cc b/src/node_serdes.cc index a2d185c4167..bcdcd19b261 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -451,7 +451,8 @@ void Initialize(Local target, Local ser = env->NewFunctionTemplate(SerializerContext::New); - ser->InstanceTemplate()->SetInternalFieldCount(1); + ser->InstanceTemplate()->SetInternalFieldCount( + SerializerContext::kInternalFieldCount); env->SetProtoMethod(ser, "writeHeader", SerializerContext::WriteHeader); env->SetProtoMethod(ser, "writeValue", SerializerContext::WriteValue); @@ -477,7 +478,8 @@ void Initialize(Local target, Local des = env->NewFunctionTemplate(DeserializerContext::New); - des->InstanceTemplate()->SetInternalFieldCount(1); + des->InstanceTemplate()->SetInternalFieldCount( + DeserializerContext::kInternalFieldCount); env->SetProtoMethod(des, "readHeader", DeserializerContext::ReadHeader); env->SetProtoMethod(des, "readValue", DeserializerContext::ReadValue); diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc index 0d67eceed54..05c540bbff1 100644 --- a/src/node_stat_watcher.cc +++ b/src/node_stat_watcher.cc @@ -47,7 +47,8 @@ void StatWatcher::Initialize(Environment* env, Local target) { HandleScope scope(env->isolate()); Local t = env->NewFunctionTemplate(StatWatcher::New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount( + StatWatcher::kInternalFieldCount); Local statWatcherString = FIXED_ONE_BYTE_STRING(env->isolate(), "StatWatcher"); t->SetClassName(statWatcherString); diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index f5852076b4e..9adee9e458c 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -129,7 +129,8 @@ void NodeCategorySet::Initialize(Local target, Local category_set = env->NewFunctionTemplate(NodeCategorySet::New); - category_set->InstanceTemplate()->SetInternalFieldCount(1); + category_set->InstanceTemplate()->SetInternalFieldCount( + NodeCategorySet::kInternalFieldCount); env->SetProtoMethod(category_set, "enable", NodeCategorySet::Enable); env->SetProtoMethod(category_set, "disable", NodeCategorySet::Disable); diff --git a/src/node_util.cc b/src/node_util.cc index e0ef7d421ff..db9b8ec8d65 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -323,7 +323,8 @@ void Initialize(Local target, FIXED_ONE_BYTE_STRING(env->isolate(), "WeakReference"); Local weak_ref = env->NewFunctionTemplate(WeakReference::New); - weak_ref->InstanceTemplate()->SetInternalFieldCount(1); + weak_ref->InstanceTemplate()->SetInternalFieldCount( + WeakReference::kInternalFieldCount); weak_ref->SetClassName(weak_ref_string); env->SetProtoMethod(weak_ref, "get", WeakReference::Get); env->SetProtoMethod(weak_ref, "incRef", WeakReference::IncRef); diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 20872c58d60..ed8f6c4fa4c 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -1810,7 +1810,7 @@ static void Initialize(Local target, Local tmpl = env->NewFunctionTemplate(WASI::New); auto wasi_wrap_string = FIXED_ONE_BYTE_STRING(env->isolate(), "WASI"); - tmpl->InstanceTemplate()->SetInternalFieldCount(1); + tmpl->InstanceTemplate()->SetInternalFieldCount(WASI::kInternalFieldCount); tmpl->SetClassName(wasi_wrap_string); env->SetProtoMethod(tmpl, "args_get", WASI::ArgsGet); diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index 4cc75c31604..29f0bc18c0d 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -121,7 +121,8 @@ SignalPropagation SigintWatchdog::HandleSigint() { void TraceSigintWatchdog::Init(Environment* env, Local target) { Local constructor = env->NewFunctionTemplate(New); - constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->InstanceTemplate()->SetInternalFieldCount( + TraceSigintWatchdog::kInternalFieldCount); Local js_sigint_watch_dog = FIXED_ONE_BYTE_STRING(env->isolate(), "TraceSigintWatchdog"); constructor->SetClassName(js_sigint_watch_dog); diff --git a/src/node_worker.cc b/src/node_worker.cc index d6e0ebb36f4..1496b937d0c 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -776,7 +776,8 @@ void InitWorker(Local target, { Local w = env->NewFunctionTemplate(Worker::New); - w->InstanceTemplate()->SetInternalFieldCount(1); + w->InstanceTemplate()->SetInternalFieldCount( + Worker::kInternalFieldCount); w->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(w, "startThread", Worker::StartThread); @@ -797,7 +798,8 @@ void InitWorker(Local target, { Local wst = FunctionTemplate::New(env->isolate()); - wst->InstanceTemplate()->SetInternalFieldCount(1); + wst->InstanceTemplate()->SetInternalFieldCount( + WorkerHeapSnapshotTaker::kInternalFieldCount); wst->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local wst_string = diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 1b7fd788b95..eacd710143a 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -1216,7 +1216,8 @@ struct MakeClass { static void Make(Environment* env, Local target, const char* name) { Local z = env->NewFunctionTemplate(Stream::New); - z->InstanceTemplate()->SetInternalFieldCount(1); + z->InstanceTemplate()->SetInternalFieldCount( + Stream::kInternalFieldCount); z->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(z, "write", Stream::template Write); diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index c2be4320387..c4a5b7cd62e 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -74,7 +74,7 @@ void PipeWrap::Initialize(Local target, Local pipeString = FIXED_ONE_BYTE_STRING(env->isolate(), "Pipe"); t->SetClassName(pipeString); t->InstanceTemplate() - ->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + ->SetInternalFieldCount(StreamBase::kInternalFieldCount); t->Inherit(LibuvStreamWrap::GetConstructorTemplate(env)); diff --git a/src/process_wrap.cc b/src/process_wrap.cc index a75f271d1c7..1e7de56c6d1 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -52,7 +52,8 @@ class ProcessWrap : public HandleWrap { void* priv) { Environment* env = Environment::GetCurrent(context); Local constructor = env->NewFunctionTemplate(New); - constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->InstanceTemplate()->SetInternalFieldCount( + ProcessWrap::kInternalFieldCount); Local processString = FIXED_ONE_BYTE_STRING(env->isolate(), "Process"); constructor->SetClassName(processString); diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index bc2d9f1e355..2be7ac98341 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -53,7 +53,8 @@ class SignalWrap : public HandleWrap { void* priv) { Environment* env = Environment::GetCurrent(context); Local constructor = env->NewFunctionTemplate(New); - constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->InstanceTemplate()->SetInternalFieldCount( + SignalWrap::kInternalFieldCount); Local signalString = FIXED_ONE_BYTE_STRING(env->isolate(), "Signal"); constructor->SetClassName(signalString); diff --git a/src/stream_base-inl.h b/src/stream_base-inl.h index f89eb3a5287..27a9a01c7c2 100644 --- a/src/stream_base-inl.h +++ b/src/stream_base-inl.h @@ -23,18 +23,22 @@ using v8::String; using v8::Value; inline void StreamReq::AttachToObject(v8::Local req_wrap_obj) { - CHECK_EQ(req_wrap_obj->GetAlignedPointerFromInternalField(kStreamReqField), + CHECK_EQ(req_wrap_obj->GetAlignedPointerFromInternalField( + StreamReq::kStreamReqField), nullptr); - req_wrap_obj->SetAlignedPointerInInternalField(kStreamReqField, this); + req_wrap_obj->SetAlignedPointerInInternalField( + StreamReq::kStreamReqField, this); } inline StreamReq* StreamReq::FromObject(v8::Local req_wrap_obj) { return static_cast( - req_wrap_obj->GetAlignedPointerFromInternalField(kStreamReqField)); + req_wrap_obj->GetAlignedPointerFromInternalField( + StreamReq::kStreamReqField)); } inline void StreamReq::Dispose() { - object()->SetAlignedPointerInInternalField(kStreamReqField, nullptr); + object()->SetAlignedPointerInInternalField( + StreamReq::kStreamReqField, nullptr); delete this; } @@ -261,15 +265,17 @@ inline WriteWrap* StreamBase::CreateWriteWrap( } inline void StreamBase::AttachToObject(v8::Local obj) { - obj->SetAlignedPointerInInternalField(kStreamBaseField, this); + obj->SetAlignedPointerInInternalField( + StreamBase::kStreamBaseField, this); } inline StreamBase* StreamBase::FromObject(v8::Local obj) { - if (obj->GetAlignedPointerFromInternalField(0) == nullptr) + if (obj->GetAlignedPointerFromInternalField(StreamBase::kSlot) == nullptr) return nullptr; return static_cast( - obj->GetAlignedPointerFromInternalField(kStreamBaseField)); + obj->GetAlignedPointerFromInternalField( + StreamBase::kStreamBaseField)); } @@ -304,7 +310,7 @@ inline void StreamReq::Done(int status, const char* error_str) { inline void StreamReq::ResetObject(v8::Local obj) { DCHECK_GT(obj->InternalFieldCount(), StreamReq::kStreamReqField); - obj->SetAlignedPointerInInternalField(0, nullptr); // BaseObject field. + obj->SetAlignedPointerInInternalField(StreamReq::kSlot, nullptr); obj->SetAlignedPointerInInternalField(StreamReq::kStreamReqField, nullptr); } diff --git a/src/stream_base.cc b/src/stream_base.cc index eaccfc995c7..28a3bf65fc0 100644 --- a/src/stream_base.cc +++ b/src/stream_base.cc @@ -340,7 +340,8 @@ MaybeLocal StreamBase::CallJSOnreadMethod(ssize_t nread, AsyncWrap* wrap = GetAsyncWrap(); CHECK_NOT_NULL(wrap); - Local onread = wrap->object()->GetInternalField(kOnReadFunctionField); + Local onread = wrap->object()->GetInternalField( + StreamBase::kOnReadFunctionField); CHECK(onread->IsFunction()); return wrap->MakeCallback(onread.As(), arraysize(argv), argv); } @@ -409,8 +410,11 @@ void StreamBase::AddMethods(Environment* env, Local t) { True(env->isolate())); t->PrototypeTemplate()->SetAccessor( FIXED_ONE_BYTE_STRING(env->isolate(), "onread"), - BaseObject::InternalFieldGet, - BaseObject::InternalFieldSet); + BaseObject::InternalFieldGet< + StreamBase::kOnReadFunctionField>, + BaseObject::InternalFieldSet< + StreamBase::kOnReadFunctionField, + &Value::IsFunction>); } void StreamBase::GetFD(const FunctionCallbackInfo& args) { diff --git a/src/stream_base.h b/src/stream_base.h index 3df9e99f6e4..15b83ec91f6 100644 --- a/src/stream_base.h +++ b/src/stream_base.h @@ -29,7 +29,14 @@ using JSMethodFunction = void(const v8::FunctionCallbackInfo& args); class StreamReq { public: - static constexpr int kStreamReqField = 1; + // The kSlot internal field here mirrors BaseObject::InternalFields::kSlot + // here because instances derived from StreamReq will also derive from + // BaseObject, and the slots are used for the identical purpose. + enum InternalFields { + kSlot = BaseObject::kSlot, + kStreamReqField = BaseObject::kInternalFieldCount, + kInternalFieldCount + }; explicit StreamReq(StreamBase* stream, v8::Local req_wrap_obj) : stream_(stream) { @@ -275,10 +282,15 @@ class StreamResource { class StreamBase : public StreamResource { public: - // 0 is reserved for the BaseObject pointer. - static constexpr int kStreamBaseField = 1; - static constexpr int kOnReadFunctionField = 2; - static constexpr int kStreamBaseFieldCount = 3; + // The kSlot field here mirrors that of BaseObject::InternalFields::kSlot + // because instances deriving from StreamBase generally also derived from + // BaseObject (it's possible for it not to, however). + enum InternalFields { + kSlot = BaseObject::kSlot, + kStreamBaseField = BaseObject::kInternalFieldCount, + kOnReadFunctionField, + kInternalFieldCount + }; static void AddMethods(Environment* env, v8::Local target); diff --git a/src/stream_pipe.cc b/src/stream_pipe.cc index 5f7514b1b84..40b094ab593 100644 --- a/src/stream_pipe.cc +++ b/src/stream_pipe.cc @@ -298,7 +298,8 @@ void InitializeStreamPipe(Local target, env->SetProtoMethod(pipe, "pendingWrites", StreamPipe::PendingWrites); pipe->Inherit(AsyncWrap::GetConstructorTemplate(env)); pipe->SetClassName(stream_pipe_string); - pipe->InstanceTemplate()->SetInternalFieldCount(1); + pipe->InstanceTemplate()->SetInternalFieldCount( + StreamPipe::kInternalFieldCount); target ->Set(context, stream_pipe_string, pipe->GetFunction(context).ToLocalChecked()) diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 21b775401e4..7548516e477 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -64,8 +64,7 @@ void LibuvStreamWrap::Initialize(Local target, }; Local sw = FunctionTemplate::New(env->isolate(), is_construct_call_callback); - sw->InstanceTemplate()->SetInternalFieldCount( - StreamReq::kStreamReqField + 1 + 3); + sw->InstanceTemplate()->SetInternalFieldCount(StreamReq::kInternalFieldCount); Local wrapString = FIXED_ONE_BYTE_STRING(env->isolate(), "ShutdownWrap"); sw->SetClassName(wrapString); @@ -94,7 +93,8 @@ void LibuvStreamWrap::Initialize(Local target, Local ww = FunctionTemplate::New(env->isolate(), is_construct_call_callback); - ww->InstanceTemplate()->SetInternalFieldCount(StreamReq::kStreamReqField + 1); + ww->InstanceTemplate()->SetInternalFieldCount( + StreamReq::kInternalFieldCount); Local writeWrapString = FIXED_ONE_BYTE_STRING(env->isolate(), "WriteWrap"); ww->SetClassName(writeWrapString); @@ -136,7 +136,7 @@ Local LibuvStreamWrap::GetConstructorTemplate( FIXED_ONE_BYTE_STRING(env->isolate(), "LibuvStreamWrap")); tmpl->Inherit(HandleWrap::GetConstructorTemplate(env)); tmpl->InstanceTemplate()->SetInternalFieldCount( - StreamBase::kStreamBaseFieldCount); + StreamBase::kInternalFieldCount); Local get_write_queue_size = FunctionTemplate::New(env->isolate(), GetWriteQueueSize, diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 89c4e215bbe..1aca3a5e6ae 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -77,8 +77,7 @@ void TCPWrap::Initialize(Local target, Local t = env->NewFunctionTemplate(New); Local tcpString = FIXED_ONE_BYTE_STRING(env->isolate(), "TCP"); t->SetClassName(tcpString); - t->InstanceTemplate() - ->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + t->InstanceTemplate()->SetInternalFieldCount(StreamBase::kInternalFieldCount); // Init properties t->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "reading"), diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 2f8da61f647..39dcf532a9f 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -1269,8 +1269,7 @@ void TLSWrap::Initialize(Local target, Local tlsWrapString = FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"); t->SetClassName(tlsWrapString); - t->InstanceTemplate() - ->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + t->InstanceTemplate()->SetInternalFieldCount(StreamBase::kInternalFieldCount); Local get_write_queue_size = FunctionTemplate::New(env->isolate(), diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index 7dface926e4..8536fae3ed7 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -51,8 +51,7 @@ void TTYWrap::Initialize(Local target, Local t = env->NewFunctionTemplate(New); t->SetClassName(ttyString); - t->InstanceTemplate() - ->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); + t->InstanceTemplate()->SetInternalFieldCount(StreamBase::kInternalFieldCount); t->Inherit(LibuvStreamWrap::GetConstructorTemplate(env)); env->SetProtoMethodNoSideEffect(t, "getWindowSize", TTYWrap::GetWindowSize); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 4a66ce0a1f1..fa5cf8da479 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -91,7 +91,7 @@ void UDPWrap::Initialize(Local target, Environment* env = Environment::GetCurrent(context); Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(1); + t->InstanceTemplate()->SetInternalFieldCount(UDPWrap::kInternalFieldCount); Local udpString = FIXED_ONE_BYTE_STRING(env->isolate(), "UDP"); t->SetClassName(udpString); From eb2fe5ff90d68520b148fdc86362c07561c1c635 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 27 Feb 2020 13:14:38 -0800 Subject: [PATCH 104/192] perf,src: add HistogramBase and internal/histogram.js Separating this out from the QUIC PR to allow it to be separately reviewed. The QUIC implementation makes use of the hdr_histogram for dynamic performance monitoring. This introduces a BaseObject class that allows the internal histograms to be accessed on the JavaScript side and adds a generic Histogram class that will be used by both QUIC and perf_hooks (for the event loop delay monitoring). Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/31988 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- lib/internal/histogram.js | 94 +++++++++++++++++++++++++ lib/perf_hooks.js | 49 ++----------- node.gyp | 2 + src/env.h | 1 + src/histogram-inl.h | 70 ++++++++++++------- src/histogram.cc | 141 ++++++++++++++++++++++++++++++++++++++ src/histogram.h | 70 +++++++++++++++++-- src/node_perf.h | 2 +- 8 files changed, 355 insertions(+), 74 deletions(-) create mode 100644 lib/internal/histogram.js create mode 100644 src/histogram.cc diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js new file mode 100644 index 00000000000..6deb8314a41 --- /dev/null +++ b/lib/internal/histogram.js @@ -0,0 +1,94 @@ +'use strict'; + +const { + customInspectSymbol: kInspect, +} = require('internal/util'); + +const { format } = require('util'); +const { Map, Symbol } = primordials; + +const { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} = require('internal/errors').codes; + +const kDestroy = Symbol('kDestroy'); +const kHandle = Symbol('kHandle'); + +// Histograms are created internally by Node.js and used to +// record various metrics. This Histogram class provides a +// generally read-only view of the internal histogram. +class Histogram { + #handle = undefined; + #map = new Map(); + + constructor(internal) { + this.#handle = internal; + } + + [kInspect]() { + const obj = { + min: this.min, + max: this.max, + mean: this.mean, + exceeds: this.exceeds, + stddev: this.stddev, + percentiles: this.percentiles, + }; + return `Histogram ${format(obj)}`; + } + + get min() { + return this.#handle ? this.#handle.min() : undefined; + } + + get max() { + return this.#handle ? this.#handle.max() : undefined; + } + + get mean() { + return this.#handle ? this.#handle.mean() : undefined; + } + + get exceeds() { + return this.#handle ? this.#handle.exceeds() : undefined; + } + + get stddev() { + return this.#handle ? this.#handle.stddev() : undefined; + } + + percentile(percentile) { + if (typeof percentile !== 'number') + throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile); + + if (percentile <= 0 || percentile > 100) + throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile); + + return this.#handle ? this.#handle.percentile(percentile) : undefined; + } + + get percentiles() { + this.#map.clear(); + if (this.#handle) + this.#handle.percentiles(this.#map); + return this.#map; + } + + reset() { + if (this.#handle) + this.#handle.reset(); + } + + [kDestroy]() { + this.#handle = undefined; + } + + get [kHandle]() { return this.#handle; } +} + +module.exports = { + Histogram, + kDestroy, + kHandle, +}; diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js index 267b4577ffe..a1417057282 100644 --- a/lib/perf_hooks.js +++ b/lib/perf_hooks.js @@ -3,7 +3,6 @@ const { ArrayIsArray, Boolean, - Map, NumberIsSafeInteger, ObjectDefineProperties, ObjectDefineProperty, @@ -52,16 +51,18 @@ const kInspect = require('internal/util').customInspectSymbol; const { ERR_INVALID_CALLBACK, - ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_TYPE, ERR_INVALID_OPT_VALUE, ERR_VALID_PERFORMANCE_ENTRY_TYPE, ERR_INVALID_PERFORMANCE_MARK } = require('internal/errors').codes; +const { + Histogram, + kHandle, +} = require('internal/histogram'); + const { setImmediate } = require('timers'); -const kHandle = Symbol('handle'); -const kMap = Symbol('map'); const kCallback = Symbol('callback'); const kTypes = Symbol('types'); const kEntries = Symbol('entries'); @@ -557,47 +558,9 @@ function sortedInsert(list, entry) { list.splice(location, 0, entry); } -class ELDHistogram { - constructor(handle) { - this[kHandle] = handle; - this[kMap] = new Map(); - } - - reset() { this[kHandle].reset(); } +class ELDHistogram extends Histogram { enable() { return this[kHandle].enable(); } disable() { return this[kHandle].disable(); } - - get exceeds() { return this[kHandle].exceeds(); } - get min() { return this[kHandle].min(); } - get max() { return this[kHandle].max(); } - get mean() { return this[kHandle].mean(); } - get stddev() { return this[kHandle].stddev(); } - percentile(percentile) { - if (typeof percentile !== 'number') { - throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile); - } - if (percentile <= 0 || percentile > 100) { - throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', - percentile); - } - return this[kHandle].percentile(percentile); - } - get percentiles() { - this[kMap].clear(); - this[kHandle].percentiles(this[kMap]); - return this[kMap]; - } - - [kInspect]() { - return { - min: this.min, - max: this.max, - mean: this.mean, - stddev: this.stddev, - percentiles: this.percentiles, - exceeds: this.exceeds - }; - } } function monitorEventLoopDelay(options = {}) { diff --git a/node.gyp b/node.gyp index 9c632d38385..b941eff5657 100644 --- a/node.gyp +++ b/node.gyp @@ -141,6 +141,7 @@ 'lib/internal/fs/watchers.js', 'lib/internal/http.js', 'lib/internal/heap_utils.js', + 'lib/internal/histogram.js', 'lib/internal/idna.js', 'lib/internal/inspector_async_hook.js', 'lib/internal/js_stream_socket.js', @@ -534,6 +535,7 @@ 'src/fs_event_wrap.cc', 'src/handle_wrap.cc', 'src/heap_utils.cc', + 'src/histogram.cc', 'src/js_native_api.h', 'src/js_native_api_types.h', 'src/js_native_api_v8.cc', diff --git a/src/env.h b/src/env.h index 3b577e40307..f02c8e9775f 100644 --- a/src/env.h +++ b/src/env.h @@ -406,6 +406,7 @@ constexpr size_t kFsStatsBufferLength = V(filehandlereadwrap_template, v8::ObjectTemplate) \ V(fsreqpromise_constructor_template, v8::ObjectTemplate) \ V(handle_wrap_ctor_template, v8::FunctionTemplate) \ + V(histogram_instance_template, v8::ObjectTemplate) \ V(http2settings_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2ping_constructor_template, v8::ObjectTemplate) \ diff --git a/src/histogram-inl.h b/src/histogram-inl.h index 3135041f738..58911dae8f2 100644 --- a/src/histogram-inl.h +++ b/src/histogram-inl.h @@ -4,58 +4,78 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "histogram.h" +#include "base_object-inl.h" #include "node_internals.h" namespace node { -inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { - CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_)); +void Histogram::Reset() { + hdr_reset(histogram_.get()); } -inline Histogram::~Histogram() { - hdr_close(histogram_); +bool Histogram::Record(int64_t value) { + return hdr_record_value(histogram_.get(), value); } -inline void Histogram::Reset() { - hdr_reset(histogram_); +int64_t Histogram::Min() { + return hdr_min(histogram_.get()); } -inline bool Histogram::Record(int64_t value) { - return hdr_record_value(histogram_, value); +int64_t Histogram::Max() { + return hdr_max(histogram_.get()); } -inline int64_t Histogram::Min() { - return hdr_min(histogram_); +double Histogram::Mean() { + return hdr_mean(histogram_.get()); } -inline int64_t Histogram::Max() { - return hdr_max(histogram_); +double Histogram::Stddev() { + return hdr_stddev(histogram_.get()); } -inline double Histogram::Mean() { - return hdr_mean(histogram_); -} - -inline double Histogram::Stddev() { - return hdr_stddev(histogram_); -} - -inline double Histogram::Percentile(double percentile) { +double Histogram::Percentile(double percentile) { CHECK_GT(percentile, 0); CHECK_LE(percentile, 100); - return hdr_value_at_percentile(histogram_, percentile); + return static_cast( + hdr_value_at_percentile(histogram_.get(), percentile)); } -inline void Histogram::Percentiles(std::function fn) { +template +void Histogram::Percentiles(Iterator&& fn) { hdr_iter iter; - hdr_iter_percentile_init(&iter, histogram_, 1); + hdr_iter_percentile_init(&iter, histogram_.get(), 1); while (hdr_iter_next(&iter)) { double key = iter.specifics.percentiles.percentile; - double value = iter.value; + double value = static_cast(iter.value); fn(key, value); } } +bool HistogramBase::RecordDelta() { + uint64_t time = uv_hrtime(); + bool ret = true; + if (prev_ > 0) { + int64_t delta = time - prev_; + if (delta > 0) { + ret = Record(delta); + TraceDelta(delta); + if (!ret) { + if (exceeds_ < 0xFFFFFFFF) + exceeds_++; + TraceExceeds(delta); + } + } + } + prev_ = time; + return ret; +} + +void HistogramBase::ResetState() { + Reset(); + exceeds_ = 0; + prev_ = 0; +} + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/histogram.cc b/src/histogram.cc new file mode 100644 index 00000000000..8d1eb77b1bc --- /dev/null +++ b/src/histogram.cc @@ -0,0 +1,141 @@ +#include "histogram.h" // NOLINT(build/include_inline) +#include "histogram-inl.h" +#include "memory_tracker-inl.h" + +namespace node { + +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Local; +using v8::Map; +using v8::Number; +using v8::ObjectTemplate; +using v8::String; +using v8::Value; + +Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { + hdr_histogram* histogram; + CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram)); + histogram_.reset(histogram); +} + +HistogramBase::HistogramBase( + Environment* env, + v8::Local wrap, + int64_t lowest, + int64_t highest, + int figures) + : BaseObject(env, wrap), + Histogram(lowest, highest, figures) { + MakeWeak(); +} + +void HistogramBase::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("histogram", GetMemorySize()); +} + +void HistogramBase::GetMin(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Min()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetMax(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Max()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetMean(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Mean()); +} + +void HistogramBase::GetExceeds(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Exceeds()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetStddev(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Stddev()); +} + +void HistogramBase::GetPercentile( + const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As()->Value(); + args.GetReturnValue().Set(histogram->Percentile(percentile)); +} + +void HistogramBase::GetPercentiles( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local map = args[0].As(); + histogram->Percentiles([map, env](double key, double value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), value)).IsEmpty(); + }); +} + +void HistogramBase::DoReset(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->ResetState(); +} + +BaseObjectPtr HistogramBase::New( + Environment* env, + int64_t lowest, + int64_t highest, + int figures) { + CHECK_LE(lowest, highest); + CHECK_GT(figures, 0); + v8::Local obj; + auto tmpl = env->histogram_instance_template(); + if (!tmpl->NewInstance(env->context()).ToLocal(&obj)) + return {}; + + return MakeDetachedBaseObject( + env, obj, lowest, highest, figures); +} + +void HistogramBase::Initialize(Environment* env) { + // Guard against multiple initializations + if (!env->histogram_instance_template().IsEmpty()) + return; + + Local histogram = FunctionTemplate::New(env->isolate()); + Local classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + histogram->SetClassName(classname); + + Local histogramt = + histogram->InstanceTemplate(); + + histogramt->SetInternalFieldCount(1); + env->SetProtoMethod(histogram, "exceeds", HistogramBase::GetExceeds); + env->SetProtoMethod(histogram, "min", HistogramBase::GetMin); + env->SetProtoMethod(histogram, "max", HistogramBase::GetMax); + env->SetProtoMethod(histogram, "mean", HistogramBase::GetMean); + env->SetProtoMethod(histogram, "stddev", HistogramBase::GetStddev); + env->SetProtoMethod(histogram, "percentile", HistogramBase::GetPercentile); + env->SetProtoMethod(histogram, "percentiles", HistogramBase::GetPercentiles); + env->SetProtoMethod(histogram, "reset", HistogramBase::DoReset); + + env->set_histogram_instance_template(histogramt); +} + +} // namespace node diff --git a/src/histogram.h b/src/histogram.h index eb94af5da2a..e92c31c4724 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -4,15 +4,24 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "hdr_histogram.h" +#include "base_object.h" +#include "util.h" + #include +#include #include namespace node { +constexpr int kDefaultHistogramFigures = 3; + class Histogram { public: - inline Histogram(int64_t lowest, int64_t highest, int figures = 3); - inline virtual ~Histogram(); + Histogram( + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + virtual ~Histogram() = default; inline bool Record(int64_t value); inline void Reset(); @@ -21,14 +30,65 @@ class Histogram { inline double Mean(); inline double Stddev(); inline double Percentile(double percentile); - inline void Percentiles(std::function fn); + + // Iterator is a function type that takes two doubles as argument, one for + // percentile and one for the value at that percentile. + template + inline void Percentiles(Iterator&& fn); size_t GetMemorySize() const { - return hdr_get_memory_size(histogram_); + return hdr_get_memory_size(histogram_.get()); } private: - hdr_histogram* histogram_; + using HistogramPointer = DeleteFnPtr; + HistogramPointer histogram_; +}; + +class HistogramBase : public BaseObject, public Histogram { + public: + virtual ~HistogramBase() = default; + + virtual void TraceDelta(int64_t delta) {} + virtual void TraceExceeds(int64_t delta) {} + + inline bool RecordDelta(); + inline void ResetState(); + + int64_t Exceeds() const { return exceeds_; } + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(HistogramBase) + SET_SELF_SIZE(HistogramBase) + + static void GetMin(const v8::FunctionCallbackInfo& args); + static void GetMax(const v8::FunctionCallbackInfo& args); + static void GetMean(const v8::FunctionCallbackInfo& args); + static void GetExceeds(const v8::FunctionCallbackInfo& args); + static void GetStddev(const v8::FunctionCallbackInfo& args); + static void GetPercentile( + const v8::FunctionCallbackInfo& args); + static void GetPercentiles( + const v8::FunctionCallbackInfo& args); + static void DoReset(const v8::FunctionCallbackInfo& args); + static void Initialize(Environment* env); + + static BaseObjectPtr New( + Environment* env, + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + HistogramBase( + Environment* env, + v8::Local wrap, + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + private: + int64_t exceeds_ = 0; + uint64_t prev_ = 0; }; } // namespace node diff --git a/src/node_perf.h b/src/node_perf.h index 4f5ca93f223..ac65533a772 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -161,7 +161,7 @@ class ELDHistogram : public HandleWrap, public Histogram { exceeds_ = 0; prev_ = 0; } - int64_t Exceeds() { return exceeds_; } + int64_t Exceeds() const { return exceeds_; } void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackFieldWithSize("histogram", GetMemorySize()); From a0c3c4de3ba0d6286b6670d42dd598652237d2e0 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Fri, 28 Feb 2020 12:14:18 +0100 Subject: [PATCH 105/192] deps: openssl: cherry-pick 4dcb150ea30f OpenSSL 1.1.1d does not ship with getrandom syscall being predefined on all architectures. So when NodeJS is run with glibc prior to 2.25, where getentropy is unavailable, and the getrandom syscall is unknown, it will fail. PPC64LE or s390 are affected by lack of this definition. Original commit message. commit 4dcb150ea30f9bbfa7946e6b39c30a86aca5ed02 Author: Kurt Roeckx Date: Sat Sep 28 14:59:32 2019 +0200 Add defines for __NR_getrandom for all Linux architectures Fixes: https://github.com/openssl/openssl/issues/10015 Reviewed-by: Bernd Edlinger GH: https://github.com/openssl/openssl/pull/10044 Fixes: https://github.com/nodejs/node/issues/31671 PR-URL: https://github.com/nodejs/node/pull/32002 Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell Reviewed-By: Sam Roberts --- deps/openssl/openssl/crypto/rand/rand_unix.c | 52 ++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/deps/openssl/openssl/crypto/rand/rand_unix.c b/deps/openssl/openssl/crypto/rand/rand_unix.c index 69efcdeed75..315af610f84 100644 --- a/deps/openssl/openssl/crypto/rand/rand_unix.c +++ b/deps/openssl/openssl/crypto/rand/rand_unix.c @@ -282,12 +282,58 @@ static ssize_t sysctl_random(char *buf, size_t buflen) # if defined(OPENSSL_RAND_SEED_GETRANDOM) # if defined(__linux) && !defined(__NR_getrandom) -# if defined(__arm__) && defined(__NR_SYSCALL_BASE) +# if defined(__arm__) # define __NR_getrandom (__NR_SYSCALL_BASE+384) # elif defined(__i386__) # define __NR_getrandom 355 -# elif defined(__x86_64__) && !defined(__ILP32__) -# define __NR_getrandom 318 +# elif defined(__x86_64__) +# if defined(__ILP32__) +# define __NR_getrandom (__X32_SYSCALL_BIT + 318) +# else +# define __NR_getrandom 318 +# endif +# elif defined(__xtensa__) +# define __NR_getrandom 338 +# elif defined(__s390__) || defined(__s390x__) +# define __NR_getrandom 349 +# elif defined(__bfin__) +# define __NR_getrandom 389 +# elif defined(__powerpc__) +# define __NR_getrandom 359 +# elif defined(__mips__) || defined(__mips64) +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_getrandom (__NR_Linux + 353) +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_getrandom (__NR_Linux + 313) +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_getrandom (__NR_Linux + 317) +# endif +# elif defined(__hppa__) +# define __NR_getrandom (__NR_Linux + 339) +# elif defined(__sparc__) +# define __NR_getrandom 347 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__alpha__) +# define __NR_getrandom 511 +# elif defined(__sh__) +# if defined(__SH5__) +# define __NR_getrandom 373 +# else +# define __NR_getrandom 384 +# endif +# elif defined(__avr32__) +# define __NR_getrandom 317 +# elif defined(__microblaze__) +# define __NR_getrandom 385 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__cris__) +# define __NR_getrandom 356 +# elif defined(__aarch64__) +# define __NR_getrandom 278 +# else /* generic */ +# define __NR_getrandom 278 # endif # endif From 9ac42f1be42f162ec9cf6bb56e817d7b210a6db9 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 09:48:44 -0800 Subject: [PATCH 106/192] test: remove common.port from test-tls-securepair-client OpenSSL s_server accepts port 0 as an indicator to use an open port provided by the operating system. Use that instead of common.PORT in the test. Remove 500ms delay added in 8e461673c44cb550a7aadc20f0af6453810f1b18. Hopefully the race condition in OpenSSL s_server has been fixed and/or the change to port 0 means that the server is listening by the time the ACCEPT text is printed and the setTimeout() is no longer necessary. PR-URL: https://github.com/nodejs/node/pull/32024 Reviewed-By: Ben Coe Reviewed-By: Sam Roberts --- test/sequential/test-tls-securepair-client.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/sequential/test-tls-securepair-client.js b/test/sequential/test-tls-securepair-client.js index c450410baf9..d5e2b7b7d2b 100644 --- a/test/sequential/test-tls-securepair-client.js +++ b/test/sequential/test-tls-securepair-client.js @@ -62,7 +62,7 @@ function test(keyPath, certPath, check, next) { const cert = fixtures.readSync(certPath).toString(); const server = spawn(common.opensslCli, ['s_server', - '-accept', common.PORT, + '-accept', 0, '-cert', fixtures.path(certPath), '-key', fixtures.path(keyPath)]); server.stdout.pipe(process.stdout); @@ -78,10 +78,11 @@ function test(keyPath, certPath, check, next) { console.log(state); switch (state) { case 'WAIT-ACCEPT': - if (/ACCEPT/.test(serverStdoutBuffer)) { - // Give s_server half a second to start up. - setTimeout(startClient, 500); + const matches = serverStdoutBuffer.match(/ACCEPT .*?:(\d+)/); + if (matches) { + const port = matches[1]; state = 'WAIT-HELLO'; + startClient(port); } break; @@ -117,7 +118,7 @@ function test(keyPath, certPath, check, next) { }); - function startClient() { + function startClient(port) { const s = new net.Stream(); const sslcontext = tls.createSecureContext({ key, cert }); @@ -131,7 +132,7 @@ function test(keyPath, certPath, check, next) { pair.encrypted.pipe(s); s.pipe(pair.encrypted); - s.connect(common.PORT); + s.connect(port); s.on('connect', function() { console.log('client connected'); From 5e1f059db4f8b324ef4448e0fbdc5dc7fc6c57d8 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 10:07:41 -0800 Subject: [PATCH 107/192] test: move test-inspector-module to parallel test-inspector-module is very fast and seems to be runnable at the same time as other tests. Move from sequential directory to parallel. PR-URL: https://github.com/nodejs/node/pull/32025 Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater Reviewed-By: Luigi Pinca --- test/{sequential => parallel}/test-inspector-module.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{sequential => parallel}/test-inspector-module.js (100%) diff --git a/test/sequential/test-inspector-module.js b/test/parallel/test-inspector-module.js similarity index 100% rename from test/sequential/test-inspector-module.js rename to test/parallel/test-inspector-module.js From ed8007af0bc8fd4fa575217b50e8ca611a7e8679 Mon Sep 17 00:00:00 2001 From: Gerhard Stoebich <18708370+Flarna@users.noreply.github.com> Date: Tue, 18 Feb 2020 15:33:31 +0100 Subject: [PATCH 108/192] events: convert errorMonitor to a normal property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert property errorMonitor to a normal property as non-writable caused unwanted side effects. Refs: https://github.com/nodejs/node/pull/30932#discussion_r379679982 PR-URL: https://github.com/nodejs/node/pull/31848 Reviewed-By: Michaël Zasso Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Matheus Marchini Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig --- lib/events.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/events.js b/lib/events.js index 76b376e7891..4a8311af059 100644 --- a/lib/events.js +++ b/lib/events.js @@ -90,12 +90,7 @@ ObjectDefineProperty(EventEmitter, 'captureRejections', { enumerable: true }); -ObjectDefineProperty(EventEmitter, 'errorMonitor', { - value: kErrorMonitor, - writable: false, - configurable: true, - enumerable: true -}); +EventEmitter.errorMonitor = kErrorMonitor; // The default for captureRejections is false ObjectDefineProperty(EventEmitter.prototype, kCapture, { From 96e70c4ce786f43de16a0267a92ee73abf218356 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Wed, 26 Feb 2020 09:38:48 +0300 Subject: [PATCH 109/192] test: add GC test for disabled AsyncLocalStorage PR-URL: https://github.com/nodejs/node/pull/31995 Reviewed-By: Anna Henningsen Reviewed-By: Gireesh Punathil Reviewed-By: Stephen Belanger Reviewed-By: Vladimir de Turckheim Reviewed-By: James M Snell Reviewed-By: Michael Dawson --- .../test-async-local-storage-gcable.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/async-hooks/test-async-local-storage-gcable.js diff --git a/test/async-hooks/test-async-local-storage-gcable.js b/test/async-hooks/test-async-local-storage-gcable.js new file mode 100644 index 00000000000..37b04b38d14 --- /dev/null +++ b/test/async-hooks/test-async-local-storage-gcable.js @@ -0,0 +1,20 @@ +'use strict'; +// Flags: --expose_gc + +// This test ensures that AsyncLocalStorage gets gced once it was disabled +// and no strong references remain in userland. + +const common = require('../common'); +const { AsyncLocalStorage } = require('async_hooks'); +const onGC = require('../common/ongc'); + +let asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.runSyncAndReturn({}, () => { + asyncLocalStorage.disable(); + + onGC(asyncLocalStorage, { ongc: common.mustCall() }); +}); + +asyncLocalStorage = null; +global.gc(); From dd8183632d4bc8929371ee4676ef9bb5c95f7aaa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 28 Feb 2020 17:13:08 -0800 Subject: [PATCH 110/192] src: add node_crypto_common and refactor Two things in one on this commit: (a) For the QUIC implementation, we need to separate out various bits from node_crypto.cc to allow them to be reused. That's where this commit starts. (b) Quite a bit of the node_crypto.cc code was just messy in terms of it's organization and lack of error handling and use of Local vs. MaybeLocal. This cleans that up a bit and hopefully makes certain parts a bit more manageable also. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/32016 Reviewed-By: Anna Henningsen Reviewed-By: Sam Roberts --- node.gyp | 2 + src/env.h | 2 + src/node_crypto.cc | 637 +-------------------- src/node_crypto.h | 13 + src/node_crypto_common.cc | 1110 +++++++++++++++++++++++++++++++++++++ src/node_crypto_common.h | 139 +++++ src/string_bytes.cc | 12 +- src/string_bytes.h | 9 + 8 files changed, 1313 insertions(+), 611 deletions(-) create mode 100644 src/node_crypto_common.cc create mode 100644 src/node_crypto_common.h diff --git a/node.gyp b/node.gyp index b941eff5657..60aff022842 100644 --- a/node.gyp +++ b/node.gyp @@ -823,9 +823,11 @@ [ 'node_use_openssl=="true"', { 'sources': [ 'src/node_crypto.cc', + 'src/node_crypto_common.cc', 'src/node_crypto_bio.cc', 'src/node_crypto_clienthello.cc', 'src/node_crypto.h', + 'src/node_crypto_common.h', 'src/node_crypto_bio.h', 'src/node_crypto_clienthello.h', 'src/node_crypto_clienthello-inl.h', diff --git a/src/env.h b/src/env.h index f02c8e9775f..fc25f7a5b64 100644 --- a/src/env.h +++ b/src/env.h @@ -206,6 +206,7 @@ constexpr size_t kFsStatsBufferLength = V(dest_string, "dest") \ V(destroyed_string, "destroyed") \ V(detached_string, "detached") \ + V(dh_string, "DH") \ V(dns_a_string, "A") \ V(dns_aaaa_string, "AAAA") \ V(dns_cname_string, "CNAME") \ @@ -219,6 +220,7 @@ constexpr size_t kFsStatsBufferLength = V(done_string, "done") \ V(dot_string, ".") \ V(duration_string, "duration") \ + V(ecdh_string, "ECDH") \ V(emit_warning_string, "emitWarning") \ V(empty_object_string, "{}") \ V(encoding_string, "encoding") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0b48821cf6c..fdaf91acdc0 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -22,6 +22,7 @@ #include "node_crypto.h" #include "node_buffer.h" #include "node_crypto_bio.h" +#include "node_crypto_common.h" #include "node_crypto_clienthello-inl.h" #include "node_crypto_groups.h" #include "node_errors.h" @@ -59,11 +60,6 @@ #include #include -static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL - | ASN1_STRFLGS_UTF8_CONVERT - | XN_FLAG_SEP_MULTILINE - | XN_FLAG_FN_SN; - namespace node { namespace crypto { @@ -75,7 +71,6 @@ using v8::Boolean; using v8::ConstructorBehavior; using v8::Context; using v8::DontDelete; -using v8::EscapableHandleScope; using v8::Exception; using v8::External; using v8::False; @@ -110,24 +105,6 @@ using v8::Value; # define IS_OCB_MODE(mode) ((mode) == EVP_CIPH_OCB_MODE) #endif -struct StackOfX509Deleter { - void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } -}; -using StackOfX509 = std::unique_ptr; - -struct StackOfXASN1Deleter { - void operator()(STACK_OF(ASN1_OBJECT)* p) const { - sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); - } -}; -using StackOfASN1 = std::unique_ptr; - -// OPENSSL_free is a macro, so we need a wrapper function. -struct OpenSSLBufferDeleter { - void operator()(char* pointer) const { OPENSSL_free(pointer); } -}; -using OpenSSLBuffer = std::unique_ptr; - static const char* const root_certs[] = { #include "node_root_certs.h" // NOLINT(build/include_order) }; @@ -386,7 +363,7 @@ void ThrowCryptoError(Environment* env, unsigned long err, // NOLINT(runtime/int) // Default, only used if there is no SSL `err` which can // be used to create a long-style message string. - const char* message = nullptr) { + const char* message) { char message_buffer[128] = {0}; if (err != 0 || message == nullptr) { ERR_error_string_n(err, message_buffer, sizeof(message_buffer)); @@ -453,15 +430,6 @@ bool EntropySource(unsigned char* buffer, size_t length) { return RAND_bytes(buffer, length) != -1; } - -template -static T* MallocOpenSSL(size_t count) { - void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); - CHECK_IMPLIES(mem == nullptr, count == 0); - return static_cast(mem); -} - - void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( @@ -831,16 +799,6 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo& args) { } #endif // !OPENSSL_NO_ENGINE -int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - DeleteFnPtr store_ctx( - X509_STORE_CTX_new()); - return store_ctx.get() != nullptr && - X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && - X509_STORE_CTX_get1_issuer(issuer, store_ctx.get(), cert) == 1; -} - - int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, X509Pointer&& x, STACK_OF(X509)* extra_certs, @@ -1893,381 +1851,6 @@ void SSLWrap::OnClientHello(void* arg, w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); } - -static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { - const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); - - if (method != X509V3_EXT_get_nid(NID_subject_alt_name)) - return false; - - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); - if (names == nullptr) - return false; - - for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { - GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); - - if (i != 0) - BIO_write(out, ", ", 2); - - if (gen->type == GEN_DNS) { - ASN1_IA5STRING* name = gen->d.dNSName; - - BIO_write(out, "DNS:", 4); - BIO_write(out, name->data, name->length); - } else { - STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( - const_cast(method), gen, nullptr); - if (nval == nullptr) - return false; - X509V3_EXT_val_prn(out, nval, 0, 0); - sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); - } - } - sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); - - return true; -} - - -static void AddFingerprintDigest(const unsigned char* md, - unsigned int md_size, - char (*fingerprint)[3 * EVP_MAX_MD_SIZE + 1]) { - unsigned int i; - const char hex[] = "0123456789ABCDEF"; - - for (i = 0; i < md_size; i++) { - (*fingerprint)[3*i] = hex[(md[i] & 0xf0) >> 4]; - (*fingerprint)[(3*i)+1] = hex[(md[i] & 0x0f)]; - (*fingerprint)[(3*i)+2] = ':'; - } - - if (md_size > 0) { - (*fingerprint)[(3*(md_size-1))+2] = '\0'; - } else { - (*fingerprint)[0] = '\0'; - } -} - - -static MaybeLocal ECPointToBuffer(Environment* env, - const EC_GROUP* group, - const EC_POINT* point, - point_conversion_form_t form, - const char** error) { - size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); - if (len == 0) { - if (error != nullptr) *error = "Failed to get public key length"; - return MaybeLocal(); - } - AllocatedBuffer buf = env->AllocateManaged(len); - len = EC_POINT_point2oct(group, - point, - form, - reinterpret_cast(buf.data()), - buf.size(), - nullptr); - if (len == 0) { - if (error != nullptr) *error = "Failed to get public key"; - return MaybeLocal(); - } - return buf.ToBuffer(); -} - - -static Local X509ToObject(Environment* env, X509* cert) { - EscapableHandleScope scope(env->isolate()); - Local context = env->context(); - Local info = Object::New(env->isolate()); - - BIOPointer bio(BIO_new(BIO_s_mem())); - BUF_MEM* mem; - if (X509_NAME_print_ex(bio.get(), - X509_get_subject_name(cert), - 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->subject_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - } - USE(BIO_reset(bio.get())); - - X509_NAME* issuer_name = X509_get_issuer_name(cert); - if (X509_NAME_print_ex(bio.get(), issuer_name, 0, X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->issuer_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - } - USE(BIO_reset(bio.get())); - - int nids[] = { NID_subject_alt_name, NID_info_access }; - Local keys[] = { env->subjectaltname_string(), - env->infoaccess_string() }; - CHECK_EQ(arraysize(nids), arraysize(keys)); - for (size_t i = 0; i < arraysize(nids); i++) { - int index = X509_get_ext_by_NID(cert, nids[i], -1); - if (index < 0) - continue; - - X509_EXTENSION* ext = X509_get_ext(cert, index); - CHECK_NOT_NULL(ext); - - if (!SafeX509ExtPrint(bio.get(), ext) && - X509V3_EXT_print(bio.get(), ext, 0, 0) != 1) { - info->Set(context, keys[i], Null(env->isolate())).Check(); - USE(BIO_reset(bio.get())); - continue; - } - - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, keys[i], - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - - USE(BIO_reset(bio.get())); - } - - EVPKeyPointer pkey(X509_get_pubkey(cert)); - RSAPointer rsa; - ECPointer ec; - if (pkey) { - switch (EVP_PKEY_id(pkey.get())) { - case EVP_PKEY_RSA: - rsa.reset(EVP_PKEY_get1_RSA(pkey.get())); - break; - case EVP_PKEY_EC: - ec.reset(EVP_PKEY_get1_EC_KEY(pkey.get())); - break; - } - } - - if (rsa) { - const BIGNUM* n; - const BIGNUM* e; - RSA_get0_key(rsa.get(), &n, &e, nullptr); - BN_print(bio.get(), n); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->modulus_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - int bits = BN_num_bits(n); - info->Set(context, env->bits_string(), - Integer::New(env->isolate(), bits)).Check(); - - uint64_t exponent_word = static_cast(BN_get_word(e)); - uint32_t lo = static_cast(exponent_word); - uint32_t hi = static_cast(exponent_word >> 32); - if (hi == 0) { - BIO_printf(bio.get(), "0x%x", lo); - } else { - BIO_printf(bio.get(), "0x%x%08x", hi, lo); - } - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->exponent_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - int size = i2d_RSA_PUBKEY(rsa.get(), nullptr); - CHECK_GE(size, 0); - Local pubbuff = Buffer::New(env, size).ToLocalChecked(); - unsigned char* pubserialized = - reinterpret_cast(Buffer::Data(pubbuff)); - i2d_RSA_PUBKEY(rsa.get(), &pubserialized); - info->Set(env->context(), env->pubkey_string(), pubbuff).Check(); - } else if (ec) { - const EC_GROUP* group = EC_KEY_get0_group(ec.get()); - if (group != nullptr) { - int bits = EC_GROUP_order_bits(group); - if (bits > 0) { - info->Set(context, env->bits_string(), - Integer::New(env->isolate(), bits)).Check(); - } - } - - const EC_POINT* pubkey = EC_KEY_get0_public_key(ec.get()); - Local buf; - if (pubkey != nullptr && - ECPointToBuffer( - env, group, pubkey, EC_KEY_get_conv_form(ec.get()), nullptr) - .ToLocal(&buf)) { - info->Set(context, env->pubkey_string(), buf).Check(); - } - - const int nid = EC_GROUP_get_curve_name(group); - if (nid != 0) { - // Curve is well-known, get its OID and NIST nick-name (if it has one). - - if (const char* sn = OBJ_nid2sn(nid)) { - info->Set(context, env->asn1curve_string(), - OneByteString(env->isolate(), sn)).Check(); - } - - if (const char* nist = EC_curve_nid2nist(nid)) { - info->Set(context, env->nistcurve_string(), - OneByteString(env->isolate(), nist)).Check(); - } - } else { - // Unnamed curves can be described by their mathematical properties, - // but aren't used much (at all?) with X.509/TLS. Support later if needed. - } - } - - pkey.reset(); - rsa.reset(); - ec.reset(); - - ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert)); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->valid_from_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert)); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->valid_to_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - bio.reset(); - - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int md_size; - char fingerprint[EVP_MAX_MD_SIZE * 3 + 1]; - if (X509_digest(cert, EVP_sha1(), md, &md_size)) { - AddFingerprintDigest(md, md_size, &fingerprint); - info->Set(context, env->fingerprint_string(), - OneByteString(env->isolate(), fingerprint)).Check(); - } - if (X509_digest(cert, EVP_sha256(), md, &md_size)) { - AddFingerprintDigest(md, md_size, &fingerprint); - info->Set(context, env->fingerprint256_string(), - OneByteString(env->isolate(), fingerprint)).Check(); - } - - StackOfASN1 eku(static_cast( - X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr))); - if (eku) { - const int count = sk_ASN1_OBJECT_num(eku.get()); - MaybeStackBuffer, 16> ext_key_usage(count); - char buf[256]; - - int j = 0; - for (int i = 0; i < count; i++) { - if (OBJ_obj2txt(buf, - sizeof(buf), - sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { - ext_key_usage[j++] = OneByteString(env->isolate(), buf); - } - } - - eku.reset(); - info->Set(context, env->ext_key_usage_string(), - Array::New(env->isolate(), ext_key_usage.out(), count)).Check(); - } - - if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { - BignumPointer bn(ASN1_INTEGER_to_BN(serial_number, nullptr)); - if (bn) { - OpenSSLBuffer buf(BN_bn2hex(bn.get())); - if (buf) { - info->Set(context, env->serial_number_string(), - OneByteString(env->isolate(), buf.get())).Check(); - } - } - } - - // Raw DER certificate - int size = i2d_X509(cert, nullptr); - Local buff = Buffer::New(env, size).ToLocalChecked(); - unsigned char* serialized = reinterpret_cast( - Buffer::Data(buff)); - i2d_X509(cert, &serialized); - info->Set(context, env->raw_string(), buff).Check(); - - return scope.Escape(info); -} - - -static Local AddIssuerChainToObject(X509Pointer* cert, - Local object, - StackOfX509&& peer_certs, - Environment* const env) { - Local context = env->isolate()->GetCurrentContext(); - cert->reset(sk_X509_delete(peer_certs.get(), 0)); - for (;;) { - int i; - for (i = 0; i < sk_X509_num(peer_certs.get()); i++) { - X509* ca = sk_X509_value(peer_certs.get(), i); - if (X509_check_issued(ca, cert->get()) != X509_V_OK) - continue; - - Local ca_info = X509ToObject(env, ca); - object->Set(context, env->issuercert_string(), ca_info).Check(); - object = ca_info; - - // NOTE: Intentionally freeing cert that is not used anymore. - // Delete cert and continue aggregating issuers. - cert->reset(sk_X509_delete(peer_certs.get(), i)); - break; - } - - // Issuer not found, break out of the loop. - if (i == sk_X509_num(peer_certs.get())) - break; - } - return object; -} - - -static StackOfX509 CloneSSLCerts(X509Pointer&& cert, - const STACK_OF(X509)* const ssl_certs) { - StackOfX509 peer_certs(sk_X509_new(nullptr)); - if (cert) - sk_X509_push(peer_certs.get(), cert.release()); - for (int i = 0; i < sk_X509_num(ssl_certs); i++) { - X509Pointer cert(X509_dup(sk_X509_value(ssl_certs, i))); - if (!cert || !sk_X509_push(peer_certs.get(), cert.get())) - return StackOfX509(); - // `cert` is now managed by the stack. - cert.release(); - } - return peer_certs; -} - - -static Local GetLastIssuedCert(X509Pointer* cert, - const SSLPointer& ssl, - Local issuer_chain, - Environment* const env) { - Local context = env->isolate()->GetCurrentContext(); - while (X509_check_issued(cert->get(), cert->get()) != X509_V_OK) { - X509* ca; - if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl.get()), cert->get(), &ca) <= 0) - break; - - Local ca_info = X509ToObject(env, ca); - issuer_chain->Set(context, env->issuercert_string(), ca_info).Check(); - issuer_chain = ca_info; - - // Delete previous cert and continue aggregating issuers. - cert->reset(ca); - } - return issuer_chain; -} - - template void SSLWrap::GetPeerCertificate( const FunctionCallbackInfo& args) { @@ -2275,44 +1858,11 @@ void SSLWrap::GetPeerCertificate( ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - ClearErrorOnReturn clear_error_on_return; + bool abbreviated = args.Length() < 1 || !args[0]->IsTrue(); - Local result; - // Used to build the issuer certificate chain. - Local issuer_chain; - - // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` - // contains the `peer_certificate`, but on server it doesn't. - X509Pointer cert( - w->is_server() ? SSL_get_peer_certificate(w->ssl_.get()) : nullptr); - STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_.get()); - if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) - goto done; - - // Short result requested. - if (args.Length() < 1 || !args[0]->IsTrue()) { - result = X509ToObject(env, cert ? cert.get() : sk_X509_value(ssl_certs, 0)); - goto done; - } - - if (auto peer_certs = CloneSSLCerts(std::move(cert), ssl_certs)) { - // First and main certificate. - X509Pointer cert(sk_X509_value(peer_certs.get(), 0)); - CHECK(cert); - result = X509ToObject(env, cert.release()); - - issuer_chain = - AddIssuerChainToObject(&cert, result, std::move(peer_certs), env); - issuer_chain = GetLastIssuedCert(&cert, w->ssl_, issuer_chain, env); - // Last certificate should be self-signed. - if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK) - issuer_chain->Set(env->context(), - env->issuercert_string(), - issuer_chain).Check(); - } - - done: - args.GetReturnValue().Set(result); + Local ret; + if (GetPeerCert(env, w->ssl_, abbreviated, w->is_server()).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2323,16 +1873,9 @@ void SSLWrap::GetCertificate( ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - ClearErrorOnReturn clear_error_on_return; - - Local result; - - X509* cert = SSL_get_certificate(w->ssl_.get()); - - if (cert != nullptr) - result = X509ToObject(env, cert); - - args.GetReturnValue().Set(result); + Local ret; + if (GetCert(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2411,22 +1954,16 @@ void SSLWrap::SetSession(const FunctionCallbackInfo& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - if (args.Length() < 1) { + if (args.Length() < 1) return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory"); - } THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session"); - ArrayBufferViewContents sbuf(args[0].As()); - - const unsigned char* p = sbuf.data(); - SSLSessionPointer sess(d2i_SSL_SESSION(nullptr, &p, sbuf.length())); + SSLSessionPointer sess = GetTLSSession(args[0]); if (sess == nullptr) return; - int r = SSL_set_session(w->ssl_.get(), sess.get()); - - if (!r) + if (!SetTLSSession(w->ssl_, sess)) return env->ThrowError("SSL_set_session error"); } @@ -2542,7 +2079,6 @@ void SSLWrap::GetEphemeralKeyInfo( Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = Environment::GetCurrent(args); - Local context = env->context(); CHECK(w->ssl_); @@ -2550,51 +2086,12 @@ void SSLWrap::GetEphemeralKeyInfo( if (w->is_server()) return args.GetReturnValue().SetNull(); - Local info = Object::New(env->isolate()); + Local ret; + if (GetEphemeralKey(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); - EVP_PKEY* raw_key; - if (SSL_get_server_tmp_key(w->ssl_.get(), &raw_key)) { - EVPKeyPointer key(raw_key); - int kid = EVP_PKEY_id(key.get()); - switch (kid) { - case EVP_PKEY_DH: - info->Set(context, env->type_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "DH")).Check(); - info->Set(context, env->size_string(), - Integer::New(env->isolate(), EVP_PKEY_bits(key.get()))) - .Check(); - break; - case EVP_PKEY_EC: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - { - const char* curve_name; - if (kid == EVP_PKEY_EC) { - EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key.get()); - int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); - curve_name = OBJ_nid2sn(nid); - EC_KEY_free(ec); - } else { - curve_name = OBJ_nid2sn(kid); - } - info->Set(context, env->type_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH")).Check(); - info->Set(context, env->name_string(), - OneByteString(args.GetIsolate(), - curve_name)).Check(); - info->Set(context, env->size_string(), - Integer::New(env->isolate(), - EVP_PKEY_bits(key.get()))).Check(); - } - break; - default: - break; - } - } // TODO(@sam-github) semver-major: else return ThrowCryptoError(env, // ERR_get_error()) - - return args.GetReturnValue().Set(info); } @@ -2624,58 +2121,14 @@ void SSLWrap::VerifyError(const FunctionCallbackInfo& args) { // peer certificate is questionable but it's compatible with what was // here before. long x509_verify_error = // NOLINT(runtime/int) - X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; - if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_.get())) { - X509_free(peer_cert); - x509_verify_error = SSL_get_verify_result(w->ssl_.get()); - } else { - const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(w->ssl_.get()); - const SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); - // Allow no-cert for PSK authentication in TLS1.2 and lower. - // In TLS1.3 check that session was reused because TLS1.3 PSK - // looks like session resumption. Is there a better way? - if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || - (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && - SSL_session_reused(w->ssl_.get()))) - return args.GetReturnValue().SetNull(); - } + VerifyPeerCertificate(w->ssl_, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT); if (x509_verify_error == X509_V_OK) return args.GetReturnValue().SetNull(); const char* reason = X509_verify_cert_error_string(x509_verify_error); const char* code = reason; -#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; - switch (x509_verify_error) { - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) - CASE_X509_ERR(UNABLE_TO_GET_CRL) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) - CASE_X509_ERR(CERT_SIGNATURE_FAILURE) - CASE_X509_ERR(CRL_SIGNATURE_FAILURE) - CASE_X509_ERR(CERT_NOT_YET_VALID) - CASE_X509_ERR(CERT_HAS_EXPIRED) - CASE_X509_ERR(CRL_NOT_YET_VALID) - CASE_X509_ERR(CRL_HAS_EXPIRED) - CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) - CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) - CASE_X509_ERR(OUT_OF_MEM) - CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) - CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) - CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) - CASE_X509_ERR(CERT_CHAIN_TOO_LONG) - CASE_X509_ERR(CERT_REVOKED) - CASE_X509_ERR(INVALID_CA) - CASE_X509_ERR(PATH_LENGTH_EXCEEDED) - CASE_X509_ERR(INVALID_PURPOSE) - CASE_X509_ERR(CERT_UNTRUSTED) - CASE_X509_ERR(CERT_REJECTED) - } -#undef CASE_X509_ERR + code = X509ErrorCode(x509_verify_error); Isolate* isolate = args.GetIsolate(); Local reason_string = OneByteString(isolate, reason); @@ -2693,23 +2146,14 @@ void SSLWrap::GetCipher(const FunctionCallbackInfo& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - Local context = env->context(); const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_.get()); if (c == nullptr) return; - Local info = Object::New(env->isolate()); - const char* cipher_name = SSL_CIPHER_get_name(c); - info->Set(context, env->name_string(), - OneByteString(args.GetIsolate(), cipher_name)).Check(); - const char* cipher_standard_name = SSL_CIPHER_standard_name(c); - info->Set(context, env->standard_name_string(), - OneByteString(args.GetIsolate(), cipher_standard_name)).Check(); - const char* cipher_version = SSL_CIPHER_get_version(c); - info->Set(context, env->version_string(), - OneByteString(args.GetIsolate(), cipher_version)).Check(); - args.GetReturnValue().Set(info); + Local ret; + if (GetCipherInfo(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2904,10 +2348,7 @@ void SSLWrap::SetALPNProtocols(const FunctionCallbackInfo& args) { return env->ThrowTypeError("Must give a Buffer as first argument"); if (w->is_client()) { - ArrayBufferViewContents alpn_protos(args[0]); - int r = SSL_set_alpn_protos( - w->ssl_.get(), alpn_protos.data(), alpn_protos.length()); - CHECK_EQ(r, 0); + CHECK(SetALPN(w->ssl_, args[0])); } else { CHECK( w->object()->SetPrivate( @@ -2930,18 +2371,10 @@ int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg) { if (w->is_client()) { // Incoming response - const unsigned char* resp; - int len = SSL_get_tlsext_status_ocsp_resp(s, &resp); Local arg; - if (resp == nullptr) { - arg = Null(env->isolate()); - } else { - arg = - Buffer::Copy(env, reinterpret_cast(resp), len) - .ToLocalChecked(); - } - - w->MakeCallback(env->onocspresponse_string(), 1, &arg); + MaybeLocal ret = GetSSLOCSPResponse(env, s, Null(env->isolate())); + if (ret.ToLocal(&arg)) + w->MakeCallback(env->onocspresponse_string(), 1, &arg); // No async acceptance is possible, so always return 1 to accept the // response. The listener for 'OCSPResponse' event has no control over @@ -3000,7 +2433,7 @@ int SSLWrap::SSLCertCallback(SSL* s, void* arg) { Local info = Object::New(env->isolate()); - const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + const char* servername = GetServerName(s); if (servername == nullptr) { info->Set(context, env->servername_string(), @@ -3049,23 +2482,7 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { // Store the SNI context for later use. w->sni_context_ = BaseObjectPtr(sc); - int rv; - - // NOTE: reference count is not increased by this API methods - X509* x509 = SSL_CTX_get0_certificate(sc->ctx_.get()); - EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_.get()); - STACK_OF(X509)* chain; - - rv = SSL_CTX_get0_chain_certs(sc->ctx_.get(), &chain); - if (rv) - rv = SSL_use_certificate(w->ssl_.get(), x509); - if (rv) - rv = SSL_use_PrivateKey(w->ssl_.get(), pkey); - if (rv && chain != nullptr) - rv = SSL_set1_chain(w->ssl_.get(), chain); - if (rv) - rv = w->SetCACerts(sc); - if (!rv) { + if (UseSNIContext(w->ssl_, sc) && !w->SetCACerts(sc)) { // Not clear why sometimes we throw error, and sometimes we call // onerror(). Both cause .destroy(), but onerror does a bit more. unsigned long err = ERR_get_error(); // NOLINT(runtime/int) @@ -4255,7 +3672,7 @@ void CipherBase::InitIv(const FunctionCallbackInfo& args) { reinterpret_cast(key.get()), key.size(), iv_buf.data(), - iv_len, + static_cast(iv_len), auth_tag_len); } diff --git a/src/node_crypto.h b/src/node_crypto.h index 655605290b0..772a34a7da7 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -90,6 +90,8 @@ class SecureContext final : public BaseObject { static void Initialize(Environment* env, v8::Local target); + SSL_CTX* operator*() const { return ctx_.get(); } + // TODO(joyeecheung): track the memory used by OpenSSL types SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(SecureContext) @@ -779,6 +781,17 @@ void SetEngine(const v8::FunctionCallbackInfo& args); #endif // !OPENSSL_NO_ENGINE void InitCrypto(v8::Local target); +void ThrowCryptoError(Environment* env, + unsigned long err, // NOLINT(runtime/int) + const char* message = nullptr); + +template +inline T* MallocOpenSSL(size_t count) { + void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); + CHECK_IMPLIES(mem == nullptr, count == 0); + return static_cast(mem); +} + } // namespace crypto } // namespace node diff --git a/src/node_crypto_common.cc b/src/node_crypto_common.cc new file mode 100644 index 00000000000..197bc5cd591 --- /dev/null +++ b/src/node_crypto_common.cc @@ -0,0 +1,1110 @@ +#include "env-inl.h" +#include "node_buffer.h" +#include "node_crypto.h" +#include "node_crypto_common.h" +#include "node.h" +#include "node_internals.h" +#include "node_url.h" +#include "string_bytes.h" +#include "v8.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace node { + +using v8::Array; +using v8::ArrayBufferView; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Integer; +using v8::Local; +using v8::MaybeLocal; +using v8::NewStringType; +using v8::Null; +using v8::Object; +using v8::String; +using v8::Value; + +namespace crypto { + +static constexpr int X509_NAME_FLAGS = + ASN1_STRFLGS_ESC_CTRL | + ASN1_STRFLGS_UTF8_CONVERT | + XN_FLAG_SEP_MULTILINE | + XN_FLAG_FN_SN; + +int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + DeleteFnPtr store_ctx( + X509_STORE_CTX_new()); + return store_ctx.get() != nullptr && + X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && + X509_STORE_CTX_get1_issuer(issuer, store_ctx.get(), cert) == 1; +} + +void LogSecret( + const SSLPointer& ssl, + const char* name, + const unsigned char* secret, + size_t secretlen) { + auto keylog_cb = SSL_CTX_get_keylog_callback(SSL_get_SSL_CTX(ssl.get())); + unsigned char crandom[32]; + + if (keylog_cb == nullptr || + SSL_get_client_random(ssl.get(), crandom, 32) != 32) { + return; + } + + std::string line = name; + line += " " + StringBytes::hex_encode( + reinterpret_cast(crandom), 32); + line += " " + StringBytes::hex_encode( + reinterpret_cast(secret), secretlen); + keylog_cb(ssl.get(), line.c_str()); +} + +bool SetALPN(const SSLPointer& ssl, const std::string& alpn) { + return SSL_set_alpn_protos( + ssl.get(), + reinterpret_cast(alpn.c_str()), + alpn.length()) == 0; +} + +bool SetALPN(const SSLPointer& ssl, Local alpn) { + if (!alpn->IsArrayBufferView()) + return false; + ArrayBufferViewContents protos(alpn.As()); + return SSL_set_alpn_protos(ssl.get(), protos.data(), protos.length()) == 0; +} + +MaybeLocal GetSSLOCSPResponse( + Environment* env, + SSL* ssl, + Local default_value) { + const unsigned char* resp; + int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp); + if (resp == nullptr) + return default_value; + + Local ret; + MaybeLocal maybe_buffer = + Buffer::Copy(env, reinterpret_cast(resp), len); + + if (!maybe_buffer.ToLocal(&ret)) + return MaybeLocal(); + + return ret; +} + +bool SetTLSSession( + const SSLPointer& ssl, + const unsigned char* buf, + size_t length) { + SSLSessionPointer s(d2i_SSL_SESSION(nullptr, &buf, length)); + return s == nullptr ? false : SetTLSSession(ssl, s); +} + +bool SetTLSSession( + const SSLPointer& ssl, + const SSLSessionPointer& session) { + return session != nullptr && SSL_set_session(ssl.get(), session.get()) == 1; +} + +SSLSessionPointer GetTLSSession(Local val) { + if (!val->IsArrayBufferView()) + return SSLSessionPointer(); + ArrayBufferViewContents sbuf(val.As()); + return GetTLSSession(sbuf.data(), sbuf.length()); +} + +SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) { + return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length)); +} + +std::unordered_multimap +GetCertificateAltNames(X509* cert) { + std::unordered_multimap map; + BIOPointer bio(BIO_new(BIO_s_mem())); + BUF_MEM* mem; + int idx = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); + if (idx < 0) // There is no subject alt name + return map; + + X509_EXTENSION* ext = X509_get_ext(cert, idx); + CHECK_NOT_NULL(ext); + const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); + CHECK_EQ(method, X509V3_EXT_get_nid(NID_subject_alt_name)); + + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + if (names == nullptr) // There are no names + return map; + + for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { + USE(BIO_reset(bio.get())); + GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); + if (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + BIO_write(bio.get(), name->data, name->length); + BIO_get_mem_ptr(bio.get(), &mem); + map.emplace("dns", std::string(mem->data, mem->length)); + } else { + STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( + const_cast(method), gen, nullptr); + if (nval == nullptr) + continue; + X509V3_EXT_val_prn(bio.get(), nval, 0, 0); + sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); + BIO_get_mem_ptr(bio.get(), &mem); + std::string value(mem->data, mem->length); + if (value.compare(0, 11, "IP Address:") == 0) { + map.emplace("ip", value.substr(11)); + } else if (value.compare(0, 4, "URI:") == 0) { + url::URL url(value.substr(4)); + if (url.flags() & url::URL_FLAGS_CANNOT_BE_BASE || + url.flags() & url::URL_FLAGS_FAILED) { + continue; // Skip this one + } + map.emplace("uri", url.host()); + } + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + return map; +} + +std::string GetCertificateCN(X509* cert) { + X509_NAME* subject = X509_get_subject_name(cert); + if (subject != nullptr) { + int nid = OBJ_txt2nid("CN"); + int idx = X509_NAME_get_index_by_NID(subject, nid, -1); + if (idx != -1) { + X509_NAME_ENTRY* cn = X509_NAME_get_entry(subject, idx); + if (cn != nullptr) { + ASN1_STRING* cn_str = X509_NAME_ENTRY_get_data(cn); + if (cn_str != nullptr) { + return std::string(reinterpret_cast( + ASN1_STRING_get0_data(cn_str))); + } + } + } + } + return std::string(); +} + +long VerifyPeerCertificate( // NOLINT(runtime/int) + const SSLPointer& ssl, + long def) { // NOLINT(runtime/int) + long err = def; // NOLINT(runtime/int) + if (X509* peer_cert = SSL_get_peer_certificate(ssl.get())) { + X509_free(peer_cert); + err = SSL_get_verify_result(ssl.get()); + } else { + const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(ssl.get()); + const SSL_SESSION* sess = SSL_get_session(ssl.get()); + // Allow no-cert for PSK authentication in TLS1.2 and lower. + // In TLS1.3 check that session was reused because TLS1.3 PSK + // looks like session resumption. + if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || + (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && + SSL_session_reused(ssl.get()))) { + return X509_V_OK; + } + } + return err; +} + +int UseSNIContext(const SSLPointer& ssl, SecureContext* context) { + SSL_CTX* ctx = context->ctx_.get(); + X509* x509 = SSL_CTX_get0_certificate(ctx); + EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx); + STACK_OF(X509)* chain; + + int err = SSL_CTX_get0_chain_certs(ctx, &chain); + if (err == 1) err = SSL_use_certificate(ssl.get(), x509); + if (err == 1) err = SSL_use_PrivateKey(ssl.get(), pkey); + if (err == 1 && chain != nullptr) err = SSL_set1_chain(ssl.get(), chain); + return err; +} + +const char* GetClientHelloALPN(const SSLPointer& ssl) { + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext( + ssl.get(), + TLSEXT_TYPE_application_layer_protocol_negotiation, + &buf, + &rem) || + rem < 2) { + return nullptr; + } + + len = (buf[0] << 8) | buf[1]; + if (len + 2 != rem) return nullptr; + return reinterpret_cast(buf + 3); +} + +const char* GetClientHelloServerName(const SSLPointer& ssl) { + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext( + ssl.get(), + TLSEXT_TYPE_server_name, + &buf, + &rem) || rem <= 2) { + return nullptr; + } + + len = (*buf << 8) | *(buf + 1); + if (len + 2 != rem) + return nullptr; + rem = len; + + if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; + rem--; + if (rem <= 2) + return nullptr; + len = (*(buf + 3) << 8) | *(buf + 4); + if (len + 2 > rem) + return nullptr; + return reinterpret_cast(buf + 5); +} + +const char* GetServerName(SSL* ssl) { + return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); +} + +bool SetGroups(SecureContext* sc, const char* groups) { + return SSL_CTX_set1_groups_list(**sc, groups) == 1; +} + +const char* X509ErrorCode(long err) { // NOLINT(runtime/int) + const char* code = "UNSPECIFIED"; +#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; + switch (err) { + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) + CASE_X509_ERR(UNABLE_TO_GET_CRL) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) + CASE_X509_ERR(CERT_SIGNATURE_FAILURE) + CASE_X509_ERR(CRL_SIGNATURE_FAILURE) + CASE_X509_ERR(CERT_NOT_YET_VALID) + CASE_X509_ERR(CERT_HAS_EXPIRED) + CASE_X509_ERR(CRL_NOT_YET_VALID) + CASE_X509_ERR(CRL_HAS_EXPIRED) + CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) + CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) + CASE_X509_ERR(OUT_OF_MEM) + CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) + CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) + CASE_X509_ERR(CERT_CHAIN_TOO_LONG) + CASE_X509_ERR(CERT_REVOKED) + CASE_X509_ERR(INVALID_CA) + CASE_X509_ERR(PATH_LENGTH_EXCEEDED) + CASE_X509_ERR(INVALID_PURPOSE) + CASE_X509_ERR(CERT_UNTRUSTED) + CASE_X509_ERR(CERT_REJECTED) + CASE_X509_ERR(HOSTNAME_MISMATCH) + } +#undef CASE_X509_ERR + return code; +} + +MaybeLocal GetValidationErrorReason(Environment* env, int err) { + const char* reason = X509_verify_cert_error_string(err); + return OneByteString(env->isolate(), reason); +} + +MaybeLocal GetValidationErrorCode(Environment* env, int err) { + return OneByteString(env->isolate(), X509ErrorCode(err)); +} + +MaybeLocal GetCert(Environment* env, const SSLPointer& ssl) { + ClearErrorOnReturn clear_error_on_return; + X509* cert = SSL_get_certificate(ssl.get()); + if (cert == nullptr) + return Undefined(env->isolate()); + + Local ret; + MaybeLocal maybe_cert = X509ToObject(env, cert); + return maybe_cert.ToLocal(&ret) ? ret : MaybeLocal(); +} + +namespace { +template +bool Set( + Local context, + Local target, + Local name, + MaybeLocal maybe_value) { + Local value; + if (!maybe_value.ToLocal(&value)) + return false; + + // Undefined is ignored, but still considered successful + if (value->IsUndefined()) + return true; + + return !target->Set(context, name, value).IsNothing(); +} + +Local ToV8Value(Environment* env, const BIOPointer& bio) { + BUF_MEM* mem; + BIO_get_mem_ptr(bio.get(), &mem); + MaybeLocal ret = + String::NewFromUtf8( + env->isolate(), + mem->data, + NewStringType::kNormal, + mem->length); + USE(BIO_reset(bio.get())); + return ret.FromMaybe(Local()); +} + +MaybeLocal GetCipherName( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher)); +} + +MaybeLocal GetCipherStandardName( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_standard_name(cipher)); +} + +MaybeLocal GetCipherVersion( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_get_version(cipher)); +} + +StackOfX509 CloneSSLCerts(X509Pointer&& cert, + const STACK_OF(X509)* const ssl_certs) { + StackOfX509 peer_certs(sk_X509_new(nullptr)); + if (cert) + sk_X509_push(peer_certs.get(), cert.release()); + for (int i = 0; i < sk_X509_num(ssl_certs); i++) { + X509Pointer cert(X509_dup(sk_X509_value(ssl_certs, i))); + if (!cert || !sk_X509_push(peer_certs.get(), cert.get())) + return StackOfX509(); + // `cert` is now managed by the stack. + cert.release(); + } + return peer_certs; +} + +MaybeLocal AddIssuerChainToObject( + X509Pointer* cert, + Local object, + StackOfX509&& peer_certs, + Environment* const env) { + Local context = env->isolate()->GetCurrentContext(); + cert->reset(sk_X509_delete(peer_certs.get(), 0)); + for (;;) { + int i; + for (i = 0; i < sk_X509_num(peer_certs.get()); i++) { + X509* ca = sk_X509_value(peer_certs.get(), i); + if (X509_check_issued(ca, cert->get()) != X509_V_OK) + continue; + + Local ca_info; + MaybeLocal maybe_ca_info = X509ToObject(env, ca); + if (!maybe_ca_info.ToLocal(&ca_info)) + return MaybeLocal(); + + if (!Set(context, object, env->issuercert_string(), ca_info)) + return MaybeLocal(); + object = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore. + // Delete cert and continue aggregating issuers. + cert->reset(sk_X509_delete(peer_certs.get(), i)); + break; + } + + // Issuer not found, break out of the loop. + if (i == sk_X509_num(peer_certs.get())) + break; + } + return MaybeLocal(object); +} + +MaybeLocal GetLastIssuedCert( + X509Pointer* cert, + const SSLPointer& ssl, + Local issuer_chain, + Environment* const env) { + Local context = env->isolate()->GetCurrentContext(); + while (X509_check_issued(cert->get(), cert->get()) != X509_V_OK) { + X509* ca; + if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl.get()), cert->get(), &ca) <= 0) + break; + + Local ca_info; + MaybeLocal maybe_ca_info = X509ToObject(env, ca); + if (!maybe_ca_info.ToLocal(&ca_info)) + return MaybeLocal(); + + if (!Set(context, issuer_chain, env->issuercert_string(), ca_info)) + return MaybeLocal(); + issuer_chain = ca_info; + + // Delete previous cert and continue aggregating issuers. + cert->reset(ca); + } + return MaybeLocal(issuer_chain); +} + +MaybeLocal GetRawDERCertificate(Environment* env, X509* cert) { + int size = i2d_X509(cert, nullptr); + + AllocatedBuffer buffer = env->AllocateManaged(size); + unsigned char* serialized = + reinterpret_cast(buffer.data()); + i2d_X509(cert, &serialized); + return buffer.ToBuffer(); +} + +MaybeLocal GetSerialNumber(Environment* env, X509* cert) { + if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { + BignumPointer bn(ASN1_INTEGER_to_BN(serial_number, nullptr)); + if (bn) { + OpenSSLBuffer buf(BN_bn2hex(bn.get())); + if (buf) + return OneByteString(env->isolate(), buf.get()); + } + } + + return Undefined(env->isolate()); +} + +MaybeLocal GetKeyUsage(Environment* env, X509* cert) { + StackOfASN1 eku(static_cast( + X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr))); + if (eku) { + const int count = sk_ASN1_OBJECT_num(eku.get()); + MaybeStackBuffer, 16> ext_key_usage(count); + char buf[256]; + + int j = 0; + for (int i = 0; i < count; i++) { + if (OBJ_obj2txt(buf, + sizeof(buf), + sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { + ext_key_usage[j++] = OneByteString(env->isolate(), buf); + } + } + + return Array::New(env->isolate(), ext_key_usage.out(), count); + } + + return Undefined(env->isolate()); +} + +void AddFingerprintDigest( + const unsigned char* md, + unsigned int md_size, + char (*fingerprint)[3 * EVP_MAX_MD_SIZE + 1]) { + unsigned int i; + const char hex[] = "0123456789ABCDEF"; + + for (i = 0; i < md_size; i++) { + (*fingerprint)[3*i] = hex[(md[i] & 0xf0) >> 4]; + (*fingerprint)[(3*i)+1] = hex[(md[i] & 0x0f)]; + (*fingerprint)[(3*i)+2] = ':'; + } + + if (md_size > 0) { + (*fingerprint)[(3*(md_size-1))+2] = '\0'; + } else { + (*fingerprint)[0] = '\0'; + } +} + +bool SafeX509ExtPrint(const BIOPointer& out, X509_EXTENSION* ext) { + const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); + + if (method != X509V3_EXT_get_nid(NID_subject_alt_name)) + return false; + + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + if (names == nullptr) + return false; + + for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); + + if (i != 0) + BIO_write(out.get(), ", ", 2); + + if (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + + BIO_write(out.get(), "DNS:", 4); + BIO_write(out.get(), name->data, name->length); + } else { + STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( + const_cast(method), gen, nullptr); + if (nval == nullptr) + return false; + X509V3_EXT_val_prn(out.get(), nval, 0, 0); + sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return true; +} + +MaybeLocal GetFingerprintDigest( + Environment* env, + const EVP_MD* method, + X509* cert) { + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_size; + char fingerprint[EVP_MAX_MD_SIZE * 3 + 1]; + + if (X509_digest(cert, method, md, &md_size)) { + AddFingerprintDigest(md, md_size, &fingerprint); + return OneByteString(env->isolate(), fingerprint); + } + return Undefined(env->isolate()); +} + +MaybeLocal GetValidTo( + Environment* env, + X509* cert, + const BIOPointer& bio) { + ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert)); + return ToV8Value(env, bio); +} + +MaybeLocal GetValidFrom( + Environment* env, + X509* cert, + const BIOPointer& bio) { + ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert)); + return ToV8Value(env, bio); +} + +MaybeLocal GetCurveASN1Name(Environment* env, const int nid) { + const char* nist = OBJ_nid2sn(nid); + return nist != nullptr ? + MaybeLocal(OneByteString(env->isolate(), nist)) : + MaybeLocal(Undefined(env->isolate())); +} + +MaybeLocal GetCurveNistName(Environment* env, const int nid) { + const char* nist = EC_curve_nid2nist(nid); + return nist != nullptr ? + MaybeLocal(OneByteString(env->isolate(), nist)) : + MaybeLocal(Undefined(env->isolate())); +} + +MaybeLocal GetECPubKey( + Environment* env, + const EC_GROUP* group, + const ECPointer& ec) { + const EC_POINT* pubkey = EC_KEY_get0_public_key(ec.get()); + if (pubkey == nullptr) + return Undefined(env->isolate()); + + return ECPointToBuffer( + env, + group, + pubkey, + EC_KEY_get_conv_form(ec.get()), + nullptr).FromMaybe(Local()); +} + +MaybeLocal GetECGroup( + Environment* env, + const EC_GROUP* group, + const ECPointer& ec) { + if (group == nullptr) + return Undefined(env->isolate()); + + int bits = EC_GROUP_order_bits(group); + if (bits <= 0) + return Undefined(env->isolate()); + + return Integer::New(env->isolate(), bits); +} + +MaybeLocal GetPubKey(Environment* env, const RSAPointer& rsa) { + int size = i2d_RSA_PUBKEY(rsa.get(), nullptr); + CHECK_GE(size, 0); + + AllocatedBuffer buffer = env->AllocateManaged(size); + unsigned char* serialized = + reinterpret_cast(buffer.data()); + i2d_RSA_PUBKEY(rsa.get(), &serialized); + return buffer.ToBuffer(); +} + +MaybeLocal GetExponentString( + Environment* env, + const BIOPointer& bio, + const BIGNUM* e) { + uint64_t exponent_word = static_cast(BN_get_word(e)); + uint32_t lo = static_cast(exponent_word); + uint32_t hi = static_cast(exponent_word >> 32); + if (hi == 0) + BIO_printf(bio.get(), "0x%x", lo); + else + BIO_printf(bio.get(), "0x%x%08x", hi, lo); + + return ToV8Value(env, bio); +} + +Local GetBits(Environment* env, const BIGNUM* n) { + return Integer::New(env->isolate(), BN_num_bits(n)); +} + +MaybeLocal GetModulusString( + Environment* env, + const BIOPointer& bio, + const BIGNUM* n) { + BN_print(bio.get(), n); + return ToV8Value(env, bio); +} + +template +MaybeLocal GetInfoString( + Environment* env, + const BIOPointer& bio, + X509* cert) { + int index = X509_get_ext_by_NID(cert, nid, -1); + if (index < 0) + return Undefined(env->isolate()); + + X509_EXTENSION* ext = X509_get_ext(cert, index); + CHECK_NOT_NULL(ext); + + if (!SafeX509ExtPrint(bio, ext) && + X509V3_EXT_print(bio.get(), ext, 0, 0) != 1) { + USE(BIO_reset(bio.get())); + return Null(env->isolate()); + } + + return ToV8Value(env, bio); +} + +MaybeLocal GetIssuerString( + Environment* env, + const BIOPointer& bio, + X509* cert) { + X509_NAME* issuer_name = X509_get_issuer_name(cert); + if (X509_NAME_print_ex(bio.get(), issuer_name, 0, X509_NAME_FLAGS) <= 0) { + USE(BIO_reset(bio.get())); + return Undefined(env->isolate()); + } + + return ToV8Value(env, bio); +} + +MaybeLocal GetSubject( + Environment* env, + const BIOPointer& bio, + X509* cert) { + if (X509_NAME_print_ex( + bio.get(), + X509_get_subject_name(cert), + 0, + X509_NAME_FLAGS) <= 0) { + USE(BIO_reset(bio.get())); + return Undefined(env->isolate()); + } + + return ToV8Value(env, bio); +} +} // namespace + +MaybeLocal GetCipherName(Environment* env, const SSLPointer& ssl) { + return GetCipherName(env, SSL_get_current_cipher(ssl.get())); +} + +MaybeLocal GetCipherStandardName( + Environment* env, + const SSLPointer& ssl) { + return GetCipherStandardName(env, SSL_get_current_cipher(ssl.get())); +} + +MaybeLocal GetCipherVersion(Environment* env, const SSLPointer& ssl) { + return GetCipherVersion(env, SSL_get_current_cipher(ssl.get())); +} + +MaybeLocal GetClientHelloCiphers( + Environment* env, + const SSLPointer& ssl) { + EscapableHandleScope scope(env->isolate()); + const unsigned char* buf; + size_t len = SSL_client_hello_get0_ciphers(ssl.get(), &buf); + size_t count = len / 2; + MaybeStackBuffer, 16> ciphers(count); + int j = 0; + for (size_t n = 0; n < len; n += 2) { + const SSL_CIPHER* cipher = SSL_CIPHER_find(ssl.get(), buf); + buf += 2; + Local obj = Object::New(env->isolate()); + if (!Set(env->context(), + obj, + env->name_string(), + GetCipherName(env, cipher)) || + !Set(env->context(), + obj, + env->standard_name_string(), + GetCipherStandardName(env, cipher)) || + !Set(env->context(), + obj, + env->version_string(), + GetCipherVersion(env, cipher))) { + return MaybeLocal(); + } + ciphers[j++] = obj; + } + Local ret = Array::New(env->isolate(), ciphers.out(), count); + return scope.Escape(ret); +} + + +MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { + EscapableHandleScope scope(env->isolate()); + Local info = Object::New(env->isolate()); + + if (!Set(env->context(), + info, + env->name_string(), + GetCipherName(env, ssl)) || + !Set(env->context(), + info, + env->standard_name_string(), + GetCipherStandardName(env, ssl)) || + !Set(env->context(), + info, + env->version_string(), + GetCipherVersion(env, ssl))) { + return MaybeLocal(); + } + + return scope.Escape(info); +} + +MaybeLocal GetEphemeralKey(Environment* env, const SSLPointer& ssl) { + CHECK_EQ(SSL_is_server(ssl.get()), 0); + EVP_PKEY* raw_key; + + EscapableHandleScope scope(env->isolate()); + Local info = Object::New(env->isolate()); + if (!SSL_get_server_tmp_key(ssl.get(), &raw_key)) + return scope.Escape(info); + + Local context = env->context(); + crypto::EVPKeyPointer key(raw_key); + + int kid = EVP_PKEY_id(key.get()); + int bits = EVP_PKEY_bits(key.get()); + switch (kid) { + case EVP_PKEY_DH: + if (!Set(context, info, env->type_string(), env->dh_string()) || + !Set(context, + info, + env->size_string(), + Integer::New(env->isolate(), bits))) { + return MaybeLocal(); + } + break; + case EVP_PKEY_EC: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + { + const char* curve_name; + if (kid == EVP_PKEY_EC) { + ECKeyPointer ec(EVP_PKEY_get1_EC_KEY(key.get())); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get())); + curve_name = OBJ_nid2sn(nid); + } else { + curve_name = OBJ_nid2sn(kid); + } + if (!Set(context, + info, + env->type_string(), + env->ecdh_string()) || + !Set(context, + info, + env->name_string(), + OneByteString(env->isolate(), curve_name)) || + !Set(context, + info, + env->size_string(), + Integer::New(env->isolate(), bits))) { + return MaybeLocal(); + } + } + break; + } + + return scope.Escape(info); +} + +MaybeLocal ECPointToBuffer(Environment* env, + const EC_GROUP* group, + const EC_POINT* point, + point_conversion_form_t form, + const char** error) { + size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); + if (len == 0) { + if (error != nullptr) *error = "Failed to get public key length"; + return MaybeLocal(); + } + AllocatedBuffer buf = env->AllocateManaged(len); + len = EC_POINT_point2oct(group, + point, + form, + reinterpret_cast(buf.data()), + buf.size(), + nullptr); + if (len == 0) { + if (error != nullptr) *error = "Failed to get public key"; + return MaybeLocal(); + } + return buf.ToBuffer(); +} + +MaybeLocal GetPeerCert( + Environment* env, + const SSLPointer& ssl, + bool abbreviated, + bool is_server) { + ClearErrorOnReturn clear_error_on_return; + Local result; + MaybeLocal maybe_cert; + + // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` + // contains the `peer_certificate`, but on server it doesn't. + X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr); + STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); + if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) + return Undefined(env->isolate()); + + // Short result requested. + if (abbreviated) { + maybe_cert = + X509ToObject(env, cert ? cert.get() : sk_X509_value(ssl_certs, 0)); + return maybe_cert.ToLocal(&result) ? result : MaybeLocal(); + } + + StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs); + if (peer_certs == nullptr) + return Undefined(env->isolate()); + + // First and main certificate. + X509Pointer first_cert(sk_X509_value(peer_certs.get(), 0)); + CHECK(first_cert); + maybe_cert = X509ToObject(env, first_cert.release()).ToLocalChecked(); + if (!maybe_cert.ToLocal(&result)) + return MaybeLocal(); + + Local issuer_chain; + MaybeLocal maybe_issuer_chain; + + maybe_issuer_chain = + AddIssuerChainToObject( + &cert, + result, + std::move(peer_certs), + env); + if (!maybe_issuer_chain.ToLocal(&issuer_chain)) + return MaybeLocal(); + + maybe_issuer_chain = + GetLastIssuedCert( + &cert, + ssl, + issuer_chain, + env); + + issuer_chain.Clear(); + if (!maybe_issuer_chain.ToLocal(&issuer_chain)) + return MaybeLocal(); + + // Last certificate should be self-signed. + if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK && + !Set(env->context(), + issuer_chain, + env->issuercert_string(), + issuer_chain)) { + return MaybeLocal(); + } + + return result; +} + +MaybeLocal X509ToObject(Environment* env, X509* cert) { + EscapableHandleScope scope(env->isolate()); + Local context = env->context(); + Local info = Object::New(env->isolate()); + + BIOPointer bio(BIO_new(BIO_s_mem())); + + if (!Set(context, + info, + env->subject_string(), + GetSubject(env, bio, cert)) || + !Set(context, + info, + env->issuer_string(), + GetIssuerString(env, bio, cert)) || + !Set(context, + info, + env->subjectaltname_string(), + GetInfoString(env, bio, cert)) || + !Set(context, + info, + env->infoaccess_string(), + GetInfoString(env, bio, cert))) { + return MaybeLocal(); + } + + EVPKeyPointer pkey(X509_get_pubkey(cert)); + RSAPointer rsa; + ECPointer ec; + if (pkey) { + switch (EVP_PKEY_id(pkey.get())) { + case EVP_PKEY_RSA: + rsa.reset(EVP_PKEY_get1_RSA(pkey.get())); + break; + case EVP_PKEY_EC: + ec.reset(EVP_PKEY_get1_EC_KEY(pkey.get())); + break; + } + } + + if (rsa) { + const BIGNUM* n; + const BIGNUM* e; + RSA_get0_key(rsa.get(), &n, &e, nullptr); + if (!Set(context, + info, + env->modulus_string(), + GetModulusString(env, bio, n)) || + !Set(context, info, env->bits_string(), GetBits(env, n)) || + !Set(context, + info, + env->exponent_string(), + GetExponentString(env, bio, e)) || + !Set(context, + info, + env->pubkey_string(), + GetPubKey(env, rsa))) { + return MaybeLocal(); + } + } else if (ec) { + const EC_GROUP* group = EC_KEY_get0_group(ec.get()); + + if (!Set(context, + info, + env->bits_string(), + GetECGroup(env, group, ec)) || + !Set(context, + info, + env->pubkey_string(), + GetECPubKey(env, group, ec))) { + return MaybeLocal(); + } + + const int nid = EC_GROUP_get_curve_name(group); + if (nid != 0) { + // Curve is well-known, get its OID and NIST nick-name (if it has one). + + if (!Set(context, + info, + env->asn1curve_string(), + GetCurveASN1Name(env, nid)) || + !Set(context, + info, + env->nistcurve_string(), + GetCurveNistName(env, nid))) { + return MaybeLocal(); + } + } else { + // Unnamed curves can be described by their mathematical properties, + // but aren't used much (at all?) with X.509/TLS. Support later if needed. + } + } + + // pkey, rsa, and ec pointers are no longer needed. + pkey.reset(); + rsa.reset(); + ec.reset(); + + if (!Set(context, + info, + env->valid_from_string(), + GetValidFrom(env, cert, bio)) || + !Set(context, + info, + env->valid_to_string(), + GetValidTo(env, cert, bio))) { + return MaybeLocal(); + } + + // bio is no longer needed + bio.reset(); + + if (!Set(context, + info, + env->fingerprint_string(), + GetFingerprintDigest(env, EVP_sha1(), cert)) || + !Set(context, + info, + env->fingerprint256_string(), + GetFingerprintDigest(env, EVP_sha256(), cert)) || + !Set(context, + info, + env->ext_key_usage_string(), + GetKeyUsage(env, cert)) || + !Set(context, + info, + env->serial_number_string(), + GetSerialNumber(env, cert)) || + !Set(context, + info, + env->raw_string(), + GetRawDERCertificate(env, cert))) { + return MaybeLocal(); + } + + return scope.Escape(info); +} + +} // namespace crypto +} // namespace node diff --git a/src/node_crypto_common.h b/src/node_crypto_common.h new file mode 100644 index 00000000000..e42e249ef2b --- /dev/null +++ b/src/node_crypto_common.h @@ -0,0 +1,139 @@ +#ifndef SRC_NODE_CRYPTO_COMMON_H_ +#define SRC_NODE_CRYPTO_COMMON_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "node_crypto.h" +#include "v8.h" +#include +#include + +#include +#include + +namespace node { +namespace crypto { + +// OPENSSL_free is a macro, so we need a wrapper function. +struct OpenSSLBufferDeleter { + void operator()(char* pointer) const { OPENSSL_free(pointer); } +}; +using OpenSSLBuffer = std::unique_ptr; + +struct StackOfX509Deleter { + void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } +}; +using StackOfX509 = std::unique_ptr; + +struct StackOfXASN1Deleter { + void operator()(STACK_OF(ASN1_OBJECT)* p) const { + sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); + } +}; +using StackOfASN1 = std::unique_ptr; + +int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer); + +void LogSecret( + const SSLPointer& ssl, + const char* name, + const unsigned char* secret, + size_t secretlen); + +bool SetALPN(const SSLPointer& ssl, const std::string& alpn); + +bool SetALPN(const SSLPointer& ssl, v8::Local alpn); + +v8::MaybeLocal GetSSLOCSPResponse( + Environment* env, + SSL* ssl, + v8::Local default_value); + +bool SetTLSSession( + const SSLPointer& ssl, + const unsigned char* buf, + size_t length); + +bool SetTLSSession( + const SSLPointer& ssl, + const SSLSessionPointer& session); + +SSLSessionPointer GetTLSSession(v8::Local val); + +SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length); + +std::unordered_multimap +GetCertificateAltNames(X509* cert); + +std::string GetCertificateCN(X509* cert); + +long VerifyPeerCertificate( // NOLINT(runtime/int) + const SSLPointer& ssl, + long def = X509_V_ERR_UNSPECIFIED); // NOLINT(runtime/int) + +int UseSNIContext(const SSLPointer& ssl, SecureContext* context); + +const char* GetClientHelloALPN(const SSLPointer& ssl); + +const char* GetClientHelloServerName(const SSLPointer& ssl); + +const char* GetServerName(SSL* ssl); + +v8::MaybeLocal GetClientHelloCiphers( + Environment* env, + const SSLPointer& ssl); + +bool SetGroups(SecureContext* sc, const char* groups); + +const char* X509ErrorCode(long err); // NOLINT(runtime/int) + +v8::MaybeLocal GetValidationErrorReason(Environment* env, int err); + +v8::MaybeLocal GetValidationErrorCode(Environment* env, int err); + +v8::MaybeLocal GetCert(Environment* env, const SSLPointer& ssl); + +v8::MaybeLocal GetCipherName( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherStandardName( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherVersion( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherInfo( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetEphemeralKey( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetPeerCert( + Environment* env, + const SSLPointer& ssl, + bool abbreviated = false, + bool is_server = false); + +v8::MaybeLocal ECPointToBuffer( + Environment* env, + const EC_GROUP* group, + const EC_POINT* point, + point_conversion_form_t form, + const char** error); + +v8::MaybeLocal X509ToObject( + Environment* env, + X509* cert); + +} // namespace crypto +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_CRYPTO_COMMON_H_ diff --git a/src/string_bytes.cc b/src/string_bytes.cc index f8d7243e5d6..7ee87a8ebe8 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -587,7 +587,11 @@ static void force_ascii(const char* src, char* dst, size_t len) { } -static size_t hex_encode(const char* src, size_t slen, char* dst, size_t dlen) { +size_t StringBytes::hex_encode( + const char* src, + size_t slen, + char* dst, + size_t dlen) { // We know how much we'll write, just make sure that there's space. CHECK(dlen >= slen * 2 && "not enough space provided for hex encode"); @@ -603,6 +607,12 @@ static size_t hex_encode(const char* src, size_t slen, char* dst, size_t dlen) { return dlen; } +std::string StringBytes::hex_encode(const char* src, size_t slen) { + size_t dlen = slen * 2; + std::string dst(dlen, '\0'); + hex_encode(src, slen, &dst[0], dlen); + return dst; +} #define CHECK_BUFLEN_IN_RANGE(len) \ do { \ diff --git a/src/string_bytes.h b/src/string_bytes.h index 5ef05fc48cd..69bb828e018 100644 --- a/src/string_bytes.h +++ b/src/string_bytes.h @@ -29,6 +29,8 @@ #include "v8.h" #include "env-inl.h" +#include + namespace node { class StringBytes { @@ -97,6 +99,13 @@ class StringBytes { enum encoding encoding, v8::Local* error); + static size_t hex_encode(const char* src, + size_t slen, + char* dst, + size_t dlen); + + static std::string hex_encode(const char* src, size_t slen); + private: static size_t WriteUCS2(v8::Isolate* isolate, char* buf, From 987a67339518d0380177a2e589f2bbd274230d0e Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 26 Feb 2020 16:21:41 -0800 Subject: [PATCH 111/192] src: start the .text section with an asm symbol We create an object file in assembly which introduces the symbol `__node_text_start` into the .text section and place the resulting object file as the first file the linker encounters. We do this to ensure that we can recognize the boundaries of the .text section when attempting to establish the address range to map to large pages. Additionally, we rename the section containing the remapping code from `.lpstub` to `lpstub` so as to take advantage of the linker's feature whereby it inserts the symbol `__start_lpstub` when the section's name can be rendered as a valid C variable. We need this symbol in order to avoid self-mapping the remapping code to large pages, because doing so would cause the process to crash. PR-URL: https://github.com/nodejs/node/pull/31981 Reviewed-By: Franziska Hinkelmann Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell Reviewed-By: David Carlier Signed-off-by: Gabriel Schulhof --- node.gyp | 20 ++++++++++++++++++++ src/large_pages/node_large_page.cc | 28 ++++++++++++++-------------- src/large_pages/node_text_start.S | 5 +++++ 3 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 src/large_pages/node_text_start.S diff --git a/node.gyp b/node.gyp index 60aff022842..c6858584cd0 100644 --- a/node.gyp +++ b/node.gyp @@ -314,6 +314,19 @@ }, 'targets': [ + { + 'target_name': 'node_text_start', + 'type': 'none', + 'conditions': [ + [ 'OS=="linux" and ' + 'target_arch=="x64"', { + 'type': 'static_library', + 'sources': [ + 'src/large_pages/node_text_start.S' + ] + }], + ] + }, { 'target_name': '<(node_core_target_name)', 'type': 'executable', @@ -499,6 +512,13 @@ 'src/node_snapshot_stub.cc' ], }], + [ 'OS=="linux" and ' + 'target_arch=="x64"', { + 'dependencies': [ 'node_text_start' ], + 'ldflags+': [ + '<(PRODUCT_DIR)/obj.target/node_text_start/src/large_pages/node_text_start.o' + ] + }], ], }, # node_core_target_name { diff --git a/src/large_pages/node_large_page.cc b/src/large_pages/node_large_page.cc index a72cb65bb65..31d85c1734a 100644 --- a/src/large_pages/node_large_page.cc +++ b/src/large_pages/node_large_page.cc @@ -71,7 +71,11 @@ #if defined(__linux__) extern "C" { -extern char __executable_start; +// This symbol must be declared weak because this file becomes part of all +// Node.js targets (like node_mksnapshot, node_mkcodecache, and cctest) and +// those files do not supply the symbol. +extern char __attribute__((weak)) __node_text_start; +extern char __start_lpstub; } // extern "C" #endif // defined(__linux__) @@ -121,6 +125,8 @@ struct text_region FindNodeTextRegion() { std::string dev; char dash; uintptr_t start, end, offset, inode; + uintptr_t node_text_start = reinterpret_cast(&__node_text_start); + uintptr_t lpstub_start = reinterpret_cast(&__start_lpstub); ifs.open("/proc/self/maps"); if (!ifs) { @@ -144,21 +150,15 @@ struct text_region FindNodeTextRegion() { std::string pathname; iss >> pathname; - if (start != reinterpret_cast(&__executable_start)) + if (permission != "r-xp") continue; - // The next line is our .text section. - if (!std::getline(ifs, map_line)) - break; - - iss = std::istringstream(map_line); - iss >> std::hex >> start; - iss >> dash; - iss >> std::hex >> end; - iss >> permission; + if (node_text_start < start || node_text_start >= end) + continue; - if (permission != "r-xp") - break; + start = node_text_start; + if (lpstub_start > start && lpstub_start <= end) + end = lpstub_start; char* from = reinterpret_cast(hugepage_align_up(start)); char* to = reinterpret_cast(hugepage_align_down(end)); @@ -318,7 +318,7 @@ static bool IsSuperPagesEnabled() { // d. If successful copy the code there and unmap the original region int #if !defined(__APPLE__) -__attribute__((__section__(".lpstub"))) +__attribute__((__section__("lpstub"))) #else __attribute__((__section__("__TEXT,__lpstub"))) #endif diff --git a/src/large_pages/node_text_start.S b/src/large_pages/node_text_start.S new file mode 100644 index 00000000000..1609b254f04 --- /dev/null +++ b/src/large_pages/node_text_start.S @@ -0,0 +1,5 @@ +.text +.align 0x2000 +.global __node_text_start +.hidden __node_text_start +__node_text_start: From 3ec4b21b1c438255df6f1652377011080dc28052 Mon Sep 17 00:00:00 2001 From: gengjiawen Date: Fri, 21 Feb 2020 22:31:15 +0800 Subject: [PATCH 112/192] build: add asan check in Github action PR-URL: https://github.com/nodejs/node/pull/31902 Reviewed-By: Ben Noordhuis --- .github/workflows/ASAN.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ASAN.yml diff --git a/.github/workflows/ASAN.yml b/.github/workflows/ASAN.yml new file mode 100644 index 00000000000..77b8fd530ad --- /dev/null +++ b/.github/workflows/ASAN.yml @@ -0,0 +1,20 @@ +name: node ASAN + +on: [push, pull_request] + +jobs: + ubuntu-build: + runs-on: ubuntu-latest + container: gengjiawen/node-build:2020-02-14 + steps: + - uses: actions/checkout@v2 + - name: Build + run: | + npx envinfo + ./configure --debug --enable-asan --ninja && ninja -C out/Debug + - name: Test + env: + ASAN_OPTIONS: halt_on_error=0 + continue-on-error: true + run: | + python3 tools/test.py -J --mode=debug From 7cafd5f3a924451294a4f2ce6efb628b53fce7eb Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Tue, 28 Jan 2020 07:05:03 +0100 Subject: [PATCH 113/192] stream: fix finished w/ 'close' before 'end' Emitting 'close' before 'end' on a Readable should result in a premature close error. PR-URL: https://github.com/nodejs/node/pull/31545 Reviewed-By: Matteo Collina Reviewed-By: Rich Trott --- lib/internal/streams/end-of-stream.js | 9 ++++++- lib/internal/streams/pipeline.js | 38 ++++++++++++++++++--------- test/parallel/test-stream-finished.js | 10 +++++++ test/parallel/test-stream-pipeline.js | 18 +++++++++++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/lib/internal/streams/end-of-stream.js b/lib/internal/streams/end-of-stream.js index 88bdcb643dd..9f1e0ba0dc9 100644 --- a/lib/internal/streams/end-of-stream.js +++ b/lib/internal/streams/end-of-stream.js @@ -34,6 +34,13 @@ function isWritableFinished(stream) { function nop() {} +function isReadableEnded(stream) { + if (stream.readableEnded) return true; + const rState = stream._readableState; + if (!rState || rState.errored) return false; + return rState.endEmitted || (rState.ended && rState.length === 0); +} + function eos(stream, opts, callback) { if (arguments.length === 2) { callback = opts; @@ -84,7 +91,7 @@ function eos(stream, opts, callback) { const onclose = () => { let err; if (readable && !readableEnded) { - if (!rState || !rState.ended) + if (!isReadableEnded(stream)) err = new ERR_STREAM_PREMATURE_CLOSE(); return callback.call(stream, err); } diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index 8c6cbf7524e..46d499709ee 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -26,23 +26,37 @@ let PassThrough; let createReadableStreamAsyncIterator; function destroyer(stream, reading, writing, callback) { - callback = once(callback); - let destroyed = false; + const _destroy = once((err) => { + destroyImpl.destroyer(stream, err); + callback(err); + }); if (eos === undefined) eos = require('internal/streams/end-of-stream'); eos(stream, { readable: reading, writable: writing }, (err) => { - if (destroyed) return; - destroyed = true; - destroyImpl.destroyer(stream, err); - callback(err); + const rState = stream._readableState; + if ( + err && + err.code === 'ERR_STREAM_PREMATURE_CLOSE' && + reading && + (rState && rState.ended && !rState.errored && !rState.errorEmitted) + ) { + // Some readable streams will emit 'close' before 'end'. However, since + // this is on the readable side 'end' should still be emitted if the + // stream has been ended and no error emitted. This should be allowed in + // favor of backwards compatibility. Since the stream is piped to a + // destination this should not result in any observable difference. + // We don't need to check if this is a writable premature close since + // eos will only fail with premature close on the reading side for + // duplex streams. + stream + .once('end', _destroy) + .once('error', _destroy); + } else { + _destroy(err); + } }); - return (err) => { - if (destroyed) return; - destroyed = true; - destroyImpl.destroyer(stream, err); - callback(err || new ERR_STREAM_DESTROYED('pipe')); - }; + return (err) => _destroy(err || new ERR_STREAM_DESTROYED('pipe')); } function popCallback(streams) { diff --git a/test/parallel/test-stream-finished.js b/test/parallel/test-stream-finished.js index c9c6bafd642..46a5f939134 100644 --- a/test/parallel/test-stream-finished.js +++ b/test/parallel/test-stream-finished.js @@ -342,3 +342,13 @@ testClosed((opts) => new Writable({ write() {}, ...opts })); d._writableState.errored = true; d.emit('close'); } + +{ + const r = new Readable(); + finished(r, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); + r.push('asd'); + r.push(null); + r.destroy(); +} diff --git a/test/parallel/test-stream-pipeline.js b/test/parallel/test-stream-pipeline.js index 87e11f81162..74473b0db40 100644 --- a/test/parallel/test-stream-pipeline.js +++ b/test/parallel/test-stream-pipeline.js @@ -920,3 +920,21 @@ const { promisify } = require('util'); })); src.end(); } + +{ + // Make sure 'close' before 'end' finishes without error + // if readable has received eof. + // Ref: https://github.com/nodejs/node/issues/29699 + const r = new Readable(); + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + } + }); + pipeline(r, w, (err) => { + assert.strictEqual(err, undefined); + }); + r.push('asd'); + r.push(null); + r.emit('close'); +} From 43b7142fedc706d79ea77254bc463f63a3b73358 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 9 Oct 2019 18:56:21 +0200 Subject: [PATCH 114/192] dgram: make UDPWrap more reusable Allow using the handle more directly for I/O in other parts of the codebase. Originally landed in the QUIC repo Original review metadata: ``` PR-URL: https://github.com/nodejs/quic/pull/165 Reviewed-By: James M Snell Reviewed-By: Daniel Bevenius ``` Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/31871 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis --- lib/dgram.js | 4 +- src/udp_wrap.cc | 205 +++++++++++++++++++++++++++++++++++------------- src/udp_wrap.h | 116 +++++++++++++++++++++++++-- 3 files changed, 265 insertions(+), 60 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index 15421de4649..ac90f35f83e 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -231,7 +231,9 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { this.on('listening', onListening); } - if (port instanceof UDP) { + if (port !== null && + typeof port === 'object' && + typeof port.recvStart === 'function') { replaceHandle(this, port); startListening(this); return this; diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index fa5cf8da479..702449daae9 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -69,18 +69,57 @@ SendWrap::SendWrap(Environment* env, } -inline bool SendWrap::have_callback() const { +bool SendWrap::have_callback() const { return have_callback_; } +UDPListener::~UDPListener() { + if (wrap_ != nullptr) + wrap_->set_listener(nullptr); +} + +UDPWrapBase::~UDPWrapBase() { + set_listener(nullptr); +} + +UDPListener* UDPWrapBase::listener() const { + CHECK_NOT_NULL(listener_); + return listener_; +} + +void UDPWrapBase::set_listener(UDPListener* listener) { + if (listener_ != nullptr) + listener_->wrap_ = nullptr; + listener_ = listener; + if (listener_ != nullptr) { + CHECK_NULL(listener_->wrap_); + listener_->wrap_ = this; + } +} + +UDPWrapBase* UDPWrapBase::FromObject(Local obj) { + CHECK_GT(obj->InternalFieldCount(), UDPWrapBase::kUDPWrapBaseField); + return static_cast( + obj->GetAlignedPointerFromInternalField(UDPWrapBase::kUDPWrapBaseField)); +} + +void UDPWrapBase::AddMethods(Environment* env, Local t) { + env->SetProtoMethod(t, "recvStart", RecvStart); + env->SetProtoMethod(t, "recvStop", RecvStop); +} UDPWrap::UDPWrap(Environment* env, Local object) : HandleWrap(env, object, reinterpret_cast(&handle_), AsyncWrap::PROVIDER_UDPWRAP) { + object->SetAlignedPointerInInternalField( + UDPWrapBase::kUDPWrapBaseField, static_cast(this)); + int r = uv_udp_init(env->event_loop(), &handle_); CHECK_EQ(r, 0); // can't fail anyway + + set_listener(this); } @@ -91,7 +130,8 @@ void UDPWrap::Initialize(Local target, Environment* env = Environment::GetCurrent(context); Local t = env->NewFunctionTemplate(New); - t->InstanceTemplate()->SetInternalFieldCount(UDPWrap::kInternalFieldCount); + t->InstanceTemplate()->SetInternalFieldCount( + UDPWrapBase::kInternalFieldCount); Local udpString = FIXED_ONE_BYTE_STRING(env->isolate(), "UDP"); t->SetClassName(udpString); @@ -112,6 +152,7 @@ void UDPWrap::Initialize(Local target, Local(), attributes); + UDPWrapBase::AddMethods(env, t); env->SetProtoMethod(t, "open", Open); env->SetProtoMethod(t, "bind", Bind); env->SetProtoMethod(t, "connect", Connect); @@ -120,8 +161,6 @@ void UDPWrap::Initialize(Local target, env->SetProtoMethod(t, "connect6", Connect6); env->SetProtoMethod(t, "send6", Send6); env->SetProtoMethod(t, "disconnect", Disconnect); - env->SetProtoMethod(t, "recvStart", RecvStart); - env->SetProtoMethod(t, "recvStop", RecvStop); env->SetProtoMethod(t, "getpeername", GetSockOrPeerName); env->SetProtoMethod(t, "getsockname", @@ -220,6 +259,9 @@ void UDPWrap::DoBind(const FunctionCallbackInfo& args, int family) { flags); } + if (err == 0) + wrap->listener()->OnAfterBind(); + args.GetReturnValue().Set(err); } @@ -464,14 +506,10 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { CHECK(args[3]->IsBoolean()); } - Local req_wrap_obj = args[0].As(); Local chunks = args[1].As(); // it is faster to fetch the length of the // array in js-land size_t count = args[2].As()->Value(); - const bool have_callback = sendto ? args[5]->IsTrue() : args[3]->IsTrue(); - - size_t msg_size = 0; MaybeStackBuffer bufs(count); @@ -482,7 +520,6 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { size_t length = Buffer::Length(chunk); bufs[i] = uv_buf_init(Buffer::Data(chunk), length); - msg_size += length; } int err = 0; @@ -492,14 +529,36 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { const unsigned short port = args[3].As()->Value(); node::Utf8Value address(env->isolate(), args[4]); err = sockaddr_for_family(family, address.out(), port, &addr_storage); - if (err == 0) { + if (err == 0) addr = reinterpret_cast(&addr_storage); - } } - uv_buf_t* bufs_ptr = *bufs; - if (err == 0 && !UNLIKELY(env->options()->test_udp_no_try_send)) { - err = uv_udp_try_send(&wrap->handle_, bufs_ptr, count, addr); + if (err == 0) { + wrap->current_send_req_wrap_ = args[0].As(); + wrap->current_send_has_callback_ = + sendto ? args[5]->IsTrue() : args[3]->IsTrue(); + + err = wrap->Send(*bufs, count, addr); + + wrap->current_send_req_wrap_.Clear(); + wrap->current_send_has_callback_ = false; + } + + args.GetReturnValue().Set(err); +} + +ssize_t UDPWrap::Send(uv_buf_t* bufs_ptr, + size_t count, + const sockaddr* addr) { + if (IsHandleClosing()) return UV_EBADF; + + size_t msg_size = 0; + for (size_t i = 0; i < count; i++) + msg_size += bufs_ptr[i].len; + + int err = 0; + if (!UNLIKELY(env()->options()->test_udp_no_try_send)) { + err = uv_udp_try_send(&handle_, bufs_ptr, count, addr); if (err == UV_ENOSYS || err == UV_EAGAIN) { err = 0; } else if (err >= 0) { @@ -517,28 +576,41 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { CHECK_EQ(static_cast(err), msg_size); // + 1 so that the JS side can distinguish 0-length async sends from // 0-length sync sends. - args.GetReturnValue().Set(static_cast(msg_size) + 1); - return; + return msg_size + 1; } } } if (err == 0) { - AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(wrap); - SendWrap* req_wrap = new SendWrap(env, req_wrap_obj, have_callback); - req_wrap->msg_size = msg_size; - - err = req_wrap->Dispatch(uv_udp_send, - &wrap->handle_, - bufs_ptr, - count, - addr, - OnSend); + AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(this); + ReqWrap* req_wrap = listener()->CreateSendWrap(msg_size); + if (req_wrap == nullptr) return UV_ENOSYS; + + err = req_wrap->Dispatch( + uv_udp_send, + &handle_, + bufs_ptr, + count, + addr, + uv_udp_send_cb{[](uv_udp_send_t* req, int status) { + UDPWrap* self = ContainerOf(&UDPWrap::handle_, req->handle); + self->listener()->OnSendDone( + ReqWrap::from_req(req), status); + }}); if (err) delete req_wrap; } - args.GetReturnValue().Set(err); + return err; +} + + +ReqWrap* UDPWrap::CreateSendWrap(size_t msg_size) { + SendWrap* req_wrap = new SendWrap(env(), + current_send_req_wrap_, + current_send_has_callback_); + req_wrap->msg_size = msg_size; + return req_wrap; } @@ -552,31 +624,46 @@ void UDPWrap::Send6(const FunctionCallbackInfo& args) { } -void UDPWrap::RecvStart(const FunctionCallbackInfo& args) { - UDPWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, - args.Holder(), - args.GetReturnValue().Set(UV_EBADF)); - int err = uv_udp_recv_start(&wrap->handle_, OnAlloc, OnRecv); +AsyncWrap* UDPWrap::GetAsyncWrap() { + return this; +} + +int UDPWrap::GetPeerName(sockaddr* name, int* namelen) { + return uv_udp_getpeername(&handle_, name, namelen); +} + +int UDPWrap::GetSockName(sockaddr* name, int* namelen) { + return uv_udp_getsockname(&handle_, name, namelen); +} + +void UDPWrapBase::RecvStart(const FunctionCallbackInfo& args) { + UDPWrapBase* wrap = UDPWrapBase::FromObject(args.Holder()); + args.GetReturnValue().Set(wrap == nullptr ? UV_EBADF : wrap->RecvStart()); +} + +int UDPWrap::RecvStart() { + if (IsHandleClosing()) return UV_EBADF; + int err = uv_udp_recv_start(&handle_, OnAlloc, OnRecv); // UV_EALREADY means that the socket is already bound but that's okay if (err == UV_EALREADY) err = 0; - args.GetReturnValue().Set(err); + return err; } -void UDPWrap::RecvStop(const FunctionCallbackInfo& args) { - UDPWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, - args.Holder(), - args.GetReturnValue().Set(UV_EBADF)); - int r = uv_udp_recv_stop(&wrap->handle_); - args.GetReturnValue().Set(r); +void UDPWrapBase::RecvStop(const FunctionCallbackInfo& args) { + UDPWrapBase* wrap = UDPWrapBase::FromObject(args.Holder()); + args.GetReturnValue().Set(wrap == nullptr ? UV_EBADF : wrap->RecvStop()); +} + +int UDPWrap::RecvStop() { + if (IsHandleClosing()) return UV_EBADF; + return uv_udp_recv_stop(&handle_); } -void UDPWrap::OnSend(uv_udp_send_t* req, int status) { - std::unique_ptr req_wrap{static_cast(req->data)}; +void UDPWrap::OnSendDone(ReqWrap* req, int status) { + std::unique_ptr req_wrap{static_cast(req)}; if (req_wrap->have_callback()) { Environment* env = req_wrap->env(); HandleScope handle_scope(env->isolate()); @@ -593,19 +680,30 @@ void UDPWrap::OnSend(uv_udp_send_t* req, int status) { void UDPWrap::OnAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - UDPWrap* wrap = static_cast(handle->data); - *buf = wrap->env()->AllocateManaged(suggested_size).release(); + UDPWrap* wrap = ContainerOf(&UDPWrap::handle_, + reinterpret_cast(handle)); + *buf = wrap->listener()->OnAlloc(suggested_size); +} + +uv_buf_t UDPWrap::OnAlloc(size_t suggested_size) { + return env()->AllocateManaged(suggested_size).release(); } void UDPWrap::OnRecv(uv_udp_t* handle, ssize_t nread, - const uv_buf_t* buf_, - const struct sockaddr* addr, + const uv_buf_t* buf, + const sockaddr* addr, unsigned int flags) { - UDPWrap* wrap = static_cast(handle->data); - Environment* env = wrap->env(); + UDPWrap* wrap = ContainerOf(&UDPWrap::handle_, handle); + wrap->listener()->OnRecv(nread, *buf, addr, flags); +} - AllocatedBuffer buf(env, *buf_); +void UDPWrap::OnRecv(ssize_t nread, + const uv_buf_t& buf_, + const sockaddr* addr, + unsigned int flags) { + Environment* env = this->env(); + AllocatedBuffer buf(env, buf_); if (nread == 0 && addr == nullptr) { return; } @@ -613,23 +711,22 @@ void UDPWrap::OnRecv(uv_udp_t* handle, HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - Local wrap_obj = wrap->object(); Local argv[] = { Integer::New(env->isolate(), nread), - wrap_obj, + object(), Undefined(env->isolate()), Undefined(env->isolate()) }; if (nread < 0) { - wrap->MakeCallback(env->onmessage_string(), arraysize(argv), argv); + MakeCallback(env->onmessage_string(), arraysize(argv), argv); return; } buf.Resize(nread); argv[2] = buf.ToBuffer().ToLocalChecked(); argv[3] = AddressToJS(env, addr); - wrap->MakeCallback(env->onmessage_string(), arraysize(argv), argv); + MakeCallback(env->onmessage_string(), arraysize(argv), argv); } MaybeLocal UDPWrap::Instantiate(Environment* env, diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 2026dd1dee1..7afd9784b0a 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -25,14 +25,101 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "handle_wrap.h" +#include "req_wrap.h" #include "uv.h" #include "v8.h" namespace node { -class Environment; +class UDPWrapBase; -class UDPWrap: public HandleWrap { +// A listener that can be attached to an `UDPWrapBase` object and generally +// manages its I/O activity. This is similar to `StreamListener`. +class UDPListener { + public: + virtual ~UDPListener(); + + // Called right before data is received from the socket. Must return a + // buffer suitable for reading data into, that is then passed to OnRecv. + virtual uv_buf_t OnAlloc(size_t suggested_size) = 0; + + // Called right after data is received from the socket, and includes + // information about the source address. If `nread` is negative, an error + // has occurred, and it represents a libuv error code. + virtual void OnRecv(ssize_t nread, + const uv_buf_t& buf, + const sockaddr* addr, + unsigned int flags) = 0; + + // Called when an asynchronous request for writing data is created. + // The `msg_size` value contains the total size of the data to be sent, + // but may be ignored by the implementation of this Method. + // The return value is later passed to OnSendDone. + virtual ReqWrap* CreateSendWrap(size_t msg_size) = 0; + + // Called when an asynchronous request for writing data has finished. + // If status is negative, an error has occurred, and it represents a libuv + // error code. + virtual void OnSendDone(ReqWrap* wrap, int status) = 0; + + // Optional callback that is called after the socket has been bound. + virtual void OnAfterBind() {} + + inline UDPWrapBase* udp() const { return wrap_; } + + protected: + UDPWrapBase* wrap_ = nullptr; + + friend class UDPWrapBase; +}; + +class UDPWrapBase { + public: + // While UDPWrapBase itself does not extend from HandleWrap, classes + // derived from it will (like UDPWrap) + enum InternalFields { + kUDPWrapBaseField = HandleWrap::kInternalFieldCount, + kInternalFieldCount + }; + virtual ~UDPWrapBase(); + + // Start emitting OnAlloc() + OnRecv() events on the listener. + virtual int RecvStart() = 0; + + // Stop emitting OnAlloc() + OnRecv() events on the listener. + virtual int RecvStop() = 0; + + // Send a chunk of data over this socket. This may call CreateSendWrap() + // on the listener if an async transmission is necessary. + virtual ssize_t Send(uv_buf_t* bufs, + size_t nbufs, + const sockaddr* addr) = 0; + + // Stores the sockaddr for the peer in `name`. + virtual int GetPeerName(sockaddr* name, int* namelen) = 0; + + // Stores the sockaddr for the local socket in `name`. + virtual int GetSockName(sockaddr* name, int* namelen) = 0; + + // Returns an AsyncWrap object with the same lifetime as this object. + virtual AsyncWrap* GetAsyncWrap() = 0; + + void set_listener(UDPListener* listener); + UDPListener* listener() const; + + static UDPWrapBase* FromObject(v8::Local obj); + + static void RecvStart(const v8::FunctionCallbackInfo& args); + static void RecvStop(const v8::FunctionCallbackInfo& args); + static void AddMethods(Environment* env, v8::Local t); + + private: + UDPListener* listener_ = nullptr; +}; + +class UDPWrap final : public HandleWrap, + public UDPWrapBase, + public UDPListener { public: enum SocketType { SOCKET @@ -51,8 +138,6 @@ class UDPWrap: public HandleWrap { static void Connect6(const v8::FunctionCallbackInfo& args); static void Send6(const v8::FunctionCallbackInfo& args); static void Disconnect(const v8::FunctionCallbackInfo& args); - static void RecvStart(const v8::FunctionCallbackInfo& args); - static void RecvStop(const v8::FunctionCallbackInfo& args); static void AddMembership(const v8::FunctionCallbackInfo& args); static void DropMembership(const v8::FunctionCallbackInfo& args); static void AddSourceSpecificMembership( @@ -68,6 +153,25 @@ class UDPWrap: public HandleWrap { static void SetTTL(const v8::FunctionCallbackInfo& args); static void BufferSize(const v8::FunctionCallbackInfo& args); + // UDPListener implementation + uv_buf_t OnAlloc(size_t suggested_size) override; + void OnRecv(ssize_t nread, + const uv_buf_t& buf, + const sockaddr* addr, + unsigned int flags) override; + ReqWrap* CreateSendWrap(size_t msg_size) override; + void OnSendDone(ReqWrap* wrap, int status) override; + + // UDPWrapBase implementation + int RecvStart() override; + int RecvStop() override; + ssize_t Send(uv_buf_t* bufs, + size_t nbufs, + const sockaddr* addr) override; + int GetPeerName(sockaddr* name, int* namelen) override; + int GetSockName(sockaddr* name, int* namelen) override; + AsyncWrap* GetAsyncWrap() override; + static v8::MaybeLocal Instantiate(Environment* env, AsyncWrap* parent, SocketType type); @@ -99,7 +203,6 @@ class UDPWrap: public HandleWrap { static void OnAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); - static void OnSend(uv_udp_send_t* req, int status); static void OnRecv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, @@ -107,6 +210,9 @@ class UDPWrap: public HandleWrap { unsigned int flags); uv_udp_t handle_; + + bool current_send_has_callback_; + v8::Local current_send_req_wrap_; }; } // namespace node From 8429295c5b2bf3eb52a5b57eaceaffc3158a0c4a Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 1 Mar 2020 19:01:24 +0100 Subject: [PATCH 115/192] stream: eos make const state const writable & readable is based on type and is not actual state, treat them as such. PR-URL: https://github.com/nodejs/node/pull/32031 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca --- lib/internal/streams/end-of-stream.js | 18 +++++++----------- test/parallel/test-stream-finished.js | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/internal/streams/end-of-stream.js b/lib/internal/streams/end-of-stream.js index 9f1e0ba0dc9..aad43823f9a 100644 --- a/lib/internal/streams/end-of-stream.js +++ b/lib/internal/streams/end-of-stream.js @@ -56,9 +56,9 @@ function eos(stream, opts, callback) { callback = once(callback); - let readable = opts.readable || + const readable = opts.readable || (opts.readable !== false && isReadable(stream)); - let writable = opts.writable || + const writable = opts.writable || (opts.writable !== false && isWritable(stream)); const wState = stream._writableState; @@ -71,17 +71,15 @@ function eos(stream, opts, callback) { let writableFinished = stream.writableFinished || (wState && wState.finished); const onfinish = () => { - writable = false; writableFinished = true; - if (!readable) callback.call(stream); + if (!readable || readableEnded) callback.call(stream); }; let readableEnded = stream.readableEnded || (rState && rState.endEmitted); const onend = () => { - readable = false; readableEnded = true; - if (!writable) callback.call(stream); + if (!writable || writableFinished) callback.call(stream); }; const onerror = (err) => { @@ -89,17 +87,15 @@ function eos(stream, opts, callback) { }; const onclose = () => { - let err; if (readable && !readableEnded) { if (!isReadableEnded(stream)) - err = new ERR_STREAM_PREMATURE_CLOSE(); - return callback.call(stream, err); + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); } if (writable && !writableFinished) { if (!isWritableFinished(stream)) - err = new ERR_STREAM_PREMATURE_CLOSE(); - return callback.call(stream, err); + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); } + callback.call(stream); }; const onrequest = () => { diff --git a/test/parallel/test-stream-finished.js b/test/parallel/test-stream-finished.js index 46a5f939134..ab35d402e31 100644 --- a/test/parallel/test-stream-finished.js +++ b/test/parallel/test-stream-finished.js @@ -181,7 +181,7 @@ const { promisify } = require('util'); const streamLike = new EE(); streamLike.readableEnded = true; streamLike.readable = true; - finished(streamLike, common.mustCall); + finished(streamLike, common.mustCall()); streamLike.emit('close'); } From de6cbd0e374e3d9209ddf56d1041b83b41efcbe6 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Tue, 3 Mar 2020 15:02:02 -0500 Subject: [PATCH 116/192] build: fix building with ninja The ninja build places objects in a different directory. Co-authored-by: Gabriel Schulhof Signed-off-by: Richard Lau PR-URL: https://github.com/nodejs/node/pull/32071 Reviewed-By: Matheus Marchini Reviewed-By: Gabriel Schulhof Reviewed-By: Sam Roberts Reviewed-By: Shelley Vohr Reviewed-By: Myles Borins Reviewed-By: Jiawen Geng --- node.gyp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/node.gyp b/node.gyp index c6858584cd0..088e638115e 100644 --- a/node.gyp +++ b/node.gyp @@ -251,6 +251,11 @@ 'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)', 'mkcodecache_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mkcodecache<(EXECUTABLE_SUFFIX)', 'conditions': [ + ['GENERATOR == "ninja"', { + 'node_text_start_object_path': 'src/large_pages/node_text_start.node_text_start.o' + }, { + 'node_text_start_object_path': 'node_text_start/src/large_pages/node_text_start.o' + }], [ 'node_shared=="true"', { 'node_target_type%': 'shared_library', 'conditions': [ @@ -516,7 +521,7 @@ 'target_arch=="x64"', { 'dependencies': [ 'node_text_start' ], 'ldflags+': [ - '<(PRODUCT_DIR)/obj.target/node_text_start/src/large_pages/node_text_start.o' + '<(obj_dir)/<(node_text_start_object_path)' ] }], ], From bf7409e9740ce602b09e088aac70b7c817f5d27c Mon Sep 17 00:00:00 2001 From: ConorDavenport Date: Fri, 14 Feb 2020 12:54:11 +0000 Subject: [PATCH 117/192] doc: improve doc/markdown file organization coherence * Updated cpp style guide file name and location and fixed links to this file. * Updated collaborator guide file name and location and fixed links to this file. * Updated documentation style guide file name and location and updated links referencing the file. * Moved files to appropriate location and updated naming style for some of them. Fixes: https://github.com/nodejs/node/issues/31741 PR-URL: https://github.com/nodejs/node/pull/31792 Reviewed-By: James M Snell Reviewed-By: Sam Roberts --- CONTRIBUTING.md | 4 ++-- GOVERNANCE.md | 6 +++--- Makefile | 4 ++-- README.md | 2 +- benchmark/README.md | 8 ++++---- COLLABORATOR_GUIDE.md => doc/guides/collaborator-guide.md | 0 doc/guides/contributing/{coc.md => code-of-conduct.md} | 0 doc/guides/contributing/pull-requests.md | 6 +++--- CPP_STYLE_GUIDE.md => doc/guides/cpp-style-guide.md | 0 doc/{STYLE_GUIDE.md => guides/doc-style-guide.md} | 0 doc/guides/{internal/readme.md => internal-api.md} | 0 .../{updating-root-certs.md => maintaining-root-certs.md} | 2 +- doc/{ => guides}/offboarding.md | 0 doc/{ => guides}/onboarding-extras.md | 0 doc/{ => guides}/releases.md | 0 ...ity_release_process.md => security-release-process.md} | 0 .../guides}/writing-and-running-benchmarks.md | 0 doc/onboarding.md => onboarding.md | 0 src/README.md | 2 +- 19 files changed, 17 insertions(+), 17 deletions(-) rename COLLABORATOR_GUIDE.md => doc/guides/collaborator-guide.md (100%) rename doc/guides/contributing/{coc.md => code-of-conduct.md} (100%) rename CPP_STYLE_GUIDE.md => doc/guides/cpp-style-guide.md (100%) rename doc/{STYLE_GUIDE.md => guides/doc-style-guide.md} (100%) rename doc/guides/{internal/readme.md => internal-api.md} (100%) rename doc/guides/{updating-root-certs.md => maintaining-root-certs.md} (99%) rename doc/{ => guides}/offboarding.md (100%) rename doc/{ => guides}/onboarding-extras.md (100%) rename doc/{ => guides}/releases.md (100%) rename doc/guides/{security_release_process.md => security-release-process.md} (100%) rename {benchmark => doc/guides}/writing-and-running-benchmarks.md (100%) rename doc/onboarding.md => onboarding.md (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9d1f2cef60..29700978fb7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,13 +5,13 @@ * [Pull Requests](#pull-requests) * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) -## [Code of Conduct](./doc/guides/contributing/coc.md) +## [Code of Conduct](./doc/guides/contributing/code-of-conduct.md) The Node.js project has a [Code of Conduct](https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md) to which all contributors must adhere. -See [details on our policy on Code of Conduct](./doc/guides/contributing/coc.md). +See [details on our policy on Code of Conduct](./doc/guides/contributing/code-of-conduct.md). ## [Issues](./doc/guides/contributing/issues.md) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index d7cb6e321e1..5048a700340 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -7,7 +7,7 @@ * [Technical Steering Committee](#technical-steering-committee) * [TSC Meetings](#tsc-meetings) * [Collaborator Nominations](#collaborator-nominations) - * [Onboarding](#onboarding) + * [Onboarding](#./onboarding) * [Consensus Seeking Process](#consensus-seeking-process) @@ -39,7 +39,7 @@ result in Collaborators removing their opposition. See: * [List of Collaborators](./README.md#current-project-team-members) -* [A guide for Collaborators](./COLLABORATOR_GUIDE.md) +* [A guide for Collaborators](./doc/guides/collaborator-guide.md) ### Collaborator Activities @@ -148,7 +148,7 @@ nomination. ### Onboarding After the nomination passes, a TSC member onboards the new Collaborator. See -[the onboarding guide](./doc/onboarding.md) for details of the onboarding +[the onboarding guide](./onboarding.md) for details of the onboarding process. ## Consensus Seeking Process diff --git a/Makefile b/Makefile index d1705fdc49d..c716f88e5ff 100644 --- a/Makefile +++ b/Makefile @@ -924,12 +924,12 @@ endif .PHONY: release-only release-only: check-xz @if [ "$(DISTTYPE)" = "release" ] && `grep -q REPLACEME doc/api/*.md`; then \ - echo 'Please update REPLACEME in Added: tags in doc/api/*.md (See doc/releases.md)' ; \ + echo 'Please update REPLACEME in Added: tags in doc/api/*.md (See doc/guides/releases.md)' ; \ exit 1 ; \ fi @if [ "$(DISTTYPE)" = "release" ] && \ `grep -q DEP...X doc/api/deprecations.md`; then \ - echo 'Please update DEP...X in doc/api/deprecations.md (See doc/releases.md)' ; \ + echo 'Please update DEP...X in doc/api/deprecations.md (See doc/guides/releases.md)' ; \ exit 1 ; \ fi @if [ "$(shell git status --porcelain | egrep -v '^\?\? ')" = "" ]; then \ diff --git a/README.md b/README.md index 657fc478a37..0576cd5132e 100644 --- a/README.md +++ b/README.md @@ -529,7 +529,7 @@ For information about the governance of the Node.js project, see * [whitlockjc](https://github.com/whitlockjc) - **Jeremy Whitlock** <jwhitlock@apache.org> -Collaborators follow the [COLLABORATOR_GUIDE.md](./COLLABORATOR_GUIDE.md) in +Collaborators follow the [Collaborator Guide](./doc/guides/collaborator-guide.md) in maintaining the Node.js project. ### Release Keys diff --git a/benchmark/README.md b/benchmark/README.md index c5fdad09347..cb7c1506eb6 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -5,7 +5,7 @@ of different Node.js implementations and different ways of writing JavaScript run by the built-in JavaScript engine. For a detailed guide on how to write and run benchmarks in this -directory, see [the guide on benchmarks](writing-and-running-benchmarks.md). +directory, see [the guide on benchmarks](../doc/guides/writing-and-running-benchmarks.md). ## Table of Contents @@ -76,17 +76,17 @@ writing benchmarks. ### `createBenchmark(fn, configs[, options])` -See [the guide on writing benchmarks](writing-and-running-benchmarks.md#basics-of-a-benchmark). +See [the guide on writing benchmarks](../doc/guides/contributing/writing-and-running-benchmarks.md#basics-of-a-benchmark). ### `default_http_benchmarker` The default benchmarker used to run HTTP benchmarks. -See [the guide on writing HTTP benchmarks](writing-and-running-benchmarks.md#creating-an-http-benchmark). +See [the guide on writing HTTP benchmarks](../doc/guides/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark). ### `PORT` The default port used to run HTTP benchmarks. -See [the guide on writing HTTP benchmarks](writing-and-running-benchmarks.md#creating-an-http-benchmark). +See [the guide on writing HTTP benchmarks](../doc/guides/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark). ### `sendResult(data)` diff --git a/COLLABORATOR_GUIDE.md b/doc/guides/collaborator-guide.md similarity index 100% rename from COLLABORATOR_GUIDE.md rename to doc/guides/collaborator-guide.md diff --git a/doc/guides/contributing/coc.md b/doc/guides/contributing/code-of-conduct.md similarity index 100% rename from doc/guides/contributing/coc.md rename to doc/guides/contributing/code-of-conduct.md diff --git a/doc/guides/contributing/pull-requests.md b/doc/guides/contributing/pull-requests.md index 7f4ab4e83e0..39b84bc34f1 100644 --- a/doc/guides/contributing/pull-requests.md +++ b/doc/guides/contributing/pull-requests.md @@ -115,13 +115,13 @@ If you are modifying code, please be sure to run `make lint` from time to time to ensure that the changes follow the Node.js code style guide. Any documentation you write (including code comments and API documentation) -should follow the [Style Guide](../../STYLE_GUIDE.md). Code samples included -in the API docs will also be checked when running `make lint` (or +should follow the [Style Guide](../doc-style-guide.md). Code samples +included in the API docs will also be checked when running `make lint` (or `vcbuild.bat lint` on Windows). If you are adding to or deprecating an API, use `REPLACEME` for the version number in the documentation YAML. For contributing C++ code, you may want to look at the -[C++ Style Guide](../../../CPP_STYLE_GUIDE.md), as well as the +[C++ Style Guide](../../cpp-style-guide.md), as well as the [README of `src/`](../../../src/README.md) for an overview over Node.js C++ internals. diff --git a/CPP_STYLE_GUIDE.md b/doc/guides/cpp-style-guide.md similarity index 100% rename from CPP_STYLE_GUIDE.md rename to doc/guides/cpp-style-guide.md diff --git a/doc/STYLE_GUIDE.md b/doc/guides/doc-style-guide.md similarity index 100% rename from doc/STYLE_GUIDE.md rename to doc/guides/doc-style-guide.md diff --git a/doc/guides/internal/readme.md b/doc/guides/internal-api.md similarity index 100% rename from doc/guides/internal/readme.md rename to doc/guides/internal-api.md diff --git a/doc/guides/updating-root-certs.md b/doc/guides/maintaining-root-certs.md similarity index 99% rename from doc/guides/updating-root-certs.md rename to doc/guides/maintaining-root-certs.md index 41c83e5898b..d26bdad943a 100644 --- a/doc/guides/updating-root-certs.md +++ b/doc/guides/maintaining-root-certs.md @@ -1,4 +1,4 @@ -# Updating the Root Certificates +# Maintaining the Root Certificates Node.js contains a compiled-in set of root certificates used as trust anchors for TLS certificate validation. diff --git a/doc/offboarding.md b/doc/guides/offboarding.md similarity index 100% rename from doc/offboarding.md rename to doc/guides/offboarding.md diff --git a/doc/onboarding-extras.md b/doc/guides/onboarding-extras.md similarity index 100% rename from doc/onboarding-extras.md rename to doc/guides/onboarding-extras.md diff --git a/doc/releases.md b/doc/guides/releases.md similarity index 100% rename from doc/releases.md rename to doc/guides/releases.md diff --git a/doc/guides/security_release_process.md b/doc/guides/security-release-process.md similarity index 100% rename from doc/guides/security_release_process.md rename to doc/guides/security-release-process.md diff --git a/benchmark/writing-and-running-benchmarks.md b/doc/guides/writing-and-running-benchmarks.md similarity index 100% rename from benchmark/writing-and-running-benchmarks.md rename to doc/guides/writing-and-running-benchmarks.md diff --git a/doc/onboarding.md b/onboarding.md similarity index 100% rename from doc/onboarding.md rename to onboarding.md diff --git a/src/README.md b/src/README.md index 40790b278ac..2e59c51c3c3 100644 --- a/src/README.md +++ b/src/README.md @@ -903,7 +903,7 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [`v8.h` in Node.js master]: https://github.com/nodejs/node/blob/master/deps/v8/include/v8.h [`v8.h` in V8 master]: https://github.com/v8/v8/blob/master/include/v8.h [`vm` module]: https://nodejs.org/api/vm.html -[C++ coding style]: ../CPP_STYLE_GUIDE.md +[C++ coding style]: ../doc/guides/cpp-style-guide.md [Callback scopes]: #callback-scopes [JavaScript value handles]: #js-handles [N-API]: https://nodejs.org/api/n-api.html From 37287d3f5819daabade7b8ade81c4265bd596edf Mon Sep 17 00:00:00 2001 From: Harshitha KP Date: Thu, 16 Jan 2020 07:25:22 -0500 Subject: [PATCH 118/192] doc: visibility of Worker threads cli options Fixes: https://github.com/nodejs/node/issues/28518 PR-URL: https://github.com/nodejs/node/pull/31380 Fixes: https://github.com/nodejs/node/issues/28518 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- doc/api/worker_threads.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index b67d563fa6d..e773949b49d 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -55,6 +55,10 @@ correlation between tasks and their outcomes. See ["Using `AsyncResource` for a `Worker` thread pool"][async-resource-worker-pool] in the `async_hooks` documentation for an example implementation. +Worker threads inherit non-process-specific options by default. Refer to +[`Worker constructor options`][] to know how to customize worker thread options, +specifically `argv` and `execArgv` options. + ## `worker.isMainThread` This class is used to create asynchronous state within callbacks and promise @@ -911,7 +911,7 @@ from each other. It is safe to instantiate this class multiple times. ### `new AsyncLocalStorage()` Creates a new instance of `AsyncLocalStorage`. Store is only provided within a @@ -919,7 +919,7 @@ Creates a new instance of `AsyncLocalStorage`. Store is only provided within a ### `asyncLocalStorage.disable()` This method disables the instance of `AsyncLocalStorage`. All subsequent calls @@ -940,7 +940,7 @@ in the current process. ### `asyncLocalStorage.getStore()` * Returns: {any} @@ -952,7 +952,7 @@ return `undefined`. ### `asyncLocalStorage.run(store, callback[, ...args])` * `store` {any} @@ -987,7 +987,7 @@ asyncLocalStorage.getStore(); // Returns undefined ### `asyncLocalStorage.exit(callback[, ...args])` * `callback` {Function} @@ -1019,7 +1019,7 @@ asyncLocalStorage.run('store value', () => { ### `asyncLocalStorage.runSyncAndReturn(store, callback[, ...args])` * `store` {any} @@ -1054,7 +1054,7 @@ try { ### `asyncLocalStorage.exitSyncAndReturn(callback[, ...args])` * `callback` {Function} diff --git a/doc/api/errors.md b/doc/api/errors.md index 11d0036386c..c244bfd7f00 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1864,7 +1864,7 @@ The context must be a `SecureContext`. ### `ERR_TLS_INVALID_STATE` The TLS socket must be connected and securily established. Ensure the 'secure' diff --git a/doc/api/stream.md b/doc/api/stream.md index 7bc1874c803..69be69b5516 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -1572,7 +1572,7 @@ const cleanup = finished(rs, (err) => { diff --git a/doc/api/tls.md b/doc/api/tls.md index 3341e6e9ea5..55dbf3b8d42 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -1096,7 +1096,7 @@ for more information. ### `tlsSocket.exportKeyingMaterial(length, label[, context])` * `length` {number} number of bytes to retrieve from keying material diff --git a/doc/api/vm.md b/doc/api/vm.md index ed676414471..91d16be3986 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -298,7 +298,7 @@ console.log(globalVar); ## `vm.measureMemory([options])` > Stability: 1 - Experimental diff --git a/doc/changelogs/CHANGELOG_V13.md b/doc/changelogs/CHANGELOG_V13.md index 3f255fb09d2..92310d28754 100644 --- a/doc/changelogs/CHANGELOG_V13.md +++ b/doc/changelogs/CHANGELOG_V13.md @@ -9,6 +9,7 @@ +13.10.0
13.9.0
13.8.0
13.7.0
@@ -39,6 +40,113 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-03-04, Version 13.10.0 (Current), @codebytere + +### Notable Changes + +* **async_hooks** + * introduce async-context API (vdeturckheim) [#26540](https://github.com/nodejs/node/pull/26540) +* **stream** + * support passing generator functions into pipeline() (Robert Nagy) [#31223](https://github.com/nodejs/node/pull/31223) +* **tls** + * expose SSL\_export\_keying\_material (simon) [#31814](https://github.com/nodejs/node/pull/31814) +* **vm** + * implement vm.measureMemory() for per-context memory measurement (Joyee Cheung) [#31824](https://github.com/nodejs/node/pull/31824) + +### Commits + +* [[`f71fc9044a`](https://github.com/nodejs/node/commit/f71fc9044a)] - **async_hooks**: add store arg in AsyncLocalStorage (Andrey Pechkurov) [#31930](https://github.com/nodejs/node/pull/31930) +* [[`6af9e7e0c3`](https://github.com/nodejs/node/commit/6af9e7e0c3)] - **async_hooks**: executionAsyncResource matches in hooks (Gerhard Stoebich) [#31821](https://github.com/nodejs/node/pull/31821) +* [[`877ab97286`](https://github.com/nodejs/node/commit/877ab97286)] - **(SEMVER-MINOR)** **async_hooks**: introduce async-context API (vdeturckheim) [#26540](https://github.com/nodejs/node/pull/26540) +* [[`9a41ced0d1`](https://github.com/nodejs/node/commit/9a41ced0d1)] - **build**: only lint markdown files that have changed (POSIX-only) (Rich Trott) [#31923](https://github.com/nodejs/node/pull/31923) +* [[`ca4407105e`](https://github.com/nodejs/node/commit/ca4407105e)] - **build**: add missing comma in node.gyp (cjihrig) [#31959](https://github.com/nodejs/node/pull/31959) +* [[`4dffd0437d`](https://github.com/nodejs/node/commit/4dffd0437d)] - **cli**: --perf-prof only works on Linux (Shelley Vohr) [#31892](https://github.com/nodejs/node/pull/31892) +* [[`4d05508aa8`](https://github.com/nodejs/node/commit/4d05508aa8)] - **crypto**: turn impossible DH errors into assertions (Tobias Nießen) [#31934](https://github.com/nodejs/node/pull/31934) +* [[`d0e94fc77e`](https://github.com/nodejs/node/commit/d0e94fc77e)] - **crypto**: fix ieee-p1363 for createVerify (Tobias Nießen) [#31876](https://github.com/nodejs/node/pull/31876) +* [[`fbaab7d854`](https://github.com/nodejs/node/commit/fbaab7d854)] - **deps**: openssl: cherry-pick 4dcb150ea30f (Adam Majer) [#32002](https://github.com/nodejs/node/pull/32002) +* [[`e6125cd53b`](https://github.com/nodejs/node/commit/e6125cd53b)] - **deps**: V8: backport f7771e5b0cc4 (Matheus Marchini) [#31957](https://github.com/nodejs/node/pull/31957) +* [[`c27f0d10c4`](https://github.com/nodejs/node/commit/c27f0d10c4)] - **deps**: update zlib to upstream d7f3ca9 (Sam Roberts) [#31800](https://github.com/nodejs/node/pull/31800) +* [[`b30a6981d3`](https://github.com/nodejs/node/commit/b30a6981d3)] - **deps**: move zlib maintenance info to guides (Sam Roberts) [#31800](https://github.com/nodejs/node/pull/31800) +* [[`cd30dbb0d6`](https://github.com/nodejs/node/commit/cd30dbb0d6)] - **doc**: revise --zero-fill-buffers text in buffer.md (Rich Trott) [#32019](https://github.com/nodejs/node/pull/32019) +* [[`166579f84b`](https://github.com/nodejs/node/commit/166579f84b)] - **doc**: add link to sem-ver info (unknown) [#31985](https://github.com/nodejs/node/pull/31985) +* [[`e3258fd148`](https://github.com/nodejs/node/commit/e3258fd148)] - **doc**: update zlib doc (James M Snell) [#31665](https://github.com/nodejs/node/pull/31665) +* [[`8516602ba0`](https://github.com/nodejs/node/commit/8516602ba0)] - **doc**: clarify http2.connect authority details (James M Snell) [#31828](https://github.com/nodejs/node/pull/31828) +* [[`c5acf0a13b`](https://github.com/nodejs/node/commit/c5acf0a13b)] - **doc**: updated YAML version representation in readline.md (Rich Trott) [#31924](https://github.com/nodejs/node/pull/31924) +* [[`4c6343fdea`](https://github.com/nodejs/node/commit/4c6343fdea)] - **doc**: describe how to update zlib (Sam Roberts) [#31800](https://github.com/nodejs/node/pull/31800) +* [[`a46839279f`](https://github.com/nodejs/node/commit/a46839279f)] - **doc**: update releases guide re pushing tags (Myles Borins) [#31855](https://github.com/nodejs/node/pull/31855) +* [[`15cc9b0126`](https://github.com/nodejs/node/commit/15cc9b0126)] - **doc**: update assert.rejects() docs with a validation function example (Eric Eastwood) [#31271](https://github.com/nodejs/node/pull/31271) +* [[`2046652b4e`](https://github.com/nodejs/node/commit/2046652b4e)] - **doc**: fix anchor for ERR\_TLS\_INVALID\_CONTEXT (Tobias Nießen) [#31915](https://github.com/nodejs/node/pull/31915) +* [[`091b4bfe2d`](https://github.com/nodejs/node/commit/091b4bfe2d)] - **doc**: add note about ssh key to releases (Shelley Vohr) [#31856](https://github.com/nodejs/node/pull/31856) +* [[`3438937a37`](https://github.com/nodejs/node/commit/3438937a37)] - **doc**: fix notable changes for v13.9.0 (Shelley Vohr) [#31857](https://github.com/nodejs/node/pull/31857) +* [[`672f76d6bd`](https://github.com/nodejs/node/commit/672f76d6bd)] - **doc**: reword possessive form of Node.js in adding-new-napi-api.md (Rich Trott) [#31748](https://github.com/nodejs/node/pull/31748) +* [[`3eaf37767e`](https://github.com/nodejs/node/commit/3eaf37767e)] - **doc**: reword possessive form of Node.js in http.md (Rich Trott) [#31748](https://github.com/nodejs/node/pull/31748) +* [[`cb210e6b16`](https://github.com/nodejs/node/commit/cb210e6b16)] - **doc**: reword possessive form of Node.js in process.md (Rich Trott) [#31748](https://github.com/nodejs/node/pull/31748) +* [[`3969af43b4`](https://github.com/nodejs/node/commit/3969af43b4)] - **doc**: reword possessive form of Node.js in debugger.md (Rich Trott) [#31748](https://github.com/nodejs/node/pull/31748) +* [[`f9526057b3`](https://github.com/nodejs/node/commit/f9526057b3)] - **doc**: move gireeshpunathil to TSC emeritus (Gireesh Punathil) [#31770](https://github.com/nodejs/node/pull/31770) +* [[`b07175853f`](https://github.com/nodejs/node/commit/b07175853f)] - **doc**: pronouns for @Fishrock123 (Jeremiah Senkpiel) [#31725](https://github.com/nodejs/node/pull/31725) +* [[`7f4d6ee8ea`](https://github.com/nodejs/node/commit/7f4d6ee8ea)] - **doc**: move @Fishrock123 to TSC Emeriti (Jeremiah Senkpiel) [#31725](https://github.com/nodejs/node/pull/31725) +* [[`b177bba555`](https://github.com/nodejs/node/commit/b177bba555)] - **doc**: move @Fishrock123 to a previous releaser (Jeremiah Senkpiel) [#31725](https://github.com/nodejs/node/pull/31725) +* [[`9e4aad705f`](https://github.com/nodejs/node/commit/9e4aad705f)] - **doc**: fix typos in doc/api/https.md (Jeff) [#31793](https://github.com/nodejs/node/pull/31793) +* [[`eb2dce8342`](https://github.com/nodejs/node/commit/eb2dce8342)] - **doc**: claim ABI version 82 for Electron 10 (Samuel Attard) [#31778](https://github.com/nodejs/node/pull/31778) +* [[`db291aaf06`](https://github.com/nodejs/node/commit/db291aaf06)] - **doc**: guide - using valgrind to debug memory leaks (Michael Dawson) [#31501](https://github.com/nodejs/node/pull/31501) +* [[`aa16d80c05`](https://github.com/nodejs/node/commit/aa16d80c05)] - **doc,crypto**: re-document oaepLabel option (Ben Noordhuis) [#31825](https://github.com/nodejs/node/pull/31825) +* [[`9079bb42ea`](https://github.com/nodejs/node/commit/9079bb42ea)] - **http2**: make compat finished match http/1 (Robert Nagy) [#24347](https://github.com/nodejs/node/pull/24347) +* [[`3bd8feac0c`](https://github.com/nodejs/node/commit/3bd8feac0c)] - **meta**: move aqrln to emeritus (Rich Trott) [#31997](https://github.com/nodejs/node/pull/31997) +* [[`c801045fcd`](https://github.com/nodejs/node/commit/c801045fcd)] - **meta**: move jbergstroem to emeritus (Rich Trott) [#31996](https://github.com/nodejs/node/pull/31996) +* [[`ded3890bec`](https://github.com/nodejs/node/commit/ded3890bec)] - **meta**: move maclover7 to Emeritus (Rich Trott) [#31994](https://github.com/nodejs/node/pull/31994) +* [[`91ce69a554`](https://github.com/nodejs/node/commit/91ce69a554)] - **meta**: move Glen Keane to Collaborator Emeritus (Rich Trott) [#31993](https://github.com/nodejs/node/pull/31993) +* [[`b74c40eda6`](https://github.com/nodejs/node/commit/b74c40eda6)] - **meta**: move not-an-aardvark to emeritus (Rich Trott) [#31928](https://github.com/nodejs/node/pull/31928) +* [[`61a0d8b6cd`](https://github.com/nodejs/node/commit/61a0d8b6cd)] - **meta**: move julianduque to emeritus (Rich Trott) [#31863](https://github.com/nodejs/node/pull/31863) +* [[`94a471a422`](https://github.com/nodejs/node/commit/94a471a422)] - **meta**: move eljefedelrodeodeljefe to emeritus (Rich Trott) [#31735](https://github.com/nodejs/node/pull/31735) +* [[`9e3e6763fa`](https://github.com/nodejs/node/commit/9e3e6763fa)] - **module**: port source map sort logic from chromium (bcoe) [#31927](https://github.com/nodejs/node/pull/31927) +* [[`b9f3bfe6c8`](https://github.com/nodejs/node/commit/b9f3bfe6c8)] - **module**: disable conditional exports, self resolve warnings (Guy Bedford) [#31845](https://github.com/nodejs/node/pull/31845) +* [[`bbb6cc733c`](https://github.com/nodejs/node/commit/bbb6cc733c)] - **module**: package "exports" error refinements (Guy Bedford) [#31625](https://github.com/nodejs/node/pull/31625) +* [[`6adbfac9b0`](https://github.com/nodejs/node/commit/6adbfac9b0)] - **repl**: eager-evaluate input in parens (Shelley Vohr) [#31943](https://github.com/nodejs/node/pull/31943) +* [[`6a35b0d102`](https://github.com/nodejs/node/commit/6a35b0d102)] - **src**: don't run bootstrapper in CreateEnvironment (Shelley Vohr) [#31910](https://github.com/nodejs/node/pull/31910) +* [[`3497370d66`](https://github.com/nodejs/node/commit/3497370d66)] - **src**: move InternalCallbackScope to StartExecution (Shelley Vohr) [#31944](https://github.com/nodejs/node/pull/31944) +* [[`f62967c827`](https://github.com/nodejs/node/commit/f62967c827)] - **src**: enable `StreamPipe` for generic `StreamBase`s (Anna Henningsen) [#31869](https://github.com/nodejs/node/pull/31869) +* [[`776f379124`](https://github.com/nodejs/node/commit/776f379124)] - **src**: include large pages source unconditionally (Gabriel Schulhof) [#31904](https://github.com/nodejs/node/pull/31904) +* [[`9f68e14052`](https://github.com/nodejs/node/commit/9f68e14052)] - **src**: elevate v8 namespaces (Harshitha KP) [#31901](https://github.com/nodejs/node/pull/31901) +* [[`8fa6373e62`](https://github.com/nodejs/node/commit/8fa6373e62)] - **src**: allow unique\_ptrs with custom deleter in memory tracker (Anna Henningsen) [#31870](https://github.com/nodejs/node/pull/31870) +* [[`88ccb444e3`](https://github.com/nodejs/node/commit/88ccb444e3)] - **src**: move BaseObject subclass dtors/ctors out of node\_crypto.h (Anna Henningsen) [#31872](https://github.com/nodejs/node/pull/31872) +* [[`98d262e5f3`](https://github.com/nodejs/node/commit/98d262e5f3)] - **src**: inform callback scopes about exceptions in HTTP parser (Anna Henningsen) [#31801](https://github.com/nodejs/node/pull/31801) +* [[`57302f866e`](https://github.com/nodejs/node/commit/57302f866e)] - **src**: prefer 3-argument Array::New() (Anna Henningsen) [#31775](https://github.com/nodejs/node/pull/31775) +* [[`8a2b62e4cd`](https://github.com/nodejs/node/commit/8a2b62e4cd)] - **stream**: ensure pipeline always destroys streams (Robert Nagy) [#31940](https://github.com/nodejs/node/pull/31940) +* [[`313ecaabe5`](https://github.com/nodejs/node/commit/313ecaabe5)] - **stream**: fix broken pipeline error propagation (Robert Nagy) [#31835](https://github.com/nodejs/node/pull/31835) +* [[`8ad64b8e53`](https://github.com/nodejs/node/commit/8ad64b8e53)] - **(SEMVER-MINOR)** **stream**: support passing generator functions into pipeline() (Robert Nagy) [#31223](https://github.com/nodejs/node/pull/31223) +* [[`d0a00711f8`](https://github.com/nodejs/node/commit/d0a00711f8)] - **stream**: invoke buffered write callbacks on error (Robert Nagy) [#30596](https://github.com/nodejs/node/pull/30596) +* [[`1bca7b6c70`](https://github.com/nodejs/node/commit/1bca7b6c70)] - **test**: move test-inspector-module to parallel (Rich Trott) [#32025](https://github.com/nodejs/node/pull/32025) +* [[`932563473c`](https://github.com/nodejs/node/commit/932563473c)] - **test**: improve disable AsyncLocalStorage test (Andrey Pechkurov) [#31998](https://github.com/nodejs/node/pull/31998) +* [[`49864d161e`](https://github.com/nodejs/node/commit/49864d161e)] - **test**: fix flaky test-dns-any.js (Rich Trott) [#32017](https://github.com/nodejs/node/pull/32017) +* [[`38494746a6`](https://github.com/nodejs/node/commit/38494746a6)] - **test**: fix flaky test-gc-net-timeout (Robert Nagy) [#31918](https://github.com/nodejs/node/pull/31918) +* [[`b6d33f671a`](https://github.com/nodejs/node/commit/b6d33f671a)] - **test**: change test to not be sensitive to buffer send size (Rusty Conover) [#31499](https://github.com/nodejs/node/pull/31499) +* [[`cef5502055`](https://github.com/nodejs/node/commit/cef5502055)] - **test**: remove sequential/test-https-keep-alive-large-write.js (Rusty Conover) [#31499](https://github.com/nodejs/node/pull/31499) +* [[`f1e76488a7`](https://github.com/nodejs/node/commit/f1e76488a7)] - **test**: validate common property usage (Denys Otrishko) [#31933](https://github.com/nodejs/node/pull/31933) +* [[`ab8f060159`](https://github.com/nodejs/node/commit/ab8f060159)] - **test**: fix usage of invalid common properties (Denys Otrishko) [#31933](https://github.com/nodejs/node/pull/31933) +* [[`49c959d636`](https://github.com/nodejs/node/commit/49c959d636)] - **test**: increase timeout in vm-timeout-escape-queuemicrotask (Denys Otrishko) [#31966](https://github.com/nodejs/node/pull/31966) +* [[`04eda02d87`](https://github.com/nodejs/node/commit/04eda02d87)] - **test**: add documentation for common.enoughTestCpu (Rich Trott) [#31931](https://github.com/nodejs/node/pull/31931) +* [[`918c2b67cc`](https://github.com/nodejs/node/commit/918c2b67cc)] - **test**: fix typo in common/index.js (Rich Trott) [#31931](https://github.com/nodejs/node/pull/31931) +* [[`f89fb2751b`](https://github.com/nodejs/node/commit/f89fb2751b)] - **test**: mark empty udp tests flaky on OS X (Sam Roberts) [#31936](https://github.com/nodejs/node/pull/31936) +* [[`e08fef1fda`](https://github.com/nodejs/node/commit/e08fef1fda)] - **test**: add secp224k1 check in crypto-dh-stateless (Daniel Bevenius) [#31715](https://github.com/nodejs/node/pull/31715) +* [[`4fe9e043ef`](https://github.com/nodejs/node/commit/4fe9e043ef)] - **test**: remove common.PORT from assorted pummel tests (Rich Trott) [#31897](https://github.com/nodejs/node/pull/31897) +* [[`7d5776e119`](https://github.com/nodejs/node/commit/7d5776e119)] - **test**: remove flaky designation for test-net-connect-options-port (Rich Trott) [#31841](https://github.com/nodejs/node/pull/31841) +* [[`1933efa62f`](https://github.com/nodejs/node/commit/1933efa62f)] - **test**: remove common.PORT from test-net-write-callbacks.js (Rich Trott) [#31839](https://github.com/nodejs/node/pull/31839) +* [[`87e9014764`](https://github.com/nodejs/node/commit/87e9014764)] - **test**: remove common.PORT from test-net-pause (Rich Trott) [#31749](https://github.com/nodejs/node/pull/31749) +* [[`3fbd5ab265`](https://github.com/nodejs/node/commit/3fbd5ab265)] - **test**: remove common.PORT from test-tls-server-large-request (Rich Trott) [#31749](https://github.com/nodejs/node/pull/31749) +* [[`e76ac1d2c9`](https://github.com/nodejs/node/commit/e76ac1d2c9)] - **test**: remove common.PORT from test-net-throttle (Rich Trott) [#31749](https://github.com/nodejs/node/pull/31749) +* [[`724bf3105b`](https://github.com/nodejs/node/commit/724bf3105b)] - **test**: remove common.PORT from test-net-timeout (Rich Trott) [#31749](https://github.com/nodejs/node/pull/31749) +* [[`60c71dcad2`](https://github.com/nodejs/node/commit/60c71dcad2)] - **test**: add known issue test for sync writable callback (James M Snell) [#31756](https://github.com/nodejs/node/pull/31756) +* [[`2c0b249098`](https://github.com/nodejs/node/commit/2c0b249098)] - **tls**: reduce memory copying and number of BIO buffer allocations (Rusty Conover) [#31499](https://github.com/nodejs/node/pull/31499) +* [[`acb3aff674`](https://github.com/nodejs/node/commit/acb3aff674)] - **(SEMVER-MINOR)** **tls**: expose SSL\_export\_keying\_material (simon) [#31814](https://github.com/nodejs/node/pull/31814) +* [[`f293dcf6de`](https://github.com/nodejs/node/commit/f293dcf6de)] - **tools**: add NODE\_TEST\_NO\_INTERNET to the doc builder (Joyee Cheung) [#31849](https://github.com/nodejs/node/pull/31849) +* [[`79b1f04b15`](https://github.com/nodejs/node/commit/79b1f04b15)] - **tools**: sync gyp code base with node-gyp repo (Michaël Zasso) [#30563](https://github.com/nodejs/node/pull/30563) +* [[`f858f2366c`](https://github.com/nodejs/node/commit/f858f2366c)] - **tools**: update lint-md task to lint for possessives of Node.js (Rich Trott) [#31862](https://github.com/nodejs/node/pull/31862) +* [[`ae3929e958`](https://github.com/nodejs/node/commit/ae3929e958)] - **(SEMVER-MINOR)** **vm**: implement vm.measureMemory() for per-context memory measurement (Joyee Cheung) [#31824](https://github.com/nodejs/node/pull/31824) +* [[`a86cb0e480`](https://github.com/nodejs/node/commit/a86cb0e480)] - **vm**: lazily initialize primordials for vm contexts (Joyee Cheung) [#31738](https://github.com/nodejs/node/pull/31738) +* [[`f2389eba99`](https://github.com/nodejs/node/commit/f2389eba99)] - **worker**: emit runtime error on loop creation failure (Harshitha KP) [#31621](https://github.com/nodejs/node/pull/31621) +* [[`f87ac90849`](https://github.com/nodejs/node/commit/f87ac90849)] - **worker**: unroll file extension regexp (Anna Henningsen) [#31779](https://github.com/nodejs/node/pull/31779) + ## 2020-02-18, Version 13.9.0 (Current), @codebytere From 213047489002d3f7f7a8cc8a12d64ecf99723b82 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Wed, 4 Mar 2020 11:11:37 -0800 Subject: [PATCH 120/192] build: fix zlib tarball generation PR-URL: https://github.com/nodejs/node/pull/32094 Reviewed-By: James M Snell Reviewed-By: Richard Lau Reviewed-By: Myles Borins --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c716f88e5ff..4ebb16cdfd0 100644 --- a/Makefile +++ b/Makefile @@ -1032,7 +1032,6 @@ $(TARBALL): release-only $(NODE_EXE) doc $(RM) -r $(TARNAME)/deps/v8/samples $(RM) -r $(TARNAME)/deps/v8/tools/profviz $(RM) -r $(TARNAME)/deps/v8/tools/run-tests.py - $(RM) -r $(TARNAME)/deps/zlib/contrib # too big, unused $(RM) -r $(TARNAME)/doc/images # too big $(RM) -r $(TARNAME)/test*.tap $(RM) -r $(TARNAME)/tools/cpplint.py @@ -1043,6 +1042,7 @@ $(TARBALL): release-only $(NODE_EXE) doc $(RM) -r $(TARNAME)/tools/osx-pkg.pmdoc find $(TARNAME)/deps/v8/test/* -type d ! -regex '.*/test/torque$$' | xargs $(RM) -r find $(TARNAME)/deps/v8/test -type f ! -regex '.*/test/torque/.*' | xargs $(RM) + find $(TARNAME)/deps/zlib/contrib/* -type d ! -regex '.*/contrib/optimizations$$' | xargs $(RM) -r find $(TARNAME)/ -name ".eslint*" -maxdepth 2 | xargs $(RM) find $(TARNAME)/ -type l | xargs $(RM) # annoying on windows tar -cf $(TARNAME).tar $(TARNAME) From 787143bf3e3a96bed9da17c453c8f77e016fd1b1 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 3 Mar 2020 10:29:06 +0000 Subject: [PATCH 121/192] src: pass resource object along with InternalMakeCallback This was an oversight in 9fdb6e6aaf45b2364bac89a. Fixing this is necessary to make `executionAsyncResource()` work as expected. Refs: https://github.com/nodejs/node/pull/30959 Fixes: https://github.com/nodejs/node/issues/32060 PR-URL: https://github.com/nodejs/node/pull/32063 Reviewed-By: Vladimir de Turckheim Reviewed-By: Stephen Belanger Reviewed-By: Gireesh Punathil Reviewed-By: James M Snell --- src/api/callback.cc | 5 ++- src/async_wrap.cc | 2 +- src/node_internals.h | 1 + .../test-async-exec-resource-http-32060.js | 37 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 test/async-hooks/test-async-exec-resource-http-32060.js diff --git a/src/api/callback.cc b/src/api/callback.cc index 1fb85c5883f..f7e7ddedfae 100644 --- a/src/api/callback.cc +++ b/src/api/callback.cc @@ -139,6 +139,7 @@ void InternalCallbackScope::Close() { } MaybeLocal InternalMakeCallback(Environment* env, + Local resource, Local recv, const Local callback, int argc, @@ -150,7 +151,7 @@ MaybeLocal InternalMakeCallback(Environment* env, CHECK(!argv[i].IsEmpty()); #endif - InternalCallbackScope scope(env, recv, asyncContext); + InternalCallbackScope scope(env, resource, asyncContext); if (scope.Failed()) { return MaybeLocal(); } @@ -224,7 +225,7 @@ MaybeLocal MakeCallback(Isolate* isolate, CHECK_NOT_NULL(env); Context::Scope context_scope(env->context()); MaybeLocal ret = - InternalMakeCallback(env, recv, callback, argc, argv, asyncContext); + InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext); if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) { // This is only for legacy compatibility and we may want to look into // removing/adjusting it. diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 892e33c624b..b35cdca08a6 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -749,7 +749,7 @@ MaybeLocal AsyncWrap::MakeCallback(const Local cb, ProviderType provider = provider_type(); async_context context { get_async_id(), get_trigger_async_id() }; MaybeLocal ret = InternalMakeCallback( - env(), object(), cb, argc, argv, context); + env(), GetResource(), object(), cb, argc, argv, context); // This is a static call with cached values because the `this` object may // no longer be alive at this point. diff --git a/src/node_internals.h b/src/node_internals.h index 91ba2a58a75..7dcbf65f8e6 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -199,6 +199,7 @@ static v8::MaybeLocal New(Environment* env, v8::MaybeLocal InternalMakeCallback( Environment* env, + v8::Local resource, v8::Local recv, const v8::Local callback, int argc, diff --git a/test/async-hooks/test-async-exec-resource-http-32060.js b/test/async-hooks/test-async-exec-resource-http-32060.js new file mode 100644 index 00000000000..0ff68aa1070 --- /dev/null +++ b/test/async-hooks/test-async-exec-resource-http-32060.js @@ -0,0 +1,37 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { + executionAsyncResource, + executionAsyncId, + createHook, +} = require('async_hooks'); +const http = require('http'); + +const hooked = {}; +createHook({ + init(asyncId, type, triggerAsyncId, resource) { + hooked[asyncId] = resource; + } +}).enable(); + +const server = http.createServer((req, res) => { + res.write('hello'); + setTimeout(() => { + res.end(' world!'); + }, 1000); +}); + +server.listen(0, () => { + assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]); + http.get({ port: server.address().port }, (res) => { + assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]); + res.on('data', () => { + assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]); + }); + res.on('end', () => { + assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]); + server.close(); + }); + }); +}); From 67d45fb298158f47974a41d821c25dec26bbd40a Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Wed, 4 Mar 2020 17:04:01 -0500 Subject: [PATCH 122/192] 2020-03-04 Version 13.10.1 (Current) Notable changes: In Node.js 13.9.0 deps/zlib was switched to the chromium maintained implementation. This change had the unforseen consequence of breaking building from the tarballs we release as we were too aggressively removing `unneccessary files` from the `deps/zlib` folder. This release includes a patch that ensures that individuals will once again be able to build Node.js from source. PR-URL: https://github.com/nodejs/node/pull/32099 --- CHANGELOG.md | 3 ++- doc/changelogs/CHANGELOG_V13.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8a3198bc4..1dec016f811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ release. -13.10.0
+13.10.1
+13.10.0
13.9.0
13.8.0
13.7.0
diff --git a/doc/changelogs/CHANGELOG_V13.md b/doc/changelogs/CHANGELOG_V13.md index 92310d28754..9eef50a7667 100644 --- a/doc/changelogs/CHANGELOG_V13.md +++ b/doc/changelogs/CHANGELOG_V13.md @@ -9,6 +9,7 @@ +13.10.1
13.10.0
13.9.0
13.8.0
@@ -40,6 +41,35 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-03-04, Version 13.10.1 (Current), @MylesBorins + +### Notable Changes + +In Node.js 13.9.0 deps/zlib was switched to the chromium maintained implementation. This change +had the unforseen consequence of breaking building from the tarballs we release as we were too +aggressively removing `unneccessary files` from the `deps/zlib` folder. This release includes +a patch that ensures that individuals will once again be able to build Node.js from source. + +### Commits + +* [[`723aa41d96`](https://github.com/nodejs/node/commit/723aa41d96)] - **build**: fix zlib tarball generation (Shelley Vohr) [#32094](https://github.com/nodejs/node/pull/32094) +* [[`9c1ac50fc5`](https://github.com/nodejs/node/commit/9c1ac50fc5)] - **build**: fix building with ninja (Richard Lau) [#32071](https://github.com/nodejs/node/pull/32071) +* [[`478450d6b3`](https://github.com/nodejs/node/commit/478450d6b3)] - **build**: add asan check in Github action (gengjiawen) [#31902](https://github.com/nodejs/node/pull/31902) +* [[`0fc45f80b5`](https://github.com/nodejs/node/commit/0fc45f80b5)] - **crypto**: simplify exportKeyingMaterial (Tobias Nießen) [#31922](https://github.com/nodejs/node/pull/31922) +* [[`4dc59b91a7`](https://github.com/nodejs/node/commit/4dc59b91a7)] - **dgram**: make UDPWrap more reusable (Anna Henningsen) [#31871](https://github.com/nodejs/node/pull/31871) +* [[`4ed720e940`](https://github.com/nodejs/node/commit/4ed720e940)] - **doc**: visibility of Worker threads cli options (Harshitha KP) [#31380](https://github.com/nodejs/node/pull/31380) +* [[`2518213a1b`](https://github.com/nodejs/node/commit/2518213a1b)] - **doc**: improve doc/markdown file organization coherence (ConorDavenport) [#31792](https://github.com/nodejs/node/pull/31792) +* [[`ba3f7ff94d`](https://github.com/nodejs/node/commit/ba3f7ff94d)] - **doc**: update stream.pipeline() signature (vsemozhetbyt) [#31789](https://github.com/nodejs/node/pull/31789) +* [[`3c8daa3aa0`](https://github.com/nodejs/node/commit/3c8daa3aa0)] - **events**: convert errorMonitor to a normal property (Gerhard Stoebich) [#31848](https://github.com/nodejs/node/pull/31848) +* [[`6b44df2415`](https://github.com/nodejs/node/commit/6b44df2415)] - **perf,src**: add HistogramBase and internal/histogram.js (James M Snell) [#31988](https://github.com/nodejs/node/pull/31988) +* [[`6a9cea9ed2`](https://github.com/nodejs/node/commit/6a9cea9ed2)] - **src**: pass resource object along with InternalMakeCallback (Anna Henningsen) [#32063](https://github.com/nodejs/node/pull/32063) +* [[`70f046010c`](https://github.com/nodejs/node/commit/70f046010c)] - **src**: start the .text section with an asm symbol (Gabriel Schulhof) [#31981](https://github.com/nodejs/node/pull/31981) +* [[`755da035ce`](https://github.com/nodejs/node/commit/755da035ce)] - **src**: add node\_crypto\_common and refactor (James M Snell) [#32016](https://github.com/nodejs/node/pull/32016) +* [[`4d5318c164`](https://github.com/nodejs/node/commit/4d5318c164)] - **src**: improve handling of internal field counting (James M Snell) [#31960](https://github.com/nodejs/node/pull/31960) +* [[`1539928ed9`](https://github.com/nodejs/node/commit/1539928ed9)] - **test**: add GC test for disabled AsyncLocalStorage (Andrey Pechkurov) [#31995](https://github.com/nodejs/node/pull/31995) +* [[`be90817558`](https://github.com/nodejs/node/commit/be90817558)] - **test**: remove common.port from test-tls-securepair-client (Rich Trott) [#32024](https://github.com/nodejs/node/pull/32024) + ## 2020-03-04, Version 13.10.0 (Current), @codebytere From cb8898c48feac55ef49b1594d7a4131e827c213e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Reis?= Date: Thu, 20 Feb 2020 19:35:14 +0000 Subject: [PATCH 123/192] win: block running on EOL Windows versions Windows 7 and Windows Server 2008 R2 EOL was January 14, 2020. Windows 8 EOL was January 12, 2016. Windows 2012 (not R2) is still supported and allowed to run. This clarifies that support is experimental. PR-URL: https://github.com/nodejs/node/pull/31954 Reviewed-By: Rod Vagg Reviewed-By: Rich Trott Reviewed-By: David Carlier Reviewed-By: Franziska Hinkelmann Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- BUILDING.md | 7 ++++--- src/node_main.cc | 9 ++++++--- tools/msvs/msi/product.wxs | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 4f24ebe06c0..7a923850e6e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -106,8 +106,9 @@ platforms. This is true regardless of entries in the table below. | GNU/Linux | armv6 | kernel >= 4.14, glibc >= 2.24 | Experimental | Downgraded as of Node.js 12 | | GNU/Linux | ppc64le >=power8 | kernel >= 3.10.0, glibc >= 2.17 | Tier 2 | e.g. Ubuntu 16.04 [1](#fn1), EL 7 [2](#fn2) | | GNU/Linux | s390x | kernel >= 3.10.0, glibc >= 2.17 | Tier 2 | e.g. EL 7 [2](#fn2) | -| Windows | x64, x86 (WoW64) | >= Windows 7/2008 R2/2012 R2 | Tier 1 | [4](#fn4),[5](#fn5) | -| Windows | x86 (native) | >= Windows 7/2008 R2/2012 R2 | Tier 1 (running) / Experimental (compiling) [6](#fn6) | | +| Windows | x64, x86 (WoW64) | >= Windows 8.1/2012 R2 | Tier 1 | [4](#fn4),[5](#fn5) | +| Windows | x86 (native) | >= Windows 8.1/2012 R2 | Tier 1 (running) / Experimental (compiling) [6](#fn6) | | +| Windows | x64, x86 | Windows Server 2012 (not R2) | Experimental | | | Windows | arm64 | >= Windows 10 | Experimental | | | macOS | x64 | >= 10.11 | Tier 1 | | | SmartOS | x64 | >= 18 | Tier 2 | | @@ -174,7 +175,7 @@ Binaries at are produced on: | linux-s390x | RHEL 7 with devtoolset-6 / GCC 6 [7](#fn7) | | linux-x64 | CentOS 7 with devtoolset-6 / GCC 6 [7](#fn7) | | sunos-x64 | SmartOS 18 with GCC 7 | -| win-x64 and win-x86 | Windows 2012 R2 (x64) with Visual Studio 2017 | +| win-x64 and win-x86 | Windows 2012 R2 (x64) with Visual Studio 2019 | 7: The Enterprise Linux devtoolset-6 allows us to compile binaries with GCC 6 but linked to the glibc and libstdc++ versions of the host diff --git a/src/node_main.cc b/src/node_main.cc index e92c0df9429..00f3f2a4836 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -28,9 +28,12 @@ #include int wmain(int argc, wchar_t* wargv[]) { - if (!IsWindows7OrGreater()) { - fprintf(stderr, "This application is only supported on Windows 7, " - "Windows Server 2008 R2, or higher."); + // Windows Server 2012 (not R2) is supported until 10/10/2023, so we allow it + // to run in the experimental support tier. + if (!IsWindows8Point1OrGreater() && + !(IsWindowsServer() && IsWindows8OrGreater())) { + fprintf(stderr, "This application is only supported on Windows 8.1, " + "Windows Server 2012 R2, or higher."); exit(ERROR_EXE_MACHINE_TYPE_MISMATCH); } diff --git a/tools/msvs/msi/product.wxs b/tools/msvs/msi/product.wxs index f008ea7f9ad..8a278637e60 100755 --- a/tools/msvs/msi/product.wxs +++ b/tools/msvs/msi/product.wxs @@ -23,8 +23,8 @@ Compressed="yes" InstallScope="perMachine"/> - - = 601)]]> + + = 603) OR (VersionNT >= 602 AND MsiNTProductType <> 1)]]> From 757e2037e74fecca521c664fc8486596b306de39 Mon Sep 17 00:00:00 2001 From: Harshitha KP Date: Tue, 25 Feb 2020 02:46:16 -0500 Subject: [PATCH 124/192] src: Handle bad callback in asyc_wrap Align with the MaybeLocal<> API contract PR-URL: https://github.com/nodejs/node/pull/31946 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/async_wrap-inl.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/async_wrap-inl.h b/src/async_wrap-inl.h index e3e48666e4f..03745081f3b 100644 --- a/src/async_wrap-inl.h +++ b/src/async_wrap-inl.h @@ -74,9 +74,8 @@ inline v8::MaybeLocal AsyncWrap::MakeCallback( if (!object()->Get(env()->context(), symbol).ToLocal(&cb_v)) return v8::MaybeLocal(); if (!cb_v->IsFunction()) { - // TODO(addaleax): We should throw an error here to fulfill the - // `MaybeLocal<>` API contract. - return v8::MaybeLocal(); + v8::Isolate* isolate = env()->isolate(); + return Undefined(isolate); } return MakeCallback(cb_v.As(), argc, argv); } From 616a729b3837c6152da6bb7f7c06aecbbdaa2696 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 15:14:38 -0800 Subject: [PATCH 125/192] test: remove common.expectsInternalAssertion Remove convenience function for internal assertions. It is only used once. Signed-off-by: Rich Trott PR-URL: https://github.com/nodejs/node/pull/32057 Reviewed-By: Luigi Pinca Reviewed-By: Anto Aravinth --- test/common/index.js | 14 -------------- test/parallel/test-internal-errors.js | 11 +++++++---- test/sequential/test-fs-watch.js | 8 ++++++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/test/common/index.js b/test/common/index.js index 653de4685ca..28ce841c48c 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -547,19 +547,6 @@ function expectsError(validator, exact) { }, exact); } -const suffix = 'This is caused by either a bug in Node.js ' + - 'or incorrect usage of Node.js internals.\n' + - 'Please open an issue with this stack trace at ' + - 'https://github.com/nodejs/node/issues\n'; - -function expectsInternalAssertion(fn, message) { - assert.throws(fn, { - message: `${message}\n${suffix}`, - name: 'Error', - code: 'ERR_INTERNAL_ASSERTION' - }); -} - function skipIfInspectorDisabled() { if (!process.features.inspector) { skip('V8 inspector is disabled'); @@ -680,7 +667,6 @@ const common = { createZeroFilledFile, disableCrashOnUnhandledRejection, expectsError, - expectsInternalAssertion, expectWarning, getArrayBufferViews, getBufferSources, diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js index fbb8a0a86a3..7bcc7dcc330 100644 --- a/test/parallel/test-internal-errors.js +++ b/test/parallel/test-internal-errors.js @@ -1,6 +1,6 @@ // Flags: --expose-internals 'use strict'; -const common = require('../common'); +require('../common'); const { hijackStdout, restoreStdout, @@ -50,10 +50,13 @@ errors.E('TEST_ERROR_2', (a, b) => `${a} ${b}`, Error); } { - common.expectsInternalAssertion( + assert.throws( () => new errors.codes.TEST_ERROR_1(), - 'Code: TEST_ERROR_1; The provided arguments ' + - 'length (0) does not match the required ones (1).' + { + message: /^Code: TEST_ERROR_1; The provided arguments length \(0\) does not match the required ones \(1\)\./, + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION' + } ); } diff --git a/test/sequential/test-fs-watch.js b/test/sequential/test-fs-watch.js index 031e92c61c0..ff0a52c89c6 100644 --- a/test/sequential/test-fs-watch.js +++ b/test/sequential/test-fs-watch.js @@ -117,14 +117,18 @@ tmpdir.refresh(); // https://github.com/joyent/node/issues/6690 { let oldhandle; - common.expectsInternalAssertion( + assert.throws( () => { const w = fs.watch(__filename, common.mustNotCall()); oldhandle = w._handle; w._handle = { close: w._handle.close }; w.close(); }, - 'handle must be a FSEvent' + { + message: /^handle must be a FSEvent/, + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION', + } ); oldhandle.close(); // clean up } From b1d4c13430c92e94920f0c8c9ba1295c075c9e89 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 16:19:17 -0800 Subject: [PATCH 126/192] test: add coverage for FSWatcher exception Cover an previously uncovered exception possible in the internal start function for FSWatcher. Signed-off-by: Rich Trott PR-URL: https://github.com/nodejs/node/pull/32057 Reviewed-By: Luigi Pinca Reviewed-By: Anto Aravinth --- test/sequential/test-fs-watch.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/sequential/test-fs-watch.js b/test/sequential/test-fs-watch.js index ff0a52c89c6..8c543a2a172 100644 --- a/test/sequential/test-fs-watch.js +++ b/test/sequential/test-fs-watch.js @@ -125,9 +125,31 @@ tmpdir.refresh(); w.close(); }, { + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION', message: /^handle must be a FSEvent/, + } + ); + oldhandle.close(); // clean up +} + +{ + let oldhandle; + assert.throws( + () => { + const w = fs.watch(__filename, common.mustNotCall()); + oldhandle = w._handle; + const protoSymbols = + Object.getOwnPropertySymbols(Object.getPrototypeOf(w)); + const kFSWatchStart = + protoSymbols.find((val) => val.toString() === 'Symbol(kFSWatchStart)'); + w._handle = {}; + w[kFSWatchStart](); + }, + { name: 'Error', code: 'ERR_INTERNAL_ASSERTION', + message: /^handle must be a FSEvent/, } ); oldhandle.close(); // clean up From 9ec87815027ddf6782ab930975b86333a61ed554 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 13 Feb 2020 12:05:18 +0100 Subject: [PATCH 127/192] crypto: make update(buf, enc) ignore encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the cipher/decipher/hash/hmac update() methods ignore the input encoding when the input is a buffer. This is the documented behavior but some inputs were rejected, notably when the specified encoding is 'hex' and the buffer has an odd length (because a _string_ with an odd length is never a valid hex string.) The sign/verify update() methods work okay because they use different validation logic. Fixes: https://github.com/nodejs/node/issues/31751 PR-URL: https://github.com/nodejs/node/pull/31766 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Richard Lau Reviewed-By: Luigi Pinca Reviewed-By: Tobias Nießen --- lib/internal/crypto/cipher.js | 6 +++--- lib/internal/crypto/hash.js | 14 +++++-------- test/parallel/test-crypto-update-encoding.js | 22 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 test/parallel/test-crypto-update-encoding.js diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js index add56eae680..80b0c0e9dab 100644 --- a/lib/internal/crypto/cipher.js +++ b/lib/internal/crypto/cipher.js @@ -151,13 +151,13 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { inputEncoding = inputEncoding || encoding; outputEncoding = outputEncoding || encoding; - if (typeof data !== 'string' && !isArrayBufferView(data)) { + if (typeof data === 'string') { + validateEncoding(data, inputEncoding); + } else if (!isArrayBufferView(data)) { throw new ERR_INVALID_ARG_TYPE( 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); } - validateEncoding(data, inputEncoding); - const ret = this[kHandle].update(data, inputEncoding); if (outputEncoding && outputEncoding !== 'buffer') { diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index dca0ba767f6..1cf0188da2f 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -78,17 +78,13 @@ Hash.prototype.update = function update(data, encoding) { if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED(); - if (typeof data !== 'string' && !isArrayBufferView(data)) { - throw new ERR_INVALID_ARG_TYPE('data', - ['string', - 'Buffer', - 'TypedArray', - 'DataView'], - data); + if (typeof data === 'string') { + validateEncoding(data, encoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); } - validateEncoding(data, encoding); - if (!this[kHandle].update(data, encoding)) throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); return this; diff --git a/test/parallel/test-crypto-update-encoding.js b/test/parallel/test-crypto-update-encoding.js new file mode 100644 index 00000000000..e1e6d029aa5 --- /dev/null +++ b/test/parallel/test-crypto-update-encoding.js @@ -0,0 +1,22 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const crypto = require('crypto'); + +const zeros = Buffer.alloc; +const key = zeros(16); +const iv = zeros(16); + +const cipher = () => crypto.createCipheriv('aes-128-cbc', key, iv); +const decipher = () => crypto.createDecipheriv('aes-128-cbc', key, iv); +const hash = () => crypto.createSign('sha256'); +const hmac = () => crypto.createHmac('sha256', key); +const sign = () => crypto.createSign('sha256'); +const verify = () => crypto.createVerify('sha256'); + +for (const f of [cipher, decipher, hash, hmac, sign, verify]) + for (const n of [15, 16]) + f().update(zeros(n), 'hex'); // Should ignore inputEncoding. From b023d61716ddc9cd97cc148bb8d237ec8d894d2b Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 18 Feb 2020 09:14:50 -0800 Subject: [PATCH 128/192] lib: move isLegalPort to validators, refactor isLegalPort was used multiple places in the same way -- to validate the port and throw if necessary. Moved into internal/validators. PR-URL: https://github.com/nodejs/node/pull/31851 Reviewed-By: Richard Lau --- lib/dgram.js | 24 +++--------------- lib/dns.js | 11 ++++---- lib/internal/cluster/master.js | 7 ++---- lib/internal/dns/promises.js | 12 ++++----- lib/internal/errors.js | 2 +- lib/internal/net.js | 10 -------- lib/internal/validators.js | 25 +++++++++++++++---- lib/net.js | 16 ++++++------ .../test-internal-validators-validateport.js | 23 +++++++++++++++++ test/parallel/test-net-internal.js | 20 --------------- 10 files changed, 69 insertions(+), 81 deletions(-) create mode 100644 test/parallel/test-internal-validators-validateport.js delete mode 100644 test/parallel/test-net-internal.js diff --git a/lib/dgram.js b/lib/dgram.js index ac90f35f83e..ddac50ade19 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -35,15 +35,11 @@ const { newHandle, } = require('internal/dgram'); const { guessHandleType } = internalBinding('util'); -const { - isLegalPort, -} = require('internal/net'); const { ERR_INVALID_ARG_TYPE, ERR_MISSING_ARGS, ERR_SOCKET_ALREADY_BOUND, ERR_SOCKET_BAD_BUFFER_SIZE, - ERR_SOCKET_BAD_PORT, ERR_SOCKET_BUFFER_SIZE, ERR_SOCKET_DGRAM_IS_CONNECTED, ERR_SOCKET_DGRAM_NOT_CONNECTED, @@ -53,7 +49,8 @@ const { const { isInt32, validateString, - validateNumber + validateNumber, + validatePort, } = require('internal/validators'); const { Buffer } = require('buffer'); const { deprecate } = require('internal/util'); @@ -351,21 +348,8 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { return this; }; - -function validatePort(port) { - const legal = isLegalPort(port); - if (legal) - port = port | 0; - - if (!legal || port === 0) - throw new ERR_SOCKET_BAD_PORT(port); - - return port; -} - - Socket.prototype.connect = function(port, address, callback) { - port = validatePort(port); + port = validatePort(port, 'Port', { allowZero: false }); if (typeof address === 'function') { callback = address; address = ''; @@ -610,7 +594,7 @@ Socket.prototype.send = function(buffer, } if (!connected) - port = validatePort(port); + port = validatePort(port, 'Port', { allowZero: false }); // Normalize callback so it's either a function or undefined but not anything // else. diff --git a/lib/dns.js b/lib/dns.js index 8a6c7456bab..e33dd2620e1 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -29,7 +29,7 @@ const { const cares = internalBinding('cares_wrap'); const { toASCII } = require('internal/idna'); -const { isIP, isLegalPort } = require('internal/net'); +const { isIP } = require('internal/net'); const { customPromisifyArgs } = require('internal/util'); const errors = require('internal/errors'); const { @@ -45,9 +45,11 @@ const { ERR_INVALID_CALLBACK, ERR_INVALID_OPT_VALUE, ERR_MISSING_ARGS, - ERR_SOCKET_BAD_PORT } = errors.codes; -const { validateString } = require('internal/validators'); +const { + validatePort, + validateString, +} = require('internal/validators'); const { GetAddrInfoReqWrap, @@ -175,8 +177,7 @@ function lookupService(address, port, callback) { if (isIP(address) === 0) throw new ERR_INVALID_OPT_VALUE('address', address); - if (!isLegalPort(port)) - throw new ERR_SOCKET_BAD_PORT(port); + validatePort(port); if (typeof callback !== 'function') throw new ERR_INVALID_CALLBACK(callback); diff --git a/lib/internal/cluster/master.js b/lib/internal/cluster/master.js index 9bdb0181d3d..46c77900f42 100644 --- a/lib/internal/cluster/master.js +++ b/lib/internal/cluster/master.js @@ -14,13 +14,12 @@ const RoundRobinHandle = require('internal/cluster/round_robin_handle'); const SharedHandle = require('internal/cluster/shared_handle'); const Worker = require('internal/cluster/worker'); const { internal, sendHelper } = require('internal/cluster/utils'); -const { ERR_SOCKET_BAD_PORT } = require('internal/errors').codes; const cluster = new EventEmitter(); const intercom = new EventEmitter(); const SCHED_NONE = 1; const SCHED_RR = 2; -const { isLegalPort } = require('internal/net'); const [ minPort, maxPort ] = [ 1024, 65535 ]; +const { validatePort } = require('internal/validators'); module.exports = cluster; @@ -118,9 +117,7 @@ function createWorkerProcess(id, env) { else inspectPort = cluster.settings.inspectPort; - if (!isLegalPort(inspectPort)) { - throw new ERR_SOCKET_BAD_PORT(inspectPort); - } + validatePort(inspectPort); } else { inspectPort = process.debugPort + debugPortOffset; if (inspectPort > maxPort) diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js index ae007fd3193..6ade8854964 100644 --- a/lib/internal/dns/promises.js +++ b/lib/internal/dns/promises.js @@ -14,7 +14,7 @@ const { } = require('internal/dns/utils'); const { codes, dnsException } = require('internal/errors'); const { toASCII } = require('internal/idna'); -const { isIP, isLegalPort } = require('internal/net'); +const { isIP } = require('internal/net'); const { getaddrinfo, getnameinfo, @@ -27,10 +27,11 @@ const { ERR_INVALID_ARG_TYPE, ERR_INVALID_OPT_VALUE, ERR_MISSING_ARGS, - ERR_SOCKET_BAD_PORT } = codes; -const { validateString } = require('internal/validators'); - +const { + validatePort, + validateString +} = require('internal/validators'); function onlookup(err, addresses) { if (err) { @@ -162,8 +163,7 @@ function lookupService(address, port) { if (isIP(address) === 0) throw new ERR_INVALID_OPT_VALUE('address', address); - if (!isLegalPort(port)) - throw new ERR_SOCKET_BAD_PORT(port); + validatePort(port); return createLookupServicePromise(address, +port); } diff --git a/lib/internal/errors.js b/lib/internal/errors.js index be7385644b0..9f9a0a66f28 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1282,7 +1282,7 @@ E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound', Error); E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer', TypeError); E('ERR_SOCKET_BAD_PORT', - 'Port should be >= 0 and < 65536. Received %s.', RangeError); + '%s should be >= 0 and < 65536. Received %s.', RangeError); E('ERR_SOCKET_BAD_TYPE', 'Bad socket type specified. Valid types are: udp4, udp6', TypeError); E('ERR_SOCKET_BUFFER_SIZE', diff --git a/lib/internal/net.js b/lib/internal/net.js index 728c6f587a8..4a9a156aeab 100644 --- a/lib/internal/net.js +++ b/lib/internal/net.js @@ -41,15 +41,6 @@ function isIP(s) { return 0; } -// Check that the port number is not NaN when coerced to a number, -// is an integer and that it falls within the legal range of port numbers. -function isLegalPort(port) { - if ((typeof port !== 'number' && typeof port !== 'string') || - (typeof port === 'string' && port.trim().length === 0)) - return false; - return +port === (+port >>> 0) && port <= 0xFFFF; -} - function makeSyncWrite(fd) { return function(chunk, enc, cb) { if (enc !== 'buffer') @@ -72,7 +63,6 @@ module.exports = { isIP, isIPv4, isIPv6, - isLegalPort, makeSyncWrite, normalizedArgsSymbol: Symbol('normalizedArgs') }; diff --git a/lib/internal/validators.js b/lib/internal/validators.js index b7c3711d614..46237e54342 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -10,6 +10,7 @@ const { const { hideStackFrames, codes: { + ERR_SOCKET_BAD_PORT, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, @@ -180,6 +181,19 @@ function validateEncoding(data, encoding) { } } +// Check that the port number is not NaN when coerced to a number, +// is an integer and that it falls within the legal range of port numbers. +function validatePort(port, name = 'Port', { allowZero = true } = {}) { + if ((typeof port !== 'number' && typeof port !== 'string') || + (typeof port === 'string' && port.trim().length === 0) || + +port !== (+port >>> 0) || + port > 0xFFFF || + (port === 0 && !allowZero)) { + throw new ERR_SOCKET_BAD_PORT(name, port); + } + return port | 0; +} + module.exports = { isInt32, isUint32, @@ -188,11 +202,12 @@ module.exports = { validateBoolean, validateBuffer, validateEncoding, - validateObject, - validateInteger, validateInt32, - validateUint32, - validateString, + validateInteger, validateNumber, - validateSignalName + validateObject, + validatePort, + validateSignalName, + validateString, + validateUint32, }; diff --git a/lib/net.js b/lib/net.js index 9ff6f12b9bc..0d3cc29db64 100644 --- a/lib/net.js +++ b/lib/net.js @@ -41,7 +41,6 @@ const { isIP, isIPv4, isIPv6, - isLegalPort, normalizedArgsSymbol, makeSyncWrite } = require('internal/net'); @@ -92,7 +91,6 @@ const { ERR_INVALID_OPT_VALUE, ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, - ERR_SOCKET_BAD_PORT, ERR_SOCKET_CLOSED }, errnoException, @@ -100,7 +98,11 @@ const { uvExceptionWithHostPort } = require('internal/errors'); const { isUint8Array } = require('internal/util/types'); -const { validateInt32, validateString } = require('internal/validators'); +const { + validateInt32, + validatePort, + validateString +} = require('internal/validators'); const kLastWriteQueueSize = Symbol('lastWriteQueueSize'); const { DTRACE_NET_SERVER_CONNECTION, @@ -998,9 +1000,7 @@ function lookupAndConnect(self, options) { throw new ERR_INVALID_ARG_TYPE('options.port', ['number', 'string'], port); } - if (!isLegalPort(port)) { - throw new ERR_SOCKET_BAD_PORT(port); - } + validatePort(port); } port |= 0; @@ -1437,9 +1437,7 @@ Server.prototype.listen = function(...args) { // or if options.port is normalized as 0 before let backlog; if (typeof options.port === 'number' || typeof options.port === 'string') { - if (!isLegalPort(options.port)) { - throw new ERR_SOCKET_BAD_PORT(options.port); - } + validatePort(options.port, 'options.port'); backlog = options.backlog || backlogFromArgs; // start TCP server listening on host:port if (options.host) { diff --git a/test/parallel/test-internal-validators-validateport.js b/test/parallel/test-internal-validators-validateport.js new file mode 100644 index 00000000000..ea9c3a7b58b --- /dev/null +++ b/test/parallel/test-internal-validators-validateport.js @@ -0,0 +1,23 @@ +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); +const { validatePort } = require('internal/validators'); + +for (let n = 0; n <= 0xFFFF; n++) { + validatePort(n); + validatePort(`${n}`); + validatePort(`0x${n.toString(16)}`); + validatePort(`0o${n.toString(8)}`); + validatePort(`0b${n.toString(2)}`); +} + +[ + -1, 'a', {}, [], false, true, + 0xFFFF + 1, Infinity, -Infinity, NaN, + undefined, null, '', ' ', 1.1, '0x', + '-0x1', '-0o1', '-0b1', '0o', '0b' +].forEach((i) => assert.throws(() => validatePort(i), { + code: 'ERR_SOCKET_BAD_PORT' +})); diff --git a/test/parallel/test-net-internal.js b/test/parallel/test-net-internal.js deleted file mode 100644 index 309b56d4d9a..00000000000 --- a/test/parallel/test-net-internal.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -// Flags: --expose-internals - -require('../common'); -const assert = require('assert'); -const isLegalPort = require('internal/net').isLegalPort; - -for (let n = 0; n <= 0xFFFF; n++) { - assert(isLegalPort(n)); - assert(isLegalPort(String(n))); - assert(`0x${n.toString(16)}`); - assert(`0o${n.toString(8)}`); - assert(`0b${n.toString(2)}`); -} - -const bad = [-1, 'a', {}, [], false, true, 0xFFFF + 1, Infinity, - -Infinity, NaN, undefined, null, '', ' ', 1.1, '0x', - '-0x1', '-0o1', '-0b1', '0o', '0b']; -bad.forEach((i) => assert(!isLegalPort(i))); From ba462c2e1ef1edff3af03d079c8a9bed22d09e80 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 3 Mar 2020 11:18:45 -0800 Subject: [PATCH 129/192] src: introduce node_sockaddr Introduce the SocketAddress utility class. The QUIC implementation makes extensive use of this for handling of socket addresses. It was separated out to make it generically reusable throughout core Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/32070 Reviewed-By: David Carlier Reviewed-By: Richard Lau Reviewed-By: Anna Henningsen Reviewed-By: Denys Otrishko --- node.gyp | 4 + src/node_sockaddr-inl.h | 170 +++++++++++++++++++++++++++++++++++ src/node_sockaddr.cc | 95 ++++++++++++++++++++ src/node_sockaddr.h | 122 +++++++++++++++++++++++++ src/udp_wrap.cc | 9 +- src/udp_wrap.h | 14 +-- test/cctest/test_sockaddr.cc | 57 ++++++++++++ 7 files changed, 460 insertions(+), 11 deletions(-) create mode 100644 src/node_sockaddr-inl.h create mode 100644 src/node_sockaddr.cc create mode 100644 src/node_sockaddr.h create mode 100644 test/cctest/test_sockaddr.cc diff --git a/node.gyp b/node.gyp index 088e638115e..790fd32e4db 100644 --- a/node.gyp +++ b/node.gyp @@ -598,6 +598,7 @@ 'src/node_process_methods.cc', 'src/node_process_object.cc', 'src/node_serdes.cc', + 'src/node_sockaddr.cc', 'src/node_stat_watcher.cc', 'src/node_symbols.cc', 'src/node_task_queue.cc', @@ -685,6 +686,8 @@ 'src/node_process.h', 'src/node_revert.h', 'src/node_root_certs.h', + 'src/node_sockaddr.h', + 'src/node_sockaddr-inl.h', 'src/node_stat_watcher.h', 'src/node_union_bytes.h', 'src/node_url.h', @@ -1151,6 +1154,7 @@ 'test/cctest/test_linked_binding.cc', 'test/cctest/test_per_process.cc', 'test/cctest/test_platform.cc', + 'test/cctest/test_sockaddr.cc', 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', 'test/cctest/test_url.cc', diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h new file mode 100644 index 00000000000..a9d0ed061a1 --- /dev/null +++ b/src/node_sockaddr-inl.h @@ -0,0 +1,170 @@ +#ifndef SRC_NODE_SOCKADDR_INL_H_ +#define SRC_NODE_SOCKADDR_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_internals.h" +#include "node_sockaddr.h" +#include "util-inl.h" + +#include + +namespace node { + +static constexpr uint32_t kLabelMask = 0xFFFFF; + +inline void hash_combine(size_t* seed) { } + +template +inline void hash_combine(size_t* seed, const T& value, Args... rest) { + *seed ^= std::hash{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2); + hash_combine(seed, rest...); +} + +bool SocketAddress::is_numeric_host(const char* hostname) { + return is_numeric_host(hostname, AF_INET) || + is_numeric_host(hostname, AF_INET6); +} + +bool SocketAddress::is_numeric_host(const char* hostname, int family) { + in6_addr dst; + return inet_pton(family, hostname, &dst) == 1; +} + +int SocketAddress::GetPort(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + return ntohs(addr->sa_family == AF_INET ? + reinterpret_cast(addr)->sin_port : + reinterpret_cast(addr)->sin6_port); +} + +int SocketAddress::GetPort(const sockaddr_storage* addr) { + return GetPort(reinterpret_cast(addr)); +} + +std::string SocketAddress::GetAddress(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + char host[INET6_ADDRSTRLEN]; + const void* src = addr->sa_family == AF_INET ? + static_cast( + &(reinterpret_cast(addr)->sin_addr)) : + static_cast( + &(reinterpret_cast(addr)->sin6_addr)); + uv_inet_ntop(addr->sa_family, src, host, INET6_ADDRSTRLEN); + return std::string(host); +} + +std::string SocketAddress::GetAddress(const sockaddr_storage* addr) { + return GetAddress(reinterpret_cast(addr)); +} + +size_t SocketAddress::GetLength(const sockaddr* addr) { + return addr->sa_family == AF_INET ? + sizeof(sockaddr_in) : sizeof(sockaddr_in6); +} + +size_t SocketAddress::GetLength(const sockaddr_storage* addr) { + return GetLength(reinterpret_cast(addr)); +} + +SocketAddress::SocketAddress(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); +} + +SocketAddress& SocketAddress::operator=(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); + return *this; +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); + return *this; +} + +const sockaddr& SocketAddress::operator*() const { + return *this->data(); +} + +const sockaddr* SocketAddress::operator->() const { + return this->data(); +} + +size_t SocketAddress::length() const { + return GetLength(&address_); +} + +const sockaddr* SocketAddress::data() const { + return reinterpret_cast(&address_); +} + +const uint8_t* SocketAddress::raw() const { + return reinterpret_cast(&address_); +} + +sockaddr* SocketAddress::storage() { + return reinterpret_cast(&address_); +} + +int SocketAddress::family() const { + return address_.ss_family; +} + +std::string SocketAddress::address() const { + return GetAddress(&address_); +} + +int SocketAddress::port() const { + return GetPort(&address_); +} + +uint32_t SocketAddress::flow_label() const { + if (family() != AF_INET6) + return 0; + const sockaddr_in6* in = reinterpret_cast(data()); + return in->sin6_flowinfo; +} + +void SocketAddress::set_flow_label(uint32_t label) { + if (family() != AF_INET6) + return; + CHECK_LE(label, kLabelMask); + sockaddr_in6* in = reinterpret_cast(&address_); + in->sin6_flowinfo = label; +} + +std::string SocketAddress::ToString() const { + if (family() != AF_INET && family() != AF_INET6) return ""; + return (family() == AF_INET6 ? + std::string("[") + address() + "]:" : + address() + ":") + + std::to_string(port()); +} + +void SocketAddress::Update(uint8_t* data, size_t len) { + CHECK_LE(len, sizeof(address_)); + memcpy(&address_, data, len); +} + +v8::Local SocketAddress::ToJS( + Environment* env, + v8::Local info) const { + return AddressToJS(env, data(), info); +} + +bool SocketAddress::operator==(const SocketAddress& other) const { + if (family() != other.family()) return false; + return memcmp(raw(), other.raw(), length()) == 0; +} + +bool SocketAddress::operator!=(const SocketAddress& other) const { + return !(*this == other); +} +} // namespace node + +#endif // NODE_WANT_INTERNALS +#endif // SRC_NODE_SOCKADDR_INL_H_ diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc new file mode 100644 index 00000000000..74fe123529a --- /dev/null +++ b/src/node_sockaddr.cc @@ -0,0 +1,95 @@ +#include "node_sockaddr-inl.h" // NOLINT(build/include) +#include "uv.h" + +namespace node { + +namespace { +template +SocketAddress FromUVHandle(F fn, const T& handle) { + SocketAddress addr; + int len = sizeof(sockaddr_storage); + if (fn(&handle, addr.storage(), &len) == 0) + CHECK_EQ(static_cast(len), addr.length()); + else + addr.storage()->sa_family = 0; + return addr; +} +} // namespace + +bool SocketAddress::ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr) { + switch (family) { + case AF_INET: + return uv_ip4_addr( + host, + port, + reinterpret_cast(addr)) == 0; + case AF_INET6: + return uv_ip6_addr( + host, + port, + reinterpret_cast(addr)) == 0; + default: + UNREACHABLE(); + } +} + +bool SocketAddress::New( + const char* host, + uint32_t port, + SocketAddress* addr) { + return New(AF_INET, host, port, addr) || New(AF_INET6, host, port, addr); +} + +bool SocketAddress::New( + int32_t family, + const char* host, + uint32_t port, + SocketAddress* addr) { + return ToSockAddr(family, host, port, + reinterpret_cast(addr->storage())); +} + +size_t SocketAddress::Hash::operator()(const SocketAddress& addr) const { + size_t hash = 0; + switch (addr.family()) { + case AF_INET: { + const sockaddr_in* ipv4 = + reinterpret_cast(addr.raw()); + hash_combine(&hash, ipv4->sin_port, ipv4->sin_addr.s_addr); + break; + } + case AF_INET6: { + const sockaddr_in6* ipv6 = + reinterpret_cast(addr.raw()); + const uint64_t* a = + reinterpret_cast(&ipv6->sin6_addr); + hash_combine(&hash, ipv6->sin6_port, a[0], a[1]); + break; + } + default: + UNREACHABLE(); + } + return hash; +} + +SocketAddress SocketAddress::FromSockName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getsockname, handle); +} + +SocketAddress SocketAddress::FromSockName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getsockname, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getpeername, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getpeername, handle); +} + +} // namespace node diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h new file mode 100644 index 00000000000..2e3ae09ce3b --- /dev/null +++ b/src/node_sockaddr.h @@ -0,0 +1,122 @@ +#ifndef SRC_NODE_SOCKADDR_H_ +#define SRC_NODE_SOCKADDR_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "memory_tracker.h" +#include "node.h" +#include "uv.h" +#include "v8.h" + +#include +#include + +namespace node { + +class SocketAddress : public MemoryRetainer { + public: + struct Hash { + size_t operator()(const SocketAddress& addr) const; + }; + + inline bool operator==(const SocketAddress& other) const; + inline bool operator!=(const SocketAddress& other) const; + + inline static bool is_numeric_host(const char* hostname); + inline static bool is_numeric_host(const char* hostname, int family); + + // Returns true if converting {family, host, port} to *addr succeeded. + static bool ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr); + + // Returns true if converting {family, host, port} to *addr succeeded. + static bool New( + int32_t family, + const char* host, + uint32_t port, + SocketAddress* addr); + + static bool New( + const char* host, + uint32_t port, + SocketAddress* addr); + + // Returns the port for an IPv4 or IPv6 address. + inline static int GetPort(const sockaddr* addr); + inline static int GetPort(const sockaddr_storage* addr); + + // Returns the numeric host as a string for an IPv4 or IPv6 address. + inline static std::string GetAddress(const sockaddr* addr); + inline static std::string GetAddress(const sockaddr_storage* addr); + + // Returns the struct length for an IPv4, IPv6 or UNIX domain. + inline static size_t GetLength(const sockaddr* addr); + inline static size_t GetLength(const sockaddr_storage* addr); + + SocketAddress() = default; + + inline explicit SocketAddress(const sockaddr* addr); + inline SocketAddress(const SocketAddress& addr); + inline SocketAddress& operator=(const sockaddr* other); + inline SocketAddress& operator=(const SocketAddress& other); + + inline const sockaddr& operator*() const; + inline const sockaddr* operator->() const; + + inline const sockaddr* data() const; + inline const uint8_t* raw() const; + inline sockaddr* storage(); + inline size_t length() const; + + inline int family() const; + inline std::string address() const; + inline int port() const; + + // If the SocketAddress is an IPv6 address, returns the + // current value of the IPv6 flow label, if set. Otherwise + // returns 0. + inline uint32_t flow_label() const; + + // If the SocketAddress is an IPv6 address, sets the + // current value of the IPv6 flow label. If not an + // IPv6 address, set_flow_label is a non-op. It + // is important to note that the flow label, + // while represented as an uint32_t, the flow + // label is strictly limited to 20 bits, and + // this will assert if any value larger than + // 20-bits is specified. + inline void set_flow_label(uint32_t label = 0); + + inline void Update(uint8_t* data, size_t len); + + static SocketAddress FromSockName(const uv_udp_t& handle); + static SocketAddress FromSockName(const uv_tcp_t& handle); + static SocketAddress FromPeerName(const uv_udp_t& handle); + static SocketAddress FromPeerName(const uv_tcp_t& handle); + + inline v8::Local ToJS( + Environment* env, + v8::Local obj = v8::Local()) const; + + inline std::string ToString() const; + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SocketAddress) + SET_SELF_SIZE(SocketAddress) + + template + using Map = std::unordered_map; + + private: + sockaddr_storage address_; +}; + +} // namespace node + +#endif // NOE_WANT_INTERNALS + +#endif // SRC_NODE_SOCKADDR_H_ diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 702449daae9..277eb6b81ba 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -22,6 +22,7 @@ #include "udp_wrap.h" #include "env-inl.h" #include "node_buffer.h" +#include "node_sockaddr-inl.h" #include "handle_wrap.h" #include "req_wrap-inl.h" #include "util-inl.h" @@ -628,12 +629,12 @@ AsyncWrap* UDPWrap::GetAsyncWrap() { return this; } -int UDPWrap::GetPeerName(sockaddr* name, int* namelen) { - return uv_udp_getpeername(&handle_, name, namelen); +SocketAddress UDPWrap::GetPeerName() { + return SocketAddress::FromPeerName(handle_); } -int UDPWrap::GetSockName(sockaddr* name, int* namelen) { - return uv_udp_getsockname(&handle_, name, namelen); +SocketAddress UDPWrap::GetSockName() { + return SocketAddress::FromSockName(handle_); } void UDPWrapBase::RecvStart(const FunctionCallbackInfo& args) { diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 7afd9784b0a..6fed1d2dfea 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -26,6 +26,7 @@ #include "handle_wrap.h" #include "req_wrap.h" +#include "node_sockaddr.h" #include "uv.h" #include "v8.h" @@ -95,11 +96,8 @@ class UDPWrapBase { size_t nbufs, const sockaddr* addr) = 0; - // Stores the sockaddr for the peer in `name`. - virtual int GetPeerName(sockaddr* name, int* namelen) = 0; - - // Stores the sockaddr for the local socket in `name`. - virtual int GetSockName(sockaddr* name, int* namelen) = 0; + virtual SocketAddress GetPeerName() = 0; + virtual SocketAddress GetSockName() = 0; // Returns an AsyncWrap object with the same lifetime as this object. virtual AsyncWrap* GetAsyncWrap() = 0; @@ -168,8 +166,10 @@ class UDPWrap final : public HandleWrap, ssize_t Send(uv_buf_t* bufs, size_t nbufs, const sockaddr* addr) override; - int GetPeerName(sockaddr* name, int* namelen) override; - int GetSockName(sockaddr* name, int* namelen) override; + + SocketAddress GetPeerName() override; + SocketAddress GetSockName() override; + AsyncWrap* GetAsyncWrap() override; static v8::MaybeLocal Instantiate(Environment* env, diff --git a/test/cctest/test_sockaddr.cc b/test/cctest/test_sockaddr.cc new file mode 100644 index 00000000000..8c23463f11d --- /dev/null +++ b/test/cctest/test_sockaddr.cc @@ -0,0 +1,57 @@ +#include "node_sockaddr-inl.h" +#include "gtest/gtest.h" + +using node::SocketAddress; + +TEST(SocketAddress, SocketAddress) { + CHECK(SocketAddress::is_numeric_host("123.123.123.123")); + CHECK(!SocketAddress::is_numeric_host("localhost")); + + sockaddr_storage storage; + sockaddr_storage storage2; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage); + SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 80, &storage2); + + SocketAddress addr(reinterpret_cast(&storage)); + SocketAddress addr2(reinterpret_cast(&storage2)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "123.123.123.123"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 0); + + CHECK_NE(addr, addr2); + CHECK_EQ(addr, addr); + + CHECK_EQ(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr)); + CHECK_NE(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr2)); + + addr.Update(reinterpret_cast(&storage2), sizeof(sockaddr_in)); + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "1.1.1.1"); + CHECK_EQ(addr.port(), 80); + + SocketAddress::Map map; + map[addr]++; + map[addr]++; + CHECK_EQ(map[addr], 2); +} + +TEST(SocketAddress, SocketAddressIPv6) { + sockaddr_storage storage; + SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &storage); + + SocketAddress addr(reinterpret_cast(&storage)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in6)); + CHECK_EQ(addr.family(), AF_INET6); + CHECK_EQ(addr.address(), "::1"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 12345); +} From 0d8a84adca73a0fd3554c98236389f64f854ccc3 Mon Sep 17 00:00:00 2001 From: Yael Hermon Date: Sat, 29 Feb 2020 20:27:18 +0200 Subject: [PATCH 130/192] doc: update email address in authors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update my personal email PR-URL: https://github.com/nodejs/node/pull/32026 Reviewed-By: Rich Trott Reviewed-By: Michaël Zasso Reviewed-By: Matheus Marchini Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater Reviewed-By: Michael Dawson Reviewed-By: Luigi Pinca --- .mailmap | 1 + AUTHORS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index e140392b723..15962df50bd 100644 --- a/.mailmap +++ b/.mailmap @@ -423,6 +423,7 @@ Wilson Lin Wyatt Preul geek Xavier J Ortiz xiaoyu <306766053@qq.com> Poker <306766053@qq.com> +Yael Hermon Yazhong Liu Yazhong Liu Yazhong Liu Yorkie Yazhong Liu Yorkie diff --git a/AUTHORS b/AUTHORS index adb8aef88a4..539f311a0dc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2636,7 +2636,7 @@ Charles Samborski zhmushan yoshimoto koki Ilarion Halushka -Yael Hermon +Yael Hermon Mitch Hankins Mikko Rantanen wenjun ye <1728914873@qq.com> From c3c64a1034a52e58c9684a0135a2e4ce536e41ef Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 2 Mar 2020 22:43:02 -0800 Subject: [PATCH 131/192] meta: move thefourtheye to TSC Emeritus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit thefourtheye has a considerable history of contributions to Node.js. They have not been active much of late, and the TSC Charter has a section about activity indicating that moving to Emeritus at this time is the thing to do. Thanks for all you've done to make Node.js fantastic, thefourtheye, and hope to see you around again soon! PR-URL: https://github.com/nodejs/node/pull/32059 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: Colin Ihrig Reviewed-By: Anatoli Papirovski Reviewed-By: Joyee Cheung Reviewed-By: Luigi Pinca Reviewed-By: Michael Dawson Reviewed-By: Tobias Nießen Reviewed-By: Сковорода Никита Андреевич --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0576cd5132e..b073c034b7e 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,6 @@ For information about the governance of the Node.js project, see **Sam Roberts** <vieuxtech@gmail.com> * [targos](https://github.com/targos) - **Michaël Zasso** <targos@protonmail.com> (he/him) -* [thefourtheye](https://github.com/thefourtheye) - -**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <tniessen@tnie.de> * [Trott](https://github.com/Trott) - @@ -222,6 +220,8 @@ For information about the governance of the Node.js project, see **Rod Vagg** <r@va.gg> * [shigeki](https://github.com/shigeki) - **Shigeki Ohtsu** <ohtsu@ohtsu.org> (he/him) +* [thefourtheye](https://github.com/thefourtheye) - +**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him) * [TimothyGu](https://github.com/TimothyGu) - **Tiancheng "Timothy" Gu** <timothygu99@gmail.com> (he/him) * [trevnorris](https://github.com/trevnorris) - From 5cc0754090b4dc504652ea57803312c9b9f1cef0 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 16 Feb 2020 03:50:01 -0500 Subject: [PATCH 132/192] benchmark: remove problematic tls params These very small values can cause crashes/exceptions to occur on some systems because most time is spent in V8 GC or in parts of node core that are not being tested (e.g. streams). PR-URL: https://github.com/nodejs/node/pull/31816 Reviewed-By: James M Snell --- benchmark/tls/secure-pair.js | 2 +- benchmark/tls/throughput.js | 2 +- test/benchmark/test-benchmark-tls.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/tls/secure-pair.js b/benchmark/tls/secure-pair.js index c52f4cbf918..bb7933d837f 100644 --- a/benchmark/tls/secure-pair.js +++ b/benchmark/tls/secure-pair.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { dur: [5], securing: ['SecurePair', 'TLSSocket', 'clear'], - size: [2, 100, 1024, 1024 * 1024] + size: [100, 1024, 1024 * 1024] }); const fixtures = require('../../test/common/fixtures'); diff --git a/benchmark/tls/throughput.js b/benchmark/tls/throughput.js index 727d20e4600..3ea84aa84ef 100644 --- a/benchmark/tls/throughput.js +++ b/benchmark/tls/throughput.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { dur: [5], type: ['buf', 'asc', 'utf'], - size: [2, 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024] + size: [100, 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024] }); const fixtures = require('../../test/common/fixtures'); diff --git a/test/benchmark/test-benchmark-tls.js b/test/benchmark/test-benchmark-tls.js index 40c14af8302..264fa08d396 100644 --- a/test/benchmark/test-benchmark-tls.js +++ b/test/benchmark/test-benchmark-tls.js @@ -19,9 +19,9 @@ runBenchmark('tls', 'concurrency=1', 'dur=0.1', 'n=1', - 'size=2', + 'size=1024', 'securing=SecurePair', - 'type=asc' + 'type=buf' ], { NODEJS_BENCHMARK_ZERO_ALLOWED: 1, From b6cd2155c3442a8c56aa6f6ffa0dc9b6a308a7b1 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 3 Mar 2020 21:23:59 -0800 Subject: [PATCH 133/192] doc: remove em dashes Our documentation uses em dashes inconsistently. They are treated inconsistently typographically too. (For example, they are sometimes surrounded by spaces and sometimes not.) They are also often confused with ordinary hyphens such as in the CHANGELOG, where they are inadvertently mixed together in a single list. The difference is not obvious in the raw markdown but is very noticeable when rendered, appearing to be a typographical error (which it in fact is). The em dash is never needed. There are always alternatives. Remove em dashes entirely. PR-URL: https://github.com/nodejs/node/pull/32080 Reviewed-By: Richard Lau Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- CHANGELOG.md | 26 +++++++++++----------- doc/api/addons.md | 2 +- doc/api/errors.md | 6 ++--- doc/api/http.md | 6 ++--- doc/api/http2.md | 2 +- doc/api/modules.md | 2 +- doc/api/net.md | 2 +- doc/api/path.md | 4 ++-- doc/api/process.md | 2 +- doc/api/url.md | 2 +- doc/guides/backporting-to-release-lines.md | 2 +- doc/guides/contributing/issues.md | 4 ++-- doc/guides/doc-style-guide.md | 8 ++----- doc/guides/maintaining-icu.md | 2 +- tools/doc/versions.js | 2 +- 15 files changed, 34 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dec016f811..b32c52810cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,19 @@ Select a Node.js version below to view the changelog history: -* [Node.js 13](doc/changelogs/CHANGELOG_V13.md) - **Current** -* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) - **Long Term Support** -* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) - End-of-Life -* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) — Long Term Support -* [Node.js 9](doc/changelogs/CHANGELOG_V9.md) — End-of-Life -* [Node.js 8](doc/changelogs/CHANGELOG_V8.md) — End-of-Life -* [Node.js 7](doc/changelogs/CHANGELOG_V7.md) — End-of-Life -* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) — End-of-Life -* [Node.js 5](doc/changelogs/CHANGELOG_V5.md) — End-of-Life -* [Node.js 4](doc/changelogs/CHANGELOG_V4.md) — End-of-Life -* [io.js](doc/changelogs/CHANGELOG_IOJS.md) — End-of-Life -* [Node.js 0.12](doc/changelogs/CHANGELOG_V012.md) — End-of-Life -* [Node.js 0.10](doc/changelogs/CHANGELOG_V010.md) — End-of-Life +* [Node.js 13](doc/changelogs/CHANGELOG_V13.md) **Current** +* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) **Long Term Support** +* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) End-of-Life +* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) Long Term Support +* [Node.js 9](doc/changelogs/CHANGELOG_V9.md) End-of-Life +* [Node.js 8](doc/changelogs/CHANGELOG_V8.md) End-of-Life +* [Node.js 7](doc/changelogs/CHANGELOG_V7.md) End-of-Life +* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) End-of-Life +* [Node.js 5](doc/changelogs/CHANGELOG_V5.md) End-of-Life +* [Node.js 4](doc/changelogs/CHANGELOG_V4.md) End-of-Life +* [io.js](doc/changelogs/CHANGELOG_IOJS.md) End-of-Life +* [Node.js 0.12](doc/changelogs/CHANGELOG_V012.md) End-of-Life +* [Node.js 0.10](doc/changelogs/CHANGELOG_V010.md) End-of-Life * [Archive](doc/changelogs/CHANGELOG_ARCHIVE.md) Please use the following table to find the changelog for a specific Node.js diff --git a/doc/api/addons.md b/doc/api/addons.md index 6b77338348e..93f99afbeee 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -313,7 +313,7 @@ require('./build/Release/addon'); Once the source code has been written, it must be compiled into the binary `addon.node` file. To do so, create a file called `binding.gyp` in the top-level of the project describing the build configuration of the module -using a JSON-like format. This file is used by [node-gyp][] — a tool written +using a JSON-like format. This file is used by [node-gyp][], a tool written specifically to compile Node.js Addons. ```json diff --git a/doc/api/errors.md b/doc/api/errors.md index c244bfd7f00..26685f0ebcd 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -533,14 +533,14 @@ program. For a comprehensive list, see the [`errno`(3) man page][]. `ulimit -n 2048` in the same shell that will run the Node.js process. * `ENOENT` (No such file or directory): Commonly raised by [`fs`][] operations - to indicate that a component of the specified pathname does not exist — no + to indicate that a component of the specified pathname does not exist. No entity (file or directory) could be found by the given path. * `ENOTDIR` (Not a directory): A component of the given pathname existed, but was not a directory as expected. Commonly raised by [`fs.readdir`][]. * `ENOTEMPTY` (Directory not empty): A directory with entries was the target - of an operation that requires an empty directory — usually [`fs.unlink`][]. + of an operation that requires an empty directory, usually [`fs.unlink`][]. * `ENOTFOUND` (DNS lookup failed): Indicates a DNS failure of either `EAI_NODATA` or `EAI_NONAME`. This is not a standard POSIX error. @@ -555,7 +555,7 @@ program. For a comprehensive list, see the [`errno`(3) man page][]. * `ETIMEDOUT` (Operation timed out): A connect or send request failed because the connected party did not properly respond after a period of time. Usually - encountered by [`http`][] or [`net`][] — often a sign that a `socket.end()` + encountered by [`http`][] or [`net`][]. Often a sign that a `socket.end()` was not properly called. ## Class: `TypeError` diff --git a/doc/api/http.md b/doc/api/http.md index cf1e5d76bf4..f6aa93ecedc 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -9,7 +9,7 @@ To use the HTTP server and client one must `require('http')`. The HTTP interfaces in Node.js are designed to support many features of the protocol which have been traditionally difficult to use. In particular, large, possibly chunk-encoded, messages. The interface is -careful to never buffer entire requests or responses — the +careful to never buffer entire requests or responses, so the user is able to stream data. HTTP message headers are represented by an object like this: @@ -882,7 +882,7 @@ added: v0.1.29 Sends a chunk of the body. By calling this method many times, a request body can be sent to a -server — in that case it is suggested to use the +server. In that case, it is suggested to use the `['Transfer-Encoding', 'chunked']` header line when creating the request. @@ -1214,7 +1214,7 @@ added: v0.1.17 * Extends: {Stream} -This object is created internally by an HTTP server — not by the user. It is +This object is created internally by an HTTP server, not by the user. It is passed as the second parameter to the [`'request'`][] event. ### Event: `'close'` diff --git a/doc/api/http2.md b/doc/api/http2.md index 11f41224c4e..69252121f71 100644 --- a/doc/api/http2.md +++ b/doc/api/http2.md @@ -3008,7 +3008,7 @@ added: v8.4.0 * Extends: {Stream} -This object is created internally by an HTTP server — not by the user. It is +This object is created internally by an HTTP server, not by the user. It is passed as the second parameter to the [`'request'`][] event. #### Event: `'close'` diff --git a/doc/api/modules.md b/doc/api/modules.md index d69a2a2f151..4c91c27f52b 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -945,7 +945,7 @@ added: v0.3.7 * {Object} Provides general utility methods when interacting with instances of -`Module` — the `module` variable often seen in file modules. Accessed +`Module`, the `module` variable often seen in file modules. Accessed via `require('module')`. ### `module.builtinModules` diff --git a/doc/api/net.md b/doc/api/net.md index 84bda5e4e9b..ea0b9139fd3 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -939,7 +939,7 @@ added: v0.1.90 * Returns: {boolean} Sends data on the socket. The second parameter specifies the encoding in the -case of a string — it defaults to UTF8 encoding. +case of a string. It defaults to UTF8 encoding. Returns `true` if the entire data was flushed successfully to the kernel buffer. Returns `false` if all or part of the data was queued in user memory. diff --git a/doc/api/path.md b/doc/api/path.md index 477ef2cab09..c05a5c29efb 100644 --- a/doc/api/path.md +++ b/doc/api/path.md @@ -389,7 +389,7 @@ path.parse('/home/user/dir/file.txt'); │ root │ │ name │ ext │ " / home/user/dir / file .txt " └──────┴──────────────┴──────┴─────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` On Windows: @@ -411,7 +411,7 @@ path.parse('C:\\path\\dir\\file.txt'); │ root │ │ name │ ext │ " C:\ path\dir \ file .txt " └──────┴──────────────┴──────┴─────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` A [`TypeError`][] is thrown if `path` is not a string. diff --git a/doc/api/process.md b/doc/api/process.md index 598a75cc9a7..dfd98517829 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -190,7 +190,7 @@ rejection handler. There is no notion of a top level for a `Promise` chain at which rejections can always be handled. Being inherently asynchronous in nature, a `Promise` -rejection can be handled at a future point in time — possibly much later than +rejection can be handled at a future point in time, possibly much later than the event loop turn it takes for the `'unhandledRejection'` event to be emitted. Another way of stating this is that, unlike in synchronous code where there is diff --git a/doc/api/url.md b/doc/api/url.md index a198b699cd3..49f56509fe4 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -47,7 +47,7 @@ WHATWG URL's `origin` property includes `protocol` and `host`, but not ├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤ │ href │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` Parsing the URL string using the WHATWG API: diff --git a/doc/guides/backporting-to-release-lines.md b/doc/guides/backporting-to-release-lines.md index 4a4657d0815..55fbcb7e5bc 100644 --- a/doc/guides/backporting-to-release-lines.md +++ b/doc/guides/backporting-to-release-lines.md @@ -75,7 +75,7 @@ replace that with the staging branch for the targeted release line. 9. Open a pull request: 1. Be sure to target the `v10.x-staging` branch in the pull request. 1. Include the backport target in the pull request title in the following - format — `[v10.x backport] `. + format: `[v10.x backport] `. Example: `[v10.x backport] process: improve performance of nextTick` 1. Check the checkbox labeled "Allow edits from maintainers". 1. In the description add a reference to the original PR. diff --git a/doc/guides/contributing/issues.md b/doc/guides/contributing/issues.md index 054bbd7b277..31a47c1cd33 100644 --- a/doc/guides/contributing/issues.md +++ b/doc/guides/contributing/issues.md @@ -89,8 +89,8 @@ around it. Some contributors may have differing opinions about the issue, including whether the behavior being seen is a bug or a feature. This discussion is part of the process and should be kept focused, helpful, and professional. -Short, clipped responses—that provide neither additional context nor supporting -detail—are not helpful or professional. To many, such responses are simply +Short, clipped responses that provide neither additional context nor supporting +detail are not helpful or professional. To many, such responses are simply annoying and unfriendly. Contributors are encouraged to help one another make forward progress as much diff --git a/doc/guides/doc-style-guide.md b/doc/guides/doc-style-guide.md index f0221991b79..7ee2cac4ec3 100644 --- a/doc/guides/doc-style-guide.md +++ b/doc/guides/doc-style-guide.md @@ -25,12 +25,10 @@ * Outside of the wrapping element if the wrapping element contains only a fragment of a clause. * Documents must start with a level-one heading. -* Prefer affixing links to inlining links — prefer `[a link][]` to - `[a link](http://example.com)`. +* Prefer affixing links (`[a link][]`) to inlining links + (`[a link](http://example.com)`). * When documenting APIs, update the YAML comment associated with the API as appropriate. This is especially true when introducing or deprecating an API. -* Use [Em dashes][] ("—" or `Option+Shift+"-"` on macOS) surrounded by spaces, - as per [The New York Times Manual of Style and Usage][]. * For code blocks: * Use language aware fences. ("```js") * Code need not be complete. Treat code blocks as an illustration or aid to @@ -67,9 +65,7 @@ See also API documentation structure overview in [doctools README][]. -[Em dashes]: https://en.wikipedia.org/wiki/Dash#Em_dash [Javascript type]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Data_structures_and_types [serial commas]: https://en.wikipedia.org/wiki/Serial_comma -[The New York Times Manual of Style and Usage]: https://en.wikipedia.org/wiki/The_New_York_Times_Manual_of_Style_and_Usage [plugin]: https://editorconfig.org/#download [doctools README]: ../tools/doc/README.md diff --git a/doc/guides/maintaining-icu.md b/doc/guides/maintaining-icu.md index 4add40b7fa2..85f13c91298 100644 --- a/doc/guides/maintaining-icu.md +++ b/doc/guides/maintaining-icu.md @@ -218,7 +218,7 @@ following the steps above in the prior section of this document ought to be repeatable without concern for overriding a patch. 2. **Verifiability.** Given the number of files modified in an ICU PR, -a floating patch could easily be missed — or dropped altogether next time +a floating patch could easily be missed or dropped altogether next time something is landed. 3. **Compatibility.** There are a number of ways that ICU can be loaded into diff --git a/tools/doc/versions.js b/tools/doc/versions.js index 782ce90ee2c..bff6ac3617f 100644 --- a/tools/doc/versions.js +++ b/tools/doc/versions.js @@ -61,7 +61,7 @@ module.exports = { } } const ltsRE = /Long Term Support/i; - const versionRE = /\* \[Node\.js ([0-9.]+)\][^-—]+[-—]\s*(.*)\r?\n/g; + const versionRE = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g; _versions = []; let match; while ((match = versionRE.exec(changelog)) != null) { From 434d39dd670845cafd122fadb0a75d72c1658b05 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 2 Mar 2020 17:27:06 -0800 Subject: [PATCH 134/192] src,http2: introduce node_http_common The nghttp2 and nghttp3 (used in the QUIC implementation) share nearly identical structs for header handling. However, they differ enough that they need to be handled slightly different in each case. This PR includes some elements introduced in the QUIC PR separated out to make them independently reviewable, and updates the http2 implementation to use the shared utilities. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/32069 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- node.gyp | 2 + src/env.h | 2 +- src/node_http2.cc | 235 +++++-------- src/node_http2.h | 378 +++----------------- src/node_http_common-inl.h | 181 ++++++++++ src/node_http_common.h | 527 ++++++++++++++++++++++++++++ test/parallel/test-http2-binding.js | 11 +- 7 files changed, 862 insertions(+), 474 deletions(-) create mode 100644 src/node_http_common-inl.h create mode 100644 src/node_http_common.h diff --git a/node.gyp b/node.gyp index 790fd32e4db..b8731b21d5f 100644 --- a/node.gyp +++ b/node.gyp @@ -665,6 +665,8 @@ 'src/node_errors.h', 'src/node_file.h', 'src/node_file-inl.h', + 'src/node_http_common.h', + 'src/node_http_common-inl.h', 'src/node_http2.h', 'src/node_http2_state.h', 'src/node_i18n.h', diff --git a/src/env.h b/src/env.h index fc25f7a5b64..f48f74ca700 100644 --- a/src/env.h +++ b/src/env.h @@ -510,7 +510,7 @@ class IsolateData : public MemoryRetainer { #undef VS #undef VP - std::unordered_map> http2_static_strs; + std::unordered_map> http_static_strs; inline v8::Isolate* isolate() const; IsolateData(const IsolateData&) = delete; IsolateData& operator=(const IsolateData&) = delete; diff --git a/src/node_http2.cc b/src/node_http2.cc index 075d1b505e5..835e15587ff 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_http2.h" #include "node_http2_state.h" +#include "node_http_common-inl.h" #include "node_mem-inl.h" #include "node_perf.h" #include "node_revert.h" @@ -356,66 +357,6 @@ const char* Http2Session::TypeName() const { } } -// The Headers class initializes a proper array of nghttp2_nv structs -// containing the header name value pairs. -Headers::Headers(Isolate* isolate, - Local context, - Local headers) { - Local header_string = headers->Get(context, 0).ToLocalChecked(); - Local header_count = headers->Get(context, 1).ToLocalChecked(); - count_ = header_count.As()->Value(); - int header_string_len = header_string.As()->Length(); - - if (count_ == 0) { - CHECK_EQ(header_string_len, 0); - return; - } - - // Allocate a single buffer with count_ nghttp2_nv structs, followed - // by the raw header data as passed from JS. This looks like: - // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents | - buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) + - count_ * sizeof(nghttp2_nv) + - header_string_len); - // Make sure the start address is aligned appropriately for an nghttp2_nv*. - char* start = reinterpret_cast( - RoundUp(reinterpret_cast(*buf_), alignof(nghttp2_nv))); - char* header_contents = start + (count_ * sizeof(nghttp2_nv)); - nghttp2_nv* const nva = reinterpret_cast(start); - - CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); - CHECK_EQ(header_string.As()->WriteOneByte( - isolate, - reinterpret_cast(header_contents), - 0, - header_string_len, - String::NO_NULL_TERMINATION), - header_string_len); - - size_t n = 0; - char* p; - for (p = header_contents; p < header_contents + header_string_len; n++) { - if (n >= count_) { - // This can happen if a passed header contained a null byte. In that - // case, just provide nghttp2 with an invalid header to make it reject - // the headers list. - static uint8_t zero = '\0'; - nva[0].name = nva[0].value = &zero; - nva[0].namelen = nva[0].valuelen = 1; - count_ = 1; - return; - } - - nva[n].flags = NGHTTP2_NV_FLAG_NONE; - nva[n].name = reinterpret_cast(p); - nva[n].namelen = strlen(p); - p += nva[n].namelen + 1; - nva[n].value = reinterpret_cast(p); - nva[n].valuelen = strlen(p); - p += nva[n].valuelen + 1; - } -} - Origins::Origins(Isolate* isolate, Local context, Local origin_string, @@ -538,8 +479,8 @@ Http2Session::Http2Session(Environment* env, uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs(); max_header_pairs_ = type == NGHTTP2_SESSION_SERVER - ? std::max(maxHeaderPairs, 4U) // minimum # of request headers - : std::max(maxHeaderPairs, 1U); // minimum # of response headers + ? GetServerMaxHeaderPairs(maxHeaderPairs) + : GetClientMaxHeaderPairs(maxHeaderPairs); max_outstanding_pings_ = opts.GetMaxOutstandingPings(); max_outstanding_settings_ = opts.GetMaxOutstandingSettings(); @@ -1249,34 +1190,30 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) { if (stream->IsDestroyed()) return; - std::vector headers(stream->move_headers()); - DecrementCurrentSessionMemory(stream->current_headers_length_); - stream->current_headers_length_ = 0; - - // The headers are passed in above as a queue of nghttp2_header structs. + // The headers are stored as a vector of Http2Header instances. // The following converts that into a JS array with the structure: // [name1, value1, name2, value2, name3, value3, name3, value4] and so on. // That array is passed up to the JS layer and converted into an Object form // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it // this way for performance reasons (it's faster to generate and pass an // array than it is to generate and pass the object). - size_t headers_size = headers.size(); - std::vector> headers_v(headers_size * 2); - for (size_t i = 0; i < headers_size; ++i) { - const nghttp2_header& item = headers[i]; - // The header name and value are passed as external one-byte strings - headers_v[i * 2] = - ExternalHeader::New(this, item.name).ToLocalChecked(); - headers_v[i * 2 + 1] = - ExternalHeader::New(this, item.value).ToLocalChecked(); - } + + std::vector> headers_v(stream->headers_count() * 2); + stream->TransferHeaders([&](const Http2Header& header, size_t i) { + headers_v[i * 2] = header.GetName(this).ToLocalChecked(); + headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked(); + }); + CHECK_EQ(stream->headers_count(), 0); + + DecrementCurrentSessionMemory(stream->current_headers_length_); + stream->current_headers_length_ = 0; Local args[5] = { stream->object(), Integer::New(isolate, id), Integer::New(isolate, stream->headers_category()), Integer::New(isolate, frame->hd.flags), - Array::New(isolate, headers_v.data(), headers_size * 2)}; + Array::New(isolate, headers_v.data(), headers_v.size())}; MakeCallback(env()->http2session_on_headers_function(), arraysize(args), args); } @@ -1761,15 +1698,20 @@ int Http2Session::OnSendData( // Creates a new Http2Stream and submits a new http2 request. Http2Stream* Http2Session::SubmitRequest( nghttp2_priority_spec* prispec, - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options) { Debug(this, "submitting request"); Http2Scope h2scope(this); Http2Stream* stream = nullptr; Http2Stream::Provider::Stream prov(options); - *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr); + *ret = nghttp2_submit_request( + session_, + prispec, + headers.data(), + headers.length(), + *prov, + nullptr); CHECK_NE(*ret, NGHTTP2_ERR_NOMEM); if (LIKELY(*ret > 0)) stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options); @@ -1918,13 +1860,7 @@ Http2Stream::Http2Stream(Http2Session* session, session->AddStream(this); } - Http2Stream::~Http2Stream() { - for (nghttp2_header& header : current_headers_) { - nghttp2_rcbuf_decref(header.name); - nghttp2_rcbuf_decref(header.value); - } - if (!session_) return; Debug(this, "tearing down stream"); @@ -2026,7 +1962,7 @@ void Http2Stream::Destroy() { // Initiates a response on the Http2Stream using data provided via the // StreamBase Streams API. -int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) { +int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); Debug(this, "submitting response"); @@ -2037,21 +1973,30 @@ int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) { options |= STREAM_OPTION_EMPTY_PAYLOAD; Http2Stream::Provider::Stream prov(this, options); - int ret = nghttp2_submit_response(**session_, id_, nva, len, *prov); + int ret = nghttp2_submit_response( + **session_, + id_, + headers.data(), + headers.length(), + *prov); CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; } // Submit informational headers for a stream. -int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) { +int Http2Stream::SubmitInfo(const Http2Headers& headers) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); - Debug(this, "sending %d informational headers", len); - int ret = nghttp2_submit_headers(**session_, - NGHTTP2_FLAG_NONE, - id_, nullptr, - nva, len, nullptr); + Debug(this, "sending %d informational headers", headers.length()); + int ret = nghttp2_submit_headers( + **session_, + NGHTTP2_FLAG_NONE, + id_, + nullptr, + headers.data(), + headers.length(), + nullptr); CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; } @@ -2068,19 +2013,23 @@ void Http2Stream::OnTrailers() { } // Submit informational headers for a stream. -int Http2Stream::SubmitTrailers(nghttp2_nv* nva, size_t len) { +int Http2Stream::SubmitTrailers(const Http2Headers& headers) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); - Debug(this, "sending %d trailers", len); + Debug(this, "sending %d trailers", headers.length()); int ret; // Sending an empty trailers frame poses problems in Safari, Edge & IE. // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM // to indicate that the stream is ready to be closed. - if (len == 0) { + if (headers.length() == 0) { Http2Stream::Provider::Stream prov(this, 0); ret = nghttp2_submit_data(**session_, NGHTTP2_FLAG_END_STREAM, id_, *prov); } else { - ret = nghttp2_submit_trailer(**session_, id_, nva, len); + ret = nghttp2_submit_trailer( + **session_, + id_, + headers.data(), + headers.length()); } CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; @@ -2129,15 +2078,19 @@ void Http2Stream::FlushRstStream() { // Submit a push promise and create the associated Http2Stream if successful. -Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva, - size_t len, +Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers, int32_t* ret, int options) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); Debug(this, "sending push promise"); - *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE, - id_, nva, len, nullptr); + *ret = nghttp2_submit_push_promise( + **session_, + NGHTTP2_FLAG_NONE, + id_, + headers.data(), + headers.length(), + nullptr); CHECK_NE(*ret, NGHTTP2_ERR_NOMEM); Http2Stream* stream = nullptr; if (*ret > 0) { @@ -2221,12 +2174,12 @@ bool Http2Stream::AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags) { CHECK(!this->IsDestroyed()); - if (this->statistics_.first_header == 0) - this->statistics_.first_header = uv_hrtime(); - size_t name_len = nghttp2_rcbuf_get_buf(name).len; - if (name_len == 0) return true; // Ignore headers with empty names. - size_t value_len = nghttp2_rcbuf_get_buf(value).len; - size_t length = name_len + value_len + 32; + + if (Http2RcBufferPointer::IsZeroLength(name)) + return true; // Ignore empty headers. + + Http2Header header(env(), name, value, flags); + size_t length = header.length() + 32; // A header can only be added if we have not exceeded the maximum number // of headers and the session has memory available for it. if (!session_->IsAvailableSessionMemory(length) || @@ -2234,13 +2187,12 @@ bool Http2Stream::AddHeader(nghttp2_rcbuf* name, current_headers_length_ + length > max_header_length_) { return false; } - nghttp2_header header; - header.name = name; - header.value = value; - header.flags = flags; - current_headers_.push_back(header); - nghttp2_rcbuf_incref(name); - nghttp2_rcbuf_incref(value); + + if (statistics_.first_header == 0) + statistics_.first_header = uv_hrtime(); + + current_headers_.push_back(std::move(header)); + current_headers_length_ += length; session_->IncrementCurrentSessionMemory(length); return true; @@ -2487,21 +2439,20 @@ void Http2Session::Request(const FunctionCallbackInfo& args) { Http2Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); Environment* env = session->env(); - Local context = env->context(); - Isolate* isolate = env->isolate(); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); + int options = args[1]->IntegerValue(env->context()).ToChecked(); Http2Priority priority(env, args[2], args[3], args[4]); - Headers list(isolate, context, headers); - Debug(session, "request submitted"); int32_t ret = 0; Http2Stream* stream = - session->Http2Session::SubmitRequest(*priority, *list, list.length(), - &ret, options); + session->Http2Session::SubmitRequest( + *priority, + Http2Headers(env, headers), + &ret, + options); if (ret <= 0 || stream == nullptr) { Debug(session, "could not submit request: %s", nghttp2_strerror(ret)); @@ -2586,18 +2537,14 @@ void Http2Stream::RstStream(const FunctionCallbackInfo& args) { // outbound DATA frames. void Http2Stream::Respond(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); - - Headers list(isolate, context, headers); + int options = args[1]->IntegerValue(env->context()).ToChecked(); args.GetReturnValue().Set( - stream->SubmitResponse(*list, list.length(), options)); + stream->SubmitResponse(Http2Headers(env, headers), options)); Debug(stream, "response submitted"); } @@ -2605,31 +2552,24 @@ void Http2Stream::Respond(const FunctionCallbackInfo& args) { // Submits informational headers on the Http2Stream void Http2Stream::Info(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - Headers list(isolate, context, headers); - args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length())); - Debug(stream, "%d informational headers sent", list.length()); + args.GetReturnValue().Set(stream->SubmitInfo(Http2Headers(env, headers))); } // Submits trailing headers on the Http2Stream void Http2Stream::Trailers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - Headers list(isolate, context, headers); - args.GetReturnValue().Set(stream->SubmitTrailers(*list, list.length())); - Debug(stream, "%d trailing headers sent", list.length()); + args.GetReturnValue().Set( + stream->SubmitTrailers(Http2Headers(env, headers))); } // Grab the numeric id of the Http2Stream @@ -2650,21 +2590,18 @@ void Http2Stream::Destroy(const FunctionCallbackInfo& args) { // Initiate a Push Promise and create the associated Http2Stream void Http2Stream::PushPromise(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* parent; ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder()); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); - - Headers list(isolate, context, headers); + int options = args[1]->IntegerValue(env->context()).ToChecked(); Debug(parent, "creating push promise"); int32_t ret = 0; - Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(), - &ret, options); + Http2Stream* stream = + parent->SubmitPushPromise(Http2Headers(env, headers), &ret, options); + if (ret <= 0 || stream == nullptr) { Debug(parent, "failed to create push stream: %d", ret); return args.GetReturnValue().Set(ret); @@ -2940,12 +2877,6 @@ void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("buf", buf); } - -void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len); - tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len); -} - void SetCallbackFunctions(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 11); diff --git a/src/node_http2.h b/src/node_http2.h index b468aac175d..97560c8a5bf 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -8,6 +8,7 @@ #include "nghttp2/nghttp2.h" #include "node_http2_state.h" +#include "node_http_common.h" #include "node_mem.h" #include "node_perf.h" #include "stream_base-inl.h" @@ -50,8 +51,36 @@ using performance::PerformanceEntry; #define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE #define MAX_INITIAL_WINDOW_SIZE 2147483647 -#define MAX_MAX_HEADER_LIST_SIZE 16777215u -#define DEFAULT_MAX_HEADER_LIST_PAIRS 128u +struct Http2HeadersTraits { + typedef nghttp2_nv nv_t; + static const uint8_t kNoneFlag = NGHTTP2_NV_FLAG_NONE; +}; + +struct Http2RcBufferPointerTraits { + typedef nghttp2_rcbuf rcbuf_t; + typedef nghttp2_vec vector_t; + + static void inc(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + nghttp2_rcbuf_incref(buf); + } + static void dec(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + nghttp2_rcbuf_decref(buf); + } + static vector_t get_vec(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + return nghttp2_rcbuf_get_buf(buf); + } + static bool is_static(const rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + return nghttp2_rcbuf_is_static(buf); + } +}; + +using Http2Headers = NgHeaders; +using Http2RcBufferPointer = NgRcBufPointer; + enum nghttp2_session_type { NGHTTP2_SESSION_SERVER, @@ -96,224 +125,6 @@ struct nghttp2_stream_write : public MemoryRetainer { SET_SELF_SIZE(nghttp2_stream_write) }; -struct nghttp2_header : public MemoryRetainer { - nghttp2_rcbuf* name = nullptr; - nghttp2_rcbuf* value = nullptr; - uint8_t flags = 0; - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(nghttp2_header) - SET_SELF_SIZE(nghttp2_header) -}; - - -// Unlike the HTTP/1 implementation, the HTTP/2 implementation is not limited -// to a fixed number of known supported HTTP methods. These constants, therefore -// are provided strictly as a convenience to users and are exposed via the -// require('http2').constants object. -#define HTTP_KNOWN_METHODS(V) \ - V(ACL, "ACL") \ - V(BASELINE_CONTROL, "BASELINE-CONTROL") \ - V(BIND, "BIND") \ - V(CHECKIN, "CHECKIN") \ - V(CHECKOUT, "CHECKOUT") \ - V(CONNECT, "CONNECT") \ - V(COPY, "COPY") \ - V(DELETE, "DELETE") \ - V(GET, "GET") \ - V(HEAD, "HEAD") \ - V(LABEL, "LABEL") \ - V(LINK, "LINK") \ - V(LOCK, "LOCK") \ - V(MERGE, "MERGE") \ - V(MKACTIVITY, "MKACTIVITY") \ - V(MKCALENDAR, "MKCALENDAR") \ - V(MKCOL, "MKCOL") \ - V(MKREDIRECTREF, "MKREDIRECTREF") \ - V(MKWORKSPACE, "MKWORKSPACE") \ - V(MOVE, "MOVE") \ - V(OPTIONS, "OPTIONS") \ - V(ORDERPATCH, "ORDERPATCH") \ - V(PATCH, "PATCH") \ - V(POST, "POST") \ - V(PRI, "PRI") \ - V(PROPFIND, "PROPFIND") \ - V(PROPPATCH, "PROPPATCH") \ - V(PUT, "PUT") \ - V(REBIND, "REBIND") \ - V(REPORT, "REPORT") \ - V(SEARCH, "SEARCH") \ - V(TRACE, "TRACE") \ - V(UNBIND, "UNBIND") \ - V(UNCHECKOUT, "UNCHECKOUT") \ - V(UNLINK, "UNLINK") \ - V(UNLOCK, "UNLOCK") \ - V(UPDATE, "UPDATE") \ - V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \ - V(VERSION_CONTROL, "VERSION-CONTROL") - -// These are provided strictly as a convenience to users and are exposed via the -// require('http2').constants objects -#define HTTP_KNOWN_HEADERS(V) \ - V(STATUS, ":status") \ - V(METHOD, ":method") \ - V(AUTHORITY, ":authority") \ - V(SCHEME, ":scheme") \ - V(PATH, ":path") \ - V(PROTOCOL, ":protocol") \ - V(ACCEPT_CHARSET, "accept-charset") \ - V(ACCEPT_ENCODING, "accept-encoding") \ - V(ACCEPT_LANGUAGE, "accept-language") \ - V(ACCEPT_RANGES, "accept-ranges") \ - V(ACCEPT, "accept") \ - V(ACCESS_CONTROL_ALLOW_CREDENTIALS, "access-control-allow-credentials") \ - V(ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers") \ - V(ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods") \ - V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \ - V(ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers") \ - V(ACCESS_CONTROL_MAX_AGE, "access-control-max-age") \ - V(ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers") \ - V(ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method") \ - V(AGE, "age") \ - V(ALLOW, "allow") \ - V(AUTHORIZATION, "authorization") \ - V(CACHE_CONTROL, "cache-control") \ - V(CONNECTION, "connection") \ - V(CONTENT_DISPOSITION, "content-disposition") \ - V(CONTENT_ENCODING, "content-encoding") \ - V(CONTENT_LANGUAGE, "content-language") \ - V(CONTENT_LENGTH, "content-length") \ - V(CONTENT_LOCATION, "content-location") \ - V(CONTENT_MD5, "content-md5") \ - V(CONTENT_RANGE, "content-range") \ - V(CONTENT_TYPE, "content-type") \ - V(COOKIE, "cookie") \ - V(DATE, "date") \ - V(DNT, "dnt") \ - V(ETAG, "etag") \ - V(EXPECT, "expect") \ - V(EXPIRES, "expires") \ - V(FORWARDED, "forwarded") \ - V(FROM, "from") \ - V(HOST, "host") \ - V(IF_MATCH, "if-match") \ - V(IF_MODIFIED_SINCE, "if-modified-since") \ - V(IF_NONE_MATCH, "if-none-match") \ - V(IF_RANGE, "if-range") \ - V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \ - V(LAST_MODIFIED, "last-modified") \ - V(LINK, "link") \ - V(LOCATION, "location") \ - V(MAX_FORWARDS, "max-forwards") \ - V(PREFER, "prefer") \ - V(PROXY_AUTHENTICATE, "proxy-authenticate") \ - V(PROXY_AUTHORIZATION, "proxy-authorization") \ - V(RANGE, "range") \ - V(REFERER, "referer") \ - V(REFRESH, "refresh") \ - V(RETRY_AFTER, "retry-after") \ - V(SERVER, "server") \ - V(SET_COOKIE, "set-cookie") \ - V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \ - V(TRAILER, "trailer") \ - V(TRANSFER_ENCODING, "transfer-encoding") \ - V(TE, "te") \ - V(TK, "tk") \ - V(UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests") \ - V(UPGRADE, "upgrade") \ - V(USER_AGENT, "user-agent") \ - V(VARY, "vary") \ - V(VIA, "via") \ - V(WARNING, "warning") \ - V(WWW_AUTHENTICATE, "www-authenticate") \ - V(X_CONTENT_TYPE_OPTIONS, "x-content-type-options") \ - V(X_FRAME_OPTIONS, "x-frame-options") \ - V(HTTP2_SETTINGS, "http2-settings") \ - V(KEEP_ALIVE, "keep-alive") \ - V(PROXY_CONNECTION, "proxy-connection") - -enum http_known_headers { - HTTP_KNOWN_HEADER_MIN, -#define V(name, value) HTTP_HEADER_##name, - HTTP_KNOWN_HEADERS(V) -#undef V - HTTP_KNOWN_HEADER_MAX -}; - -// While some of these codes are used within the HTTP/2 implementation in -// core, they are provided strictly as a convenience to users and are exposed -// via the require('http2').constants object. -#define HTTP_STATUS_CODES(V) \ - V(CONTINUE, 100) \ - V(SWITCHING_PROTOCOLS, 101) \ - V(PROCESSING, 102) \ - V(EARLY_HINTS, 103) \ - V(OK, 200) \ - V(CREATED, 201) \ - V(ACCEPTED, 202) \ - V(NON_AUTHORITATIVE_INFORMATION, 203) \ - V(NO_CONTENT, 204) \ - V(RESET_CONTENT, 205) \ - V(PARTIAL_CONTENT, 206) \ - V(MULTI_STATUS, 207) \ - V(ALREADY_REPORTED, 208) \ - V(IM_USED, 226) \ - V(MULTIPLE_CHOICES, 300) \ - V(MOVED_PERMANENTLY, 301) \ - V(FOUND, 302) \ - V(SEE_OTHER, 303) \ - V(NOT_MODIFIED, 304) \ - V(USE_PROXY, 305) \ - V(TEMPORARY_REDIRECT, 307) \ - V(PERMANENT_REDIRECT, 308) \ - V(BAD_REQUEST, 400) \ - V(UNAUTHORIZED, 401) \ - V(PAYMENT_REQUIRED, 402) \ - V(FORBIDDEN, 403) \ - V(NOT_FOUND, 404) \ - V(METHOD_NOT_ALLOWED, 405) \ - V(NOT_ACCEPTABLE, 406) \ - V(PROXY_AUTHENTICATION_REQUIRED, 407) \ - V(REQUEST_TIMEOUT, 408) \ - V(CONFLICT, 409) \ - V(GONE, 410) \ - V(LENGTH_REQUIRED, 411) \ - V(PRECONDITION_FAILED, 412) \ - V(PAYLOAD_TOO_LARGE, 413) \ - V(URI_TOO_LONG, 414) \ - V(UNSUPPORTED_MEDIA_TYPE, 415) \ - V(RANGE_NOT_SATISFIABLE, 416) \ - V(EXPECTATION_FAILED, 417) \ - V(TEAPOT, 418) \ - V(MISDIRECTED_REQUEST, 421) \ - V(UNPROCESSABLE_ENTITY, 422) \ - V(LOCKED, 423) \ - V(FAILED_DEPENDENCY, 424) \ - V(TOO_EARLY, 425) \ - V(UPGRADE_REQUIRED, 426) \ - V(PRECONDITION_REQUIRED, 428) \ - V(TOO_MANY_REQUESTS, 429) \ - V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \ - V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \ - V(INTERNAL_SERVER_ERROR, 500) \ - V(NOT_IMPLEMENTED, 501) \ - V(BAD_GATEWAY, 502) \ - V(SERVICE_UNAVAILABLE, 503) \ - V(GATEWAY_TIMEOUT, 504) \ - V(HTTP_VERSION_NOT_SUPPORTED, 505) \ - V(VARIANT_ALSO_NEGOTIATES, 506) \ - V(INSUFFICIENT_STORAGE, 507) \ - V(LOOP_DETECTED, 508) \ - V(BANDWIDTH_LIMIT_EXCEEDED, 509) \ - V(NOT_EXTENDED, 510) \ - V(NETWORK_AUTHENTICATION_REQUIRED, 511) - -enum http_status_codes { -#define V(name, code) HTTP_STATUS_##name = code, - HTTP_STATUS_CODES(V) -#undef V -}; - // The Padding Strategy determines the method by which extra padding is // selected for HEADERS and DATA frames. These are configurable via the // options passed in to a Http2Session object. @@ -446,6 +257,17 @@ class Http2StreamListener : public StreamListener { void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; }; +struct Http2HeaderTraits { + typedef Http2RcBufferPointer rcbufferpointer_t; + typedef Http2Session allocator_t; + + // HTTP/2 does not support identifying header names by token id. + // HTTP/3 will, however, so we prepare for that now. + static const char* ToHttpHeaderName(int32_t token) { return nullptr; } +}; + +using Http2Header = NgHeader; + class Http2Stream : public AsyncWrap, public StreamBase { public: @@ -476,13 +298,13 @@ class Http2Stream : public AsyncWrap, bool HasWantsWrite() const override { return true; } // Initiate a response on this stream. - int SubmitResponse(nghttp2_nv* nva, size_t len, int options); + int SubmitResponse(const Http2Headers& headers, int options); // Submit informational headers for this stream - int SubmitInfo(nghttp2_nv* nva, size_t len); + int SubmitInfo(const Http2Headers& headers); // Submit trailing headers for this stream - int SubmitTrailers(nghttp2_nv* nva, size_t len); + int SubmitTrailers(const Http2Headers& headers); void OnTrailers(); // Submit a PRIORITY frame for this stream @@ -495,8 +317,7 @@ class Http2Stream : public AsyncWrap, // Submits a PUSH_PROMISE frame with this stream as the parent. Http2Stream* SubmitPushPromise( - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options = 0); @@ -545,8 +366,16 @@ class Http2Stream : public AsyncWrap, bool AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags); - inline std::vector move_headers() { - return std::move(current_headers_); + template + void TransferHeaders(Fn&& fn) { + size_t i = 0; + for (const auto& header : current_headers_ ) + fn(header, i++); + current_headers_.clear(); + } + + size_t headers_count() const { + return current_headers_.size(); } inline nghttp2_headers_category headers_category() const { @@ -625,7 +454,7 @@ class Http2Stream : public AsyncWrap, // signalling the end of the HEADERS frame nghttp2_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS; uint32_t current_headers_length_ = 0; // total number of octets - std::vector current_headers_; + std::vector current_headers_; // This keeps track of the amount of data read from the socket while the // socket was in paused mode. When `ReadStart()` is called (and not before @@ -743,8 +572,7 @@ class Http2Session : public AsyncWrap, // This only works if the session is a client session. Http2Stream* SubmitRequest( nghttp2_priority_spec* prispec, - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options = 0); @@ -1189,96 +1017,6 @@ class Http2Session::Http2Settings : public AsyncWrap { nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT]; }; -class ExternalHeader : - public String::ExternalOneByteStringResource { - public: - explicit ExternalHeader(nghttp2_rcbuf* buf) - : buf_(buf), vec_(nghttp2_rcbuf_get_buf(buf)) { - } - - ~ExternalHeader() override { - nghttp2_rcbuf_decref(buf_); - buf_ = nullptr; - } - - const char* data() const override { - return const_cast(reinterpret_cast(vec_.base)); - } - - size_t length() const override { - return vec_.len; - } - - static inline - MaybeLocal GetInternalizedString(Environment* env, - const nghttp2_vec& vec) { - return String::NewFromOneByte(env->isolate(), - vec.base, - v8::NewStringType::kInternalized, - vec.len); - } - - template - static MaybeLocal New(Http2Session* session, nghttp2_rcbuf* buf) { - Environment* env = session->env(); - if (nghttp2_rcbuf_is_static(buf)) { - auto& static_str_map = env->isolate_data()->http2_static_strs; - v8::Eternal& eternal = static_str_map[buf]; - if (eternal.IsEmpty()) { - Local str = - GetInternalizedString(env, nghttp2_rcbuf_get_buf(buf)) - .ToLocalChecked(); - eternal.Set(env->isolate(), str); - return str; - } - return eternal.Get(env->isolate()); - } - - nghttp2_vec vec = nghttp2_rcbuf_get_buf(buf); - if (vec.len == 0) { - nghttp2_rcbuf_decref(buf); - return String::Empty(env->isolate()); - } - - if (may_internalize && vec.len < 64) { - nghttp2_rcbuf_decref(buf); - // This is a short header name, so there is a good chance V8 already has - // it internalized. - return GetInternalizedString(env, vec); - } - - session->StopTrackingRcbuf(buf); - ExternalHeader* h_str = new ExternalHeader(buf); - MaybeLocal str = String::NewExternalOneByte(env->isolate(), h_str); - if (str.IsEmpty()) - delete h_str; - - return str; - } - - private: - nghttp2_rcbuf* buf_; - nghttp2_vec vec_; -}; - -class Headers { - public: - Headers(Isolate* isolate, Local context, Local headers); - ~Headers() = default; - - nghttp2_nv* operator*() { - return reinterpret_cast(*buf_); - } - - size_t length() const { - return count_; - } - - private: - size_t count_; - MaybeStackBuffer buf_; -}; - class Origins { public: Origins(Isolate* isolate, diff --git a/src/node_http_common-inl.h b/src/node_http_common-inl.h new file mode 100644 index 00000000000..1bc7b46d63a --- /dev/null +++ b/src/node_http_common-inl.h @@ -0,0 +1,181 @@ +#ifndef SRC_NODE_HTTP_COMMON_INL_H_ +#define SRC_NODE_HTTP_COMMON_INL_H_ + +#include "node_http_common.h" +#include "node.h" +#include "node_mem-inl.h" +#include "env-inl.h" +#include "v8.h" + +#include + +namespace node { + +template +NgHeaders::NgHeaders(Environment* env, v8::Local headers) { + v8::Local header_string = + headers->Get(env->context(), 0).ToLocalChecked(); + v8::Local header_count = + headers->Get(env->context(), 1).ToLocalChecked(); + CHECK(header_count->IsUint32()); + CHECK(header_string->IsString()); + count_ = header_count.As()->Value(); + int header_string_len = header_string.As()->Length(); + + if (count_ == 0) { + CHECK_EQ(header_string_len, 0); + return; + } + + buf_.AllocateSufficientStorage((alignof(nv_t) - 1) + + count_ * sizeof(nv_t) + + header_string_len); + + char* start = reinterpret_cast( + RoundUp(reinterpret_cast(*buf_), alignof(nv_t))); + char* header_contents = start + (count_ * sizeof(nv_t)); + nv_t* const nva = reinterpret_cast(start); + + CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); + CHECK_EQ(header_string.As()->WriteOneByte( + env->isolate(), + reinterpret_cast(header_contents), + 0, + header_string_len, + v8::String::NO_NULL_TERMINATION), + header_string_len); + + size_t n = 0; + char* p; + for (p = header_contents; p < header_contents + header_string_len; n++) { + if (n >= count_) { + static uint8_t zero = '\0'; + nva[0].name = nva[0].value = &zero; + nva[0].namelen = nva[0].valuelen = 1; + count_ = 1; + return; + } + + nva[n].flags = T::kNoneFlag; + nva[n].name = reinterpret_cast(p); + nva[n].namelen = strlen(p); + p += nva[n].namelen + 1; + nva[n].value = reinterpret_cast(p); + nva[n].valuelen = strlen(p); + p += nva[n].valuelen + 1; + } +} + +size_t GetClientMaxHeaderPairs(size_t max_header_pairs) { + static constexpr size_t min_header_pairs = 1; + return std::max(max_header_pairs, min_header_pairs); +} + +size_t GetServerMaxHeaderPairs(size_t max_header_pairs) { + static constexpr size_t min_header_pairs = 4; + return std::max(max_header_pairs, min_header_pairs); +} + +template +bool NgHeader::IsZeroLength( + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value) { + return IsZeroLength(-1, name, value); +} + +template +bool NgHeader::IsZeroLength( + int32_t token, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value) { + + if (NgHeader::rcbufferpointer_t::IsZeroLength(value)) + return true; + + const char* header_name = T::ToHttpHeaderName(token); + return header_name != nullptr || + NgHeader::rcbufferpointer_t::IsZeroLength(name); +} + +template +NgHeader::NgHeader( + Environment* env, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value, + uint8_t flags) + : NgHeader(env, -1, name, value, flags) {} + +template +NgHeader::NgHeader( + Environment* env, + int32_t token, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value, + uint8_t flags) : env_(env), token_(token), flags_(flags) { + if (token == -1) { + CHECK_NOT_NULL(name); + name_.reset(name, true); // Internalizable + } + CHECK_NOT_NULL(value); + name_.reset(name, true); // Internalizable + value_.reset(value); +} + +template +NgHeader::NgHeader(NgHeader&& other) noexcept + : token_(other.token_), + name_(std::move(other.name_)), + value_(std::move(other.value_)), + flags_(other.flags_) { + other.token_ = -1; + other.flags_ = 0; + other.env_ = nullptr; +} + +template +v8::MaybeLocal NgHeader::GetName( + NgHeader::allocator_t* allocator) const { + + // Not all instances will support using token id's for header names. + // HTTP/2 specifically does not support it. + const char* header_name = T::ToHttpHeaderName(token_); + + // If header_name is not nullptr, then it is a known header with + // a statically defined name. We can safely internalize it here. + if (header_name != nullptr) { + auto& static_str_map = env_->isolate_data()->http_static_strs; + v8::Eternal eternal = static_str_map[header_name]; + if (eternal.IsEmpty()) { + v8::Local str = OneByteString(env_->isolate(), header_name); + eternal.Set(env_->isolate(), str); + return str; + } + return eternal.Get(env_->isolate()); + } + return rcbufferpointer_t::External::New(allocator, name_); +} + +template +v8::MaybeLocal NgHeader::GetValue( + NgHeader::allocator_t* allocator) const { + return rcbufferpointer_t::External::New(allocator, value_); +} + +template +std::string NgHeader::name() const { + return name_.str(); +} + +template +std::string NgHeader::value() const { + return value_.str(); +} + +template +size_t NgHeader::length() const { + return name_.len() + value_.len(); +} + +} // namespace node + +#endif // SRC_NODE_HTTP_COMMON_INL_H_ diff --git a/src/node_http_common.h b/src/node_http_common.h new file mode 100644 index 00000000000..d43418ba6f2 --- /dev/null +++ b/src/node_http_common.h @@ -0,0 +1,527 @@ +#ifndef SRC_NODE_HTTP_COMMON_H_ +#define SRC_NODE_HTTP_COMMON_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" +#include "node_mem.h" + +#include + +namespace node { + +class Environment; + +#define MAX_MAX_HEADER_LIST_SIZE 16777215u +#define DEFAULT_MAX_HEADER_LIST_PAIRS 128u +#define DEFAULT_MAX_HEADER_LENGTH 8192 + +#define HTTP_SPECIAL_HEADERS(V) \ + V(STATUS, ":status") \ + V(METHOD, ":method") \ + V(AUTHORITY, ":authority") \ + V(SCHEME, ":scheme") \ + V(PATH, ":path") \ + V(PROTOCOL, ":protocol") + +#define HTTP_REGULAR_HEADERS(V) \ + V(ACCEPT_ENCODING, "accept-encoding") \ + V(ACCEPT_LANGUAGE, "accept-language") \ + V(ACCEPT_RANGES, "accept-ranges") \ + V(ACCEPT, "accept") \ + V(ACCESS_CONTROL_ALLOW_CREDENTIALS, "access-control-allow-credentials") \ + V(ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers") \ + V(ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods") \ + V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \ + V(ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers") \ + V(ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers") \ + V(ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method") \ + V(AGE, "age") \ + V(AUTHORIZATION, "authorization") \ + V(CACHE_CONTROL, "cache-control") \ + V(CONNECTION, "connection") \ + V(CONTENT_DISPOSITION, "content-disposition") \ + V(CONTENT_ENCODING, "content-encoding") \ + V(CONTENT_LENGTH, "content-length") \ + V(CONTENT_TYPE, "content-type") \ + V(COOKIE, "cookie") \ + V(DATE, "date") \ + V(ETAG, "etag") \ + V(FORWARDED, "forwarded") \ + V(HOST, "host") \ + V(IF_MODIFIED_SINCE, "if-modified-since") \ + V(IF_NONE_MATCH, "if-none-match") \ + V(IF_RANGE, "if-range") \ + V(LAST_MODIFIED, "last-modified") \ + V(LINK, "link") \ + V(LOCATION, "location") \ + V(RANGE, "range") \ + V(REFERER, "referer") \ + V(SERVER, "server") \ + V(SET_COOKIE, "set-cookie") \ + V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \ + V(TRANSFER_ENCODING, "transfer-encoding") \ + V(TE, "te") \ + V(UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests") \ + V(UPGRADE, "upgrade") \ + V(USER_AGENT, "user-agent") \ + V(VARY, "vary") \ + V(X_CONTENT_TYPE_OPTIONS, "x-content-type-options") \ + V(X_FRAME_OPTIONS, "x-frame-options") \ + V(KEEP_ALIVE, "keep-alive") \ + V(PROXY_CONNECTION, "proxy-connection") \ + V(X_XSS_PROTECTION, "x-xss-protection") \ + V(ALT_SVC, "alt-svc") \ + V(CONTENT_SECURITY_POLICY, "content-security-policy") \ + V(EARLY_DATA, "early-data") \ + V(EXPECT_CT, "expect-ct") \ + V(ORIGIN, "origin") \ + V(PURPOSE, "purpose") \ + V(TIMING_ALLOW_ORIGIN, "timing-allow-origin") \ + V(X_FORWARDED_FOR, "x-forwarded-for") + +#define HTTP_ADDITIONAL_HEADERS(V) \ + V(ACCEPT_CHARSET, "accept-charset") \ + V(ACCESS_CONTROL_MAX_AGE, "access-control-max-age") \ + V(ALLOW, "allow") \ + V(CONTENT_LANGUAGE, "content-language") \ + V(CONTENT_LOCATION, "content-location") \ + V(CONTENT_MD5, "content-md5") \ + V(CONTENT_RANGE, "content-range") \ + V(DNT, "dnt") \ + V(EXPECT, "expect") \ + V(EXPIRES, "expires") \ + V(FROM, "from") \ + V(IF_MATCH, "if-match") \ + V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \ + V(MAX_FORWARDS, "max-forwards") \ + V(PREFER, "prefer") \ + V(PROXY_AUTHENTICATE, "proxy-authenticate") \ + V(PROXY_AUTHORIZATION, "proxy-authorization") \ + V(REFRESH, "refresh") \ + V(RETRY_AFTER, "retry-after") \ + V(TRAILER, "trailer") \ + V(TK, "tk") \ + V(VIA, "via") \ + V(WARNING, "warning") \ + V(WWW_AUTHENTICATE, "www-authenticate") \ + V(HTTP2_SETTINGS, "http2-settings") + +// Special and regular headers are handled specifically by the HTTP/2 (and +// later HTTP/3) implementation. +#define HTTP_KNOWN_HEADERS(V) \ + HTTP_SPECIAL_HEADERS(V) \ + HTTP_REGULAR_HEADERS(V) \ + HTTP_ADDITIONAL_HEADERS(V) + +enum http_known_headers { + HTTP_KNOWN_HEADER_MIN, +#define V(name, value) HTTP_HEADER_##name, + HTTP_KNOWN_HEADERS(V) +#undef V + HTTP_KNOWN_HEADER_MAX +}; + +#define HTTP_STATUS_CODES(V) \ + V(CONTINUE, 100) \ + V(SWITCHING_PROTOCOLS, 101) \ + V(PROCESSING, 102) \ + V(EARLY_HINTS, 103) \ + V(OK, 200) \ + V(CREATED, 201) \ + V(ACCEPTED, 202) \ + V(NON_AUTHORITATIVE_INFORMATION, 203) \ + V(NO_CONTENT, 204) \ + V(RESET_CONTENT, 205) \ + V(PARTIAL_CONTENT, 206) \ + V(MULTI_STATUS, 207) \ + V(ALREADY_REPORTED, 208) \ + V(IM_USED, 226) \ + V(MULTIPLE_CHOICES, 300) \ + V(MOVED_PERMANENTLY, 301) \ + V(FOUND, 302) \ + V(SEE_OTHER, 303) \ + V(NOT_MODIFIED, 304) \ + V(USE_PROXY, 305) \ + V(TEMPORARY_REDIRECT, 307) \ + V(PERMANENT_REDIRECT, 308) \ + V(BAD_REQUEST, 400) \ + V(UNAUTHORIZED, 401) \ + V(PAYMENT_REQUIRED, 402) \ + V(FORBIDDEN, 403) \ + V(NOT_FOUND, 404) \ + V(METHOD_NOT_ALLOWED, 405) \ + V(NOT_ACCEPTABLE, 406) \ + V(PROXY_AUTHENTICATION_REQUIRED, 407) \ + V(REQUEST_TIMEOUT, 408) \ + V(CONFLICT, 409) \ + V(GONE, 410) \ + V(LENGTH_REQUIRED, 411) \ + V(PRECONDITION_FAILED, 412) \ + V(PAYLOAD_TOO_LARGE, 413) \ + V(URI_TOO_LONG, 414) \ + V(UNSUPPORTED_MEDIA_TYPE, 415) \ + V(RANGE_NOT_SATISFIABLE, 416) \ + V(EXPECTATION_FAILED, 417) \ + V(TEAPOT, 418) \ + V(MISDIRECTED_REQUEST, 421) \ + V(UNPROCESSABLE_ENTITY, 422) \ + V(LOCKED, 423) \ + V(FAILED_DEPENDENCY, 424) \ + V(TOO_EARLY, 425) \ + V(UPGRADE_REQUIRED, 426) \ + V(PRECONDITION_REQUIRED, 428) \ + V(TOO_MANY_REQUESTS, 429) \ + V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \ + V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \ + V(INTERNAL_SERVER_ERROR, 500) \ + V(NOT_IMPLEMENTED, 501) \ + V(BAD_GATEWAY, 502) \ + V(SERVICE_UNAVAILABLE, 503) \ + V(GATEWAY_TIMEOUT, 504) \ + V(HTTP_VERSION_NOT_SUPPORTED, 505) \ + V(VARIANT_ALSO_NEGOTIATES, 506) \ + V(INSUFFICIENT_STORAGE, 507) \ + V(LOOP_DETECTED, 508) \ + V(BANDWIDTH_LIMIT_EXCEEDED, 509) \ + V(NOT_EXTENDED, 510) \ + V(NETWORK_AUTHENTICATION_REQUIRED, 511) + +enum http_status_codes { +#define V(name, code) HTTP_STATUS_##name = code, + HTTP_STATUS_CODES(V) +#undef V +}; + +// Unlike the HTTP/1 implementation, the HTTP/2 implementation is not limited +// to a fixed number of known supported HTTP methods. These constants, therefore +// are provided strictly as a convenience to users and are exposed via the +// require('http2').constants object. +#define HTTP_KNOWN_METHODS(V) \ + V(ACL, "ACL") \ + V(BASELINE_CONTROL, "BASELINE-CONTROL") \ + V(BIND, "BIND") \ + V(CHECKIN, "CHECKIN") \ + V(CHECKOUT, "CHECKOUT") \ + V(CONNECT, "CONNECT") \ + V(COPY, "COPY") \ + V(DELETE, "DELETE") \ + V(GET, "GET") \ + V(HEAD, "HEAD") \ + V(LABEL, "LABEL") \ + V(LINK, "LINK") \ + V(LOCK, "LOCK") \ + V(MERGE, "MERGE") \ + V(MKACTIVITY, "MKACTIVITY") \ + V(MKCALENDAR, "MKCALENDAR") \ + V(MKCOL, "MKCOL") \ + V(MKREDIRECTREF, "MKREDIRECTREF") \ + V(MKWORKSPACE, "MKWORKSPACE") \ + V(MOVE, "MOVE") \ + V(OPTIONS, "OPTIONS") \ + V(ORDERPATCH, "ORDERPATCH") \ + V(PATCH, "PATCH") \ + V(POST, "POST") \ + V(PRI, "PRI") \ + V(PROPFIND, "PROPFIND") \ + V(PROPPATCH, "PROPPATCH") \ + V(PUT, "PUT") \ + V(REBIND, "REBIND") \ + V(REPORT, "REPORT") \ + V(SEARCH, "SEARCH") \ + V(TRACE, "TRACE") \ + V(UNBIND, "UNBIND") \ + V(UNCHECKOUT, "UNCHECKOUT") \ + V(UNLINK, "UNLINK") \ + V(UNLOCK, "UNLOCK") \ + V(UPDATE, "UPDATE") \ + V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \ + V(VERSION_CONTROL, "VERSION-CONTROL") + +// NgHeaders takes as input a block of headers provided by the +// JavaScript side (see http2's mapToHeaders function) and +// converts it into a array of ng header structs. This is done +// generically to handle both http/2 and (in the future) http/3, +// which use nearly identical structs. The template parameter +// takes a Traits type that defines the ng header struct and +// the kNoneFlag value. See Http2HeaderTraits in node_http2.h +// for an example. +template +class NgHeaders { + public: + typedef typename T::nv_t nv_t; + inline NgHeaders(Environment* env, v8::Local headers); + ~NgHeaders() = default; + + const nv_t* operator*() const { + return reinterpret_cast(*buf_); + } + + const nv_t* data() const { + return reinterpret_cast(*buf_); + } + + size_t length() const { + return count_; + } + + private: + size_t count_; + MaybeStackBuffer buf_; +}; + +// The ng libraries (nghttp2 and nghttp3) each use nearly identical +// reference counted structures for retaining header name and value +// information in memory until the application is done with it. +// The NgRcBufPointer is an intelligent pointer capable of working +// with either type, handling the ref counting increment and +// decrement as appropriate. The Template takes a single Traits +// type that provides the rc buffer and vec type, as well as +// implementations for multiple static functions. +// See Http2RcBufferPointerTraits in node_http2.h for an example. +template +class NgRcBufPointer : public MemoryRetainer { + public: + typedef typename T::rcbuf_t rcbuf_t; + typedef typename T::vector_t vector_t; + + NgRcBufPointer() = default; + + explicit NgRcBufPointer(rcbuf_t* buf) { + reset(buf); + } + + template + NgRcBufPointer(const NgRcBufPointer& other) { + reset(other.get()); + } + + NgRcBufPointer(const NgRcBufPointer& other) { + reset(other.get()); + } + + template + NgRcBufPointer& operator=(const NgRcBufPointer& other) { + if (other.get() == get()) return *this; + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(other); + } + + NgRcBufPointer& operator=(const NgRcBufPointer& other) { + if (other.get() == get()) return *this; + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(other); + } + + NgRcBufPointer(NgRcBufPointer&& other) { + this->~NgRcBufPointer(); + buf_ = other.buf_; + other.buf_ = nullptr; + } + + NgRcBufPointer& operator=(NgRcBufPointer&& other) { + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(std::move(other)); + } + + ~NgRcBufPointer() { + reset(); + } + + // Returns the underlying ngvec for this rcbuf + uint8_t* data() const { + vector_t v = T::get_vec(buf_); + return v.base; + } + + size_t len() const { + vector_t v = T::get_vec(buf_); + return v.len; + } + + std::string str() const { + return std::string(reinterpret_cast(data()), len()); + } + + void reset(rcbuf_t* ptr = nullptr, bool internalizable = false) { + if (buf_ == ptr) + return; + + if (buf_ != nullptr) + T::dec(buf_); + + buf_ = ptr; + + if (ptr != nullptr) { + T::inc(ptr); + internalizable_ = internalizable; + } + } + + rcbuf_t* get() const { return buf_; } + rcbuf_t& operator*() const { return *get(); } + rcbuf_t* operator->() const { return buf_; } + operator bool() const { return buf_ != nullptr; } + bool IsStatic() const { return T::is_static(buf_) != 0; } + void SetInternalizable() { internalizable_ = true; } + bool IsInternalizable() const { return internalizable_; } + + static inline bool IsZeroLength(rcbuf_t* buf) { + if (buf == nullptr) + return true; + vector_t b = T::get_vec(buf); + return b.len == 0; + } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackFieldWithSize("buf", len(), "buf"); + } + + SET_MEMORY_INFO_NAME(NgRcBufPointer) + SET_SELF_SIZE(NgRcBufPointer) + + class External : public v8::String::ExternalOneByteStringResource { + public: + explicit External(const NgRcBufPointer& ptr) : ptr_(ptr) {} + + const char* data() const override { + return const_cast(reinterpret_cast(ptr_.data())); + } + + size_t length() const override { + return ptr_.len(); + } + + static inline + v8::MaybeLocal GetInternalizedString( + Environment* env, + const NgRcBufPointer& ptr) { + return v8::String::NewFromOneByte( + env->isolate(), + ptr.data(), + v8::NewStringType::kInternalized, + ptr.len()); + } + + template + static v8::MaybeLocal New( + Allocator* allocator, + NgRcBufPointer ptr) { + Environment* env = allocator->env(); + if (ptr.IsStatic()) { + auto& static_str_map = env->isolate_data()->http_static_strs; + const char* header_name = reinterpret_cast(ptr.data()); + v8::Eternal& eternal = static_str_map[header_name]; + if (eternal.IsEmpty()) { + v8::Local str = + GetInternalizedString(env, ptr).ToLocalChecked(); + eternal.Set(env->isolate(), str); + return str; + } + return eternal.Get(env->isolate()); + } + + size_t len = ptr.len(); + + if (len == 0) { + ptr.reset(); + return v8::String::Empty(env->isolate()); + } + + if (ptr.IsInternalizable() && len < 64) { + v8::MaybeLocal ret = GetInternalizedString(env, ptr); + ptr.reset(); + return ret; + } + + allocator->StopTrackingMemory(ptr.get()); + External* h_str = new External(std::move(ptr)); + v8::MaybeLocal str = + v8::String::NewExternalOneByte(env->isolate(), h_str); + if (str.IsEmpty()) + delete h_str; + + return str; + } + + private: + NgRcBufPointer ptr_; + }; + + private: + rcbuf_t* buf_ = nullptr; + bool internalizable_ = false; +}; + +// The ng libraries use nearly identical structs to represent +// received http headers. The NgHeader class wraps those in a +// consistent way and allows converting the name and value to +// v8 strings. The template is given a Traits type that provides +// the NgRcBufPointer type, the NgLibMemoryManager to use for +// memory tracking, and implementation of static utility functions. +// See Http2HeaderTraits in node_http2.h for an example. +template +class NgHeader : public MemoryRetainer { + public: + typedef typename T::rcbufferpointer_t rcbufferpointer_t; + typedef typename T::rcbufferpointer_t::rcbuf_t rcbuf_t; + typedef typename T::allocator_t allocator_t; + + inline static bool IsZeroLength(rcbuf_t* name, rcbuf_t* value); + inline static bool IsZeroLength(int32_t token, rcbuf_t* name, rcbuf_t* value); + inline NgHeader( + Environment* env, + rcbuf_t* name, + rcbuf_t* value, + uint8_t flags); + inline NgHeader( + Environment* env, + int32_t token, + rcbuf_t* name, + rcbuf_t* value, + uint8_t flags); + inline NgHeader(NgHeader&& other) noexcept; + + // Calling GetName and GetValue will have the effect of releasing + // control over the reference counted buffer from this NgHeader + // object to the v8 string. Once the v8 string is garbage collected, + // the reference counter will be decremented. + + inline v8::MaybeLocal GetName(allocator_t* allocator) const; + inline v8::MaybeLocal GetValue(allocator_t* allocator) const; + + inline std::string name() const; + inline std::string value() const; + inline size_t length() const; + + void MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("name", name_); + tracker->TrackField("value", value_); + } + + SET_MEMORY_INFO_NAME(NgHeader) + SET_SELF_SIZE(NgHeader) + + std::string ToString() const { + std::string ret = name(); + ret += " = "; + ret += value(); + return ret; + } + + private: + Environment* env_; + rcbufferpointer_t name_; + rcbufferpointer_t value_; + int32_t token_ = -1; + uint8_t flags_ = 0; +}; + +inline size_t GetServerMaxHeaderPairs(size_t max_header_pairs); +inline size_t GetClientMaxHeaderPairs(size_t max_header_pairs); + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_HTTP_COMMON_H_ diff --git a/test/parallel/test-http2-binding.js b/test/parallel/test-http2-binding.js index 81f49691e3e..e81a58dfe8a 100644 --- a/test/parallel/test-http2-binding.js +++ b/test/parallel/test-http2-binding.js @@ -170,7 +170,16 @@ const expectedHeaderNames = { HTTP2_HEADER_CONTENT_MD5: 'content-md5', HTTP2_HEADER_TE: 'te', HTTP2_HEADER_UPGRADE: 'upgrade', - HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings' + HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings', + HTTP2_HEADER_X_XSS_PROTECTION: 'x-xss-protection', + HTTP2_HEADER_ALT_SVC: 'alt-svc', + HTTP2_HEADER_CONTENT_SECURITY_POLICY: 'content-security-policy', + HTTP2_HEADER_EARLY_DATA: 'early-data', + HTTP2_HEADER_EXPECT_CT: 'expect-ct', + HTTP2_HEADER_ORIGIN: 'origin', + HTTP2_HEADER_PURPOSE: 'purpose', + HTTP2_HEADER_TIMING_ALLOW_ORIGIN: 'timing-allow-origin', + HTTP2_HEADER_X_FORWARDED_FOR: 'x-forwarded-for', }; const expectedNGConstants = { From 86ab4ee6e49681fb4a9603128af4f6fa29806183 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Wed, 4 Mar 2020 14:57:56 +0300 Subject: [PATCH 135/192] async_hooks: fix ctx loss after nested ALS calls PR-URL: https://github.com/nodejs/node/pull/32085 Reviewed-By: Stephen Belanger Reviewed-By: Vladimir de Turckheim Reviewed-By: Michael Dawson --- lib/async_hooks.js | 21 +++++---- ...test-async-local-storage-enable-disable.js | 8 ++++ .../test-async-local-storage-nested.js | 44 +++++++++++++------ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 3797baf1832..bd3cd57d022 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -255,23 +255,21 @@ class AsyncLocalStorage { resource[this.kResourceStore] = store; } - _exit() { - const resource = executionAsyncResource(); - if (resource) { - resource[this.kResourceStore] = undefined; - } - } - runSyncAndReturn(store, callback, ...args) { + const resource = executionAsyncResource(); + const outerStore = resource[this.kResourceStore]; this._enter(store); try { return callback(...args); } finally { - this._exit(); + resource[this.kResourceStore] = outerStore; } } exitSyncAndReturn(callback, ...args) { + if (!this.enabled) { + return callback(...args); + } this.enabled = false; try { return callback(...args); @@ -288,12 +286,17 @@ class AsyncLocalStorage { } run(store, callback, ...args) { + const resource = executionAsyncResource(); + const outerStore = resource[this.kResourceStore]; this._enter(store); process.nextTick(callback, ...args); - this._exit(); + resource[this.kResourceStore] = outerStore; } exit(callback, ...args) { + if (!this.enabled) { + return process.nextTick(callback, ...args); + } this.enabled = false; process.nextTick(callback, ...args); this.enabled = true; diff --git a/test/async-hooks/test-async-local-storage-enable-disable.js b/test/async-hooks/test-async-local-storage-enable-disable.js index 22a3f5f6c8c..93132079827 100644 --- a/test/async-hooks/test-async-local-storage-enable-disable.js +++ b/test/async-hooks/test-async-local-storage-enable-disable.js @@ -12,8 +12,16 @@ asyncLocalStorage.runSyncAndReturn(new Map(), () => { process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); }); + asyncLocalStorage.disable(); assert.strictEqual(asyncLocalStorage.getStore(), undefined); + + // Calls to exit() should not mess with enabled status + asyncLocalStorage.exit(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); asyncLocalStorage.runSyncAndReturn(new Map(), () => { diff --git a/test/async-hooks/test-async-local-storage-nested.js b/test/async-hooks/test-async-local-storage-nested.js index 1409a8ebc82..143d5d45de9 100644 --- a/test/async-hooks/test-async-local-storage-nested.js +++ b/test/async-hooks/test-async-local-storage-nested.js @@ -4,19 +4,35 @@ const assert = require('assert'); const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); +const outer = {}; +const inner = {}; -setTimeout(() => { - asyncLocalStorage.run(new Map(), () => { - const asyncLocalStorage2 = new AsyncLocalStorage(); - asyncLocalStorage2.run(new Map(), () => { - const store = asyncLocalStorage.getStore(); - const store2 = asyncLocalStorage2.getStore(); - store.set('hello', 'world'); - store2.set('hello', 'foo'); - setTimeout(() => { - assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); - assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); - }, 200); - }); +function testInner() { + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.run(inner, () => { + assert.strictEqual(asyncLocalStorage.getStore(), inner); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.exit(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.runSyncAndReturn(inner, () => { + assert.strictEqual(asyncLocalStorage.getStore(), inner); }); -}, 100); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.exitSyncAndReturn(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); +} + +asyncLocalStorage.run(outer, testInner); +assert.strictEqual(asyncLocalStorage.getStore(), undefined); + +asyncLocalStorage.runSyncAndReturn(outer, testInner); +assert.strictEqual(asyncLocalStorage.getStore(), undefined); From 6f0ec79e42c07357fe33f353e65d76856d922582 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 22 Feb 2020 12:28:26 +0100 Subject: [PATCH 136/192] http,stream: make virtual methods throw an error Make virtual methods throw an ERR_METHOD_NOT_IMPLEMENTED error instead of emitting it. The error is not recoverable and the only way to handle it is to override the method. PR-URL: https://github.com/nodejs/node/pull/31912 Refs: https://github.com/nodejs/node/pull/31818#pullrequestreview-359403469 Reviewed-By: Robert Nagy Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen Reviewed-By: Benjamin Gruenbaum Reviewed-By: David Carlier Reviewed-By: James M Snell --- lib/_http_outgoing.js | 2 +- lib/_stream_readable.js | 2 +- lib/_stream_transform.js | 2 +- lib/_stream_writable.js | 2 +- test/parallel/test-http-outgoing-proto.js | 19 +++++++------ .../test-stream-readable-async-iterators.js | 4 ++- ...tream-readable-with-unimplemented-_read.js | 20 +++++++------ ...tream-transform-constructor-set-methods.js | 25 +++++++++-------- ...stream-writable-constructor-set-methods.js | 28 ++++++++++--------- .../test-tls-socket-allow-half-open-option.js | 5 +++- 10 files changed, 63 insertions(+), 46 deletions(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 0230f428311..b02edc7f34f 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -619,7 +619,7 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) { OutgoingMessage.prototype._implicitHeader = function _implicitHeader() { - this.emit('error', new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()')); + throw new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()'); }; ObjectDefineProperty(OutgoingMessage.prototype, 'headersSent', { diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index 30c808efd5c..1993d29db64 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -628,7 +628,7 @@ function maybeReadMore_(stream, state) { // for virtual (non-string, non-buffer) streams, "length" is somewhat // arbitrary, and perhaps not very meaningful. Readable.prototype._read = function(n) { - errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED('_read()')); + throw new ERR_METHOD_NOT_IMPLEMENTED('_read()'); }; Readable.prototype.pipe = function(dest, pipeOpts) { diff --git a/lib/_stream_transform.js b/lib/_stream_transform.js index cb4aae2e6d1..5928afc2581 100644 --- a/lib/_stream_transform.js +++ b/lib/_stream_transform.js @@ -163,7 +163,7 @@ Transform.prototype.push = function(chunk, encoding) { // an error, then that'll put the hurt on the whole operation. If you // never call cb(), then you'll never get another chunk. Transform.prototype._transform = function(chunk, encoding, cb) { - cb(new ERR_METHOD_NOT_IMPLEMENTED('_transform()')); + throw new ERR_METHOD_NOT_IMPLEMENTED('_transform()'); }; Transform.prototype._write = function(chunk, encoding, cb) { diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index af199956062..8bc916a3a95 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -562,7 +562,7 @@ Writable.prototype._write = function(chunk, encoding, cb) { if (this._writev) { this._writev([{ chunk, encoding }], cb); } else { - process.nextTick(cb, new ERR_METHOD_NOT_IMPLEMENTED('_write()')); + throw new ERR_METHOD_NOT_IMPLEMENTED('_write()'); } }; diff --git a/test/parallel/test-http-outgoing-proto.js b/test/parallel/test-http-outgoing-proto.js index b037c88c683..3c62eadc003 100644 --- a/test/parallel/test-http-outgoing-proto.js +++ b/test/parallel/test-http-outgoing-proto.js @@ -1,5 +1,5 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const http = require('http'); @@ -62,13 +62,16 @@ assert.throws(() => { { const outgoingMessage = new OutgoingMessage(); - outgoingMessage.on('error', common.expectsError({ - code: 'ERR_METHOD_NOT_IMPLEMENTED', - name: 'Error', - message: 'The _implicitHeader() method is not implemented' - })); - - outgoingMessage.write(''); + assert.throws( + () => { + outgoingMessage.write(''); + }, + { + code: 'ERR_METHOD_NOT_IMPLEMENTED', + name: 'Error', + message: 'The _implicitHeader() method is not implemented' + } + ); } assert(OutgoingMessage.prototype.write.call({ _header: 'test' })); diff --git a/test/parallel/test-stream-readable-async-iterators.js b/test/parallel/test-stream-readable-async-iterators.js index 9149058d153..55d16a1c5d3 100644 --- a/test/parallel/test-stream-readable-async-iterators.js +++ b/test/parallel/test-stream-readable-async-iterators.js @@ -14,7 +14,9 @@ async function tests() { { const AsyncIteratorPrototype = Object.getPrototypeOf( Object.getPrototypeOf(async function* () {}).prototype); - const rs = new Readable({}); + const rs = new Readable({ + read() {} + }); assert.strictEqual( Object.getPrototypeOf(Object.getPrototypeOf(rs[Symbol.asyncIterator]())), AsyncIteratorPrototype); diff --git a/test/parallel/test-stream-readable-with-unimplemented-_read.js b/test/parallel/test-stream-readable-with-unimplemented-_read.js index 7d48c422535..16ec2ac8cd8 100644 --- a/test/parallel/test-stream-readable-with-unimplemented-_read.js +++ b/test/parallel/test-stream-readable-with-unimplemented-_read.js @@ -1,14 +1,18 @@ 'use strict'; -const common = require('../common'); +require('../common'); +const assert = require('assert'); const { Readable } = require('stream'); const readable = new Readable(); -readable.on('error', common.expectsError({ - code: 'ERR_METHOD_NOT_IMPLEMENTED', - name: 'Error', - message: 'The _read() method is not implemented' -})); - -readable.read(); +assert.throws( + () => { + readable.read(); + }, + { + code: 'ERR_METHOD_NOT_IMPLEMENTED', + name: 'Error', + message: 'The _read() method is not implemented' + } +); diff --git a/test/parallel/test-stream-transform-constructor-set-methods.js b/test/parallel/test-stream-transform-constructor-set-methods.js index 8a4abd68606..a20a1a07cff 100644 --- a/test/parallel/test-stream-transform-constructor-set-methods.js +++ b/test/parallel/test-stream-transform-constructor-set-methods.js @@ -1,18 +1,21 @@ 'use strict'; const common = require('../common'); -const { strictEqual } = require('assert'); +const assert = require('assert'); const { Transform } = require('stream'); const t = new Transform(); -t.on('error', common.expectsError({ - name: 'Error', - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: 'The _transform() method is not implemented' -})); - -t.end(Buffer.from('blerg')); +assert.throws( + () => { + t.end(Buffer.from('blerg')); + }, + { + name: 'Error', + code: 'ERR_METHOD_NOT_IMPLEMENTED', + message: 'The _transform() method is not implemented' + } +); const _transform = common.mustCall((chunk, _, next) => { next(); @@ -32,9 +35,9 @@ const t2 = new Transform({ final: _final }); -strictEqual(t2._transform, _transform); -strictEqual(t2._flush, _flush); -strictEqual(t2._final, _final); +assert.strictEqual(t2._transform, _transform); +assert.strictEqual(t2._flush, _flush); +assert.strictEqual(t2._final, _final); t2.end(Buffer.from('blerg')); t2.resume(); diff --git a/test/parallel/test-stream-writable-constructor-set-methods.js b/test/parallel/test-stream-writable-constructor-set-methods.js index c326428210c..34fda8edda9 100644 --- a/test/parallel/test-stream-writable-constructor-set-methods.js +++ b/test/parallel/test-stream-writable-constructor-set-methods.js @@ -1,34 +1,36 @@ 'use strict'; const common = require('../common'); -const { strictEqual } = require('assert'); +const assert = require('assert'); const { Writable } = require('stream'); -const w = new Writable(); - -w.on('error', common.expectsError({ - name: 'Error', - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: 'The _write() method is not implemented' -})); - const bufferBlerg = Buffer.from('blerg'); +const w = new Writable(); -w.end(bufferBlerg); +assert.throws( + () => { + w.end(bufferBlerg); + }, + { + name: 'Error', + code: 'ERR_METHOD_NOT_IMPLEMENTED', + message: 'The _write() method is not implemented' + } +); const _write = common.mustCall((chunk, _, next) => { next(); }); const _writev = common.mustCall((chunks, next) => { - strictEqual(chunks.length, 2); + assert.strictEqual(chunks.length, 2); next(); }); const w2 = new Writable({ write: _write, writev: _writev }); -strictEqual(w2._write, _write); -strictEqual(w2._writev, _writev); +assert.strictEqual(w2._write, _write); +assert.strictEqual(w2._writev, _writev); w2.write(bufferBlerg); diff --git a/test/parallel/test-tls-socket-allow-half-open-option.js b/test/parallel/test-tls-socket-allow-half-open-option.js index 36449a6130c..6b94c39747a 100644 --- a/test/parallel/test-tls-socket-allow-half-open-option.js +++ b/test/parallel/test-tls-socket-allow-half-open-option.js @@ -21,7 +21,10 @@ const tls = require('tls'); { // The option is ignored when the `socket` argument is a generic // `stream.Duplex`. - const duplex = new stream.Duplex({ allowHalfOpen: false }); + const duplex = new stream.Duplex({ + allowHalfOpen: false, + read() {} + }); const socket = new tls.TLSSocket(duplex, { allowHalfOpen: true }); assert.strictEqual(socket.allowHalfOpen, false); } From f69de13bfe1102ff53aa93d40045352441880704 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Fri, 28 Feb 2020 13:40:30 +0100 Subject: [PATCH 137/192] fs: fix writeFile[Sync] for non-seekable files Completely disables the use of positioned writes at writeFile and writeFileSync, which allows it to work with non-seekable files. Fixes: https://github.com/nodejs/node/issues/31926 PR-URL: https://github.com/nodejs/node/pull/32006 Reviewed-By: Anna Henningsen --- lib/fs.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index a85b8ab29eb..c4fce61a0ef 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1248,9 +1248,9 @@ function futimesSync(fd, atime, mtime) { handleErrorFromBinding(ctx); } -function writeAll(fd, isUserFd, buffer, offset, length, position, callback) { +function writeAll(fd, isUserFd, buffer, offset, length, callback) { // write(fd, buffer, offset, length, position, callback) - fs.write(fd, buffer, offset, length, position, (writeErr, written) => { + fs.write(fd, buffer, offset, length, null, (writeErr, written) => { if (writeErr) { if (isUserFd) { callback(writeErr); @@ -1268,10 +1268,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, position, callback) { } else { offset += written; length -= written; - if (position !== null) { - position += written; - } - writeAll(fd, isUserFd, buffer, offset, length, position, callback); + writeAll(fd, isUserFd, buffer, offset, length, callback); } }); } @@ -1288,7 +1285,7 @@ function writeFile(path, data, options, callback) { if (isFd(path)) { const isUserFd = true; - writeAll(path, isUserFd, data, 0, data.byteLength, null, callback); + writeAll(path, isUserFd, data, 0, data.byteLength, callback); return; } @@ -1297,8 +1294,7 @@ function writeFile(path, data, options, callback) { callback(openErr); } else { const isUserFd = false; - const position = /a/.test(flag) ? null : 0; - writeAll(fd, isUserFd, data, 0, data.byteLength, position, callback); + writeAll(fd, isUserFd, data, 0, data.byteLength, callback); } }); } @@ -1318,15 +1314,11 @@ function writeFileSync(path, data, options) { let offset = 0; let length = data.byteLength; - let position = (/a/.test(flag) || isUserFd) ? null : 0; try { while (length > 0) { - const written = fs.writeSync(fd, data, offset, length, position); + const written = fs.writeSync(fd, data, offset, length); offset += written; length -= written; - if (position !== null) { - position += written; - } } } finally { if (!isUserFd) fs.closeSync(fd); From 960be159ac3b21bf8a4f8c3bca7e733d483fb7d9 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Mon, 2 Mar 2020 12:44:42 +0100 Subject: [PATCH 138/192] stream: add comments to pipeline implementation Fixes: https://github.com/nodejs/node/issues/32039 PR-URL: https://github.com/nodejs/node/pull/32042 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- lib/internal/streams/pipeline.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index 46d499709ee..c1299a6db42 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -202,6 +202,11 @@ function pipeline(...streams) { PassThrough = require('_stream_passthrough'); } + // If the last argument to pipeline is not a stream + // we must create a proxy stream so that pipeline(...) + // always returns a stream which can be further + // composed through `.pipe(stream)`. + const pt = new PassThrough(); if (isPromise(ret)) { ret @@ -236,6 +241,9 @@ function pipeline(...streams) { } } + // TODO(ronag): Consider returning a Duplex proxy if the first argument + // is a writable. Would improve composability. + // See, https://github.com/nodejs/node/issues/32020 return ret; } From 1b3dbc9635d45c83e355d312b6114d58664b1e7a Mon Sep 17 00:00:00 2001 From: zfx <502545703@qq.com> Date: Tue, 18 Feb 2020 22:35:08 +0800 Subject: [PATCH 139/192] events: fix removeListener for Symbols Fix removeListener when eventName type is 'symbol'. ```js const EventEmitter = require('events'); const myEmitter = new EventEmitter(); const sym = Symbol('symbol'); const fn = () => { }; myEmitter.on(sym, fn); myEmitter.on('removeListener', (...args) => { console.log('removeListener'); console.log(args, args[0] === sym, args[1] === fn); }); myEmitter.removeAllListeners() ``` When the listener's eventName type is 'symbol' and removeListener is called with no parameters, removeListener should be emitted. PR-URL: https://github.com/nodejs/node/pull/31847 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Ruben Bridgewater Reviewed-By: Shelley Vohr Reviewed-By: Yongsheng Zhang Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- lib/events.js | 3 +-- .../test-event-emitter-remove-all-listeners.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/events.js b/lib/events.js index 4a8311af059..b138979e138 100644 --- a/lib/events.js +++ b/lib/events.js @@ -31,7 +31,6 @@ const { ObjectDefineProperty, ObjectGetPrototypeOf, ObjectSetPrototypeOf, - ObjectKeys, Promise, PromiseReject, PromiseResolve, @@ -526,7 +525,7 @@ EventEmitter.prototype.removeAllListeners = // Emit removeListener for all listeners on all events if (arguments.length === 0) { - for (const key of ObjectKeys(events)) { + for (const key of ReflectOwnKeys(events)) { if (key === 'removeListener') continue; this.removeAllListeners(key); } diff --git a/test/parallel/test-event-emitter-remove-all-listeners.js b/test/parallel/test-event-emitter-remove-all-listeners.js index 3dfe65a8b4b..c62183fd08c 100644 --- a/test/parallel/test-event-emitter-remove-all-listeners.js +++ b/test/parallel/test-event-emitter-remove-all-listeners.js @@ -108,3 +108,16 @@ function expect(expected) { ee._events = undefined; assert.strictEqual(ee, ee.removeAllListeners()); } + +{ + const ee = new events.EventEmitter(); + const symbol = Symbol('symbol'); + const noop = common.mustNotCall(); + ee.on(symbol, noop); + + ee.on('removeListener', common.mustCall((...args) => { + assert.deepStrictEqual(args, [symbol, noop]); + })); + + ee.removeAllListeners(); +} From 8700d89306cc4c1fd1a540d4b8f27a59f7b4957e Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Fri, 28 Feb 2020 22:27:39 +0100 Subject: [PATCH 140/192] http: fix socket re-use races Whether and when a socket is destroyed or not after a timeout is up to the user. This leaves an edge case where a socket that has emitted 'timeout' might be re-used from the free pool. Even if destroy is called on the socket, it won't be removed from the freelist until 'close' which can happen several ticks later. Sockets are removed from the free list on the 'close' event. However, there is a delay between calling destroy() and 'close' being emitted. This means that it possible for a socket that has been destroyed to be re-used from the free list, causing unexpected failures. PR-URL: https://github.com/nodejs/node/pull/32000 Reviewed-By: Matteo Collina Reviewed-By: Denys Otrishko Reviewed-By: Luigi Pinca --- doc/api/http.md | 3 + lib/_http_agent.js | 50 ++++++---- .../test-http-agent-timeout-option.js | 4 +- test/parallel/test-http-agent-timeout.js | 94 +++++++++++++++++++ .../test-http-client-set-timeout-after-end.js | 2 +- test/parallel/test-http-client-set-timeout.js | 2 +- ...st-http-client-timeout-option-listeners.js | 4 +- ...t-http-client-timeout-option-with-agent.js | 4 +- 8 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 test/parallel/test-http-agent-timeout.js diff --git a/doc/api/http.md b/doc/api/http.md index f6aa93ecedc..3b45a36546d 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -239,6 +239,9 @@ added: v0.11.4 An object which contains arrays of sockets currently awaiting use by the agent when `keepAlive` is enabled. Do not modify. +Sockets in the `freeSockets` list will be automatically destroyed and +removed from the array on `'timeout'`. + ### `agent.getName(options)` + +* Returns {string} + +Returns a string identifying the kernel version. + +On POSIX systems, the operating system release is determined by calling +[uname(3)][]. On Windows, `pRtlGetVersion` is used, and if it is not available, +`GetVersionExW()` will be used. See +https://en.wikipedia.org/wiki/Uname#Examples for more information. + ## OS Constants The following constants are exported by `os.constants`. diff --git a/lib/os.js b/lib/os.js index f986b1cf878..395a372a00f 100644 --- a/lib/os.js +++ b/lib/os.js @@ -45,9 +45,8 @@ const { getHostname: _getHostname, getInterfaceAddresses: _getInterfaceAddresses, getLoadAvg, - getOSRelease: _getOSRelease, - getOSType: _getOSType, getPriority: _getPriority, + getOSInformation: _getOSInformation, getTotalMem, getUserInfo, getUptime, @@ -66,17 +65,25 @@ function getCheckedFunction(fn) { }); } +const [ + type, + version, + release +] = _getOSInformation(); + const getHomeDirectory = getCheckedFunction(_getHomeDirectory); const getHostname = getCheckedFunction(_getHostname); const getInterfaceAddresses = getCheckedFunction(_getInterfaceAddresses); -const getOSRelease = getCheckedFunction(_getOSRelease); -const getOSType = getCheckedFunction(_getOSType); +const getOSRelease = () => release; +const getOSType = () => type; +const getOSVersion = () => version; getFreeMem[SymbolToPrimitive] = () => getFreeMem(); getHostname[SymbolToPrimitive] = () => getHostname(); -getHomeDirectory[SymbolToPrimitive] = () => getHomeDirectory(); -getOSRelease[SymbolToPrimitive] = () => getOSRelease(); +getOSVersion[SymbolToPrimitive] = () => getOSVersion(); getOSType[SymbolToPrimitive] = () => getOSType(); +getOSRelease[SymbolToPrimitive] = () => getOSRelease(); +getHomeDirectory[SymbolToPrimitive] = () => getHomeDirectory(); getTotalMem[SymbolToPrimitive] = () => getTotalMem(); getUptime[SymbolToPrimitive] = () => getUptime(); @@ -281,6 +288,7 @@ module.exports = { type: getOSType, userInfo, uptime: getUptime, + version: getOSVersion }; ObjectDefineProperties(module.exports, { diff --git a/src/node_os.cc b/src/node_os.cc index 12a4ec3551a..b64b75fa6b9 100644 --- a/src/node_os.cc +++ b/src/node_os.cc @@ -75,8 +75,7 @@ static void GetHostname(const FunctionCallbackInfo& args) { .ToLocalChecked()); } - -static void GetOSType(const FunctionCallbackInfo& args) { +static void GetOSInformation(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); uv_utsname_t info; int err = uv_os_uname(&info); @@ -87,29 +86,18 @@ static void GetOSType(const FunctionCallbackInfo& args) { return args.GetReturnValue().SetUndefined(); } - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), info.sysname, NewStringType::kNormal) - .ToLocalChecked()); -} - - -static void GetOSRelease(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - uv_utsname_t info; - int err = uv_os_uname(&info); - - if (err != 0) { - CHECK_GE(args.Length(), 1); - env->CollectUVExceptionInfo(args[args.Length() - 1], err, "uv_os_uname"); - return args.GetReturnValue().SetUndefined(); - } + // [sysname, version, release] + Local osInformation[] = { + String::NewFromUtf8(env->isolate(), info.sysname).ToLocalChecked(), + String::NewFromUtf8(env->isolate(), info.version).ToLocalChecked(), + String::NewFromUtf8(env->isolate(), info.release).ToLocalChecked() + }; - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), info.release, NewStringType::kNormal) - .ToLocalChecked()); + args.GetReturnValue().Set(Array::New(env->isolate(), + osInformation, + arraysize(osInformation))); } - static void GetCPUInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); @@ -398,13 +386,12 @@ void Initialize(Local target, env->SetMethod(target, "getTotalMem", GetTotalMemory); env->SetMethod(target, "getFreeMem", GetFreeMemory); env->SetMethod(target, "getCPUs", GetCPUInfo); - env->SetMethod(target, "getOSType", GetOSType); - env->SetMethod(target, "getOSRelease", GetOSRelease); env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses); env->SetMethod(target, "getHomeDirectory", GetHomeDirectory); env->SetMethod(target, "getUserInfo", GetUserInfo); env->SetMethod(target, "setPriority", SetPriority); env->SetMethod(target, "getPriority", GetPriority); + env->SetMethod(target, "getOSInformation", GetOSInformation); target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"), Boolean::New(env->isolate(), IsBigEndian())).Check(); diff --git a/test/parallel/test-os.js b/test/parallel/test-os.js index 57b68ff8dea..5ee0fb9ca8d 100644 --- a/test/parallel/test-os.js +++ b/test/parallel/test-os.js @@ -194,6 +194,10 @@ const home = os.homedir(); is.string(home); assert.ok(home.includes(path.sep)); +const version = os.version(); +assert.strictEqual(typeof version, 'string'); +assert(version); + if (common.isWindows && process.env.USERPROFILE) { assert.strictEqual(home, process.env.USERPROFILE); delete process.env.USERPROFILE; From e70705c0f18a9317e33dcdd3189a5cb4c553a4cd Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 6 Mar 2020 12:40:10 +0800 Subject: [PATCH 155/192] src: add missing namespace using statements in node_watchdog.h Although these `using`s can derived from other header files, it will be better to be self-contained. PR-URL: https://github.com/nodejs/node/pull/32117 Reviewed-By: Ben Noordhuis Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: David Carlier Reviewed-By: Richard Lau Reviewed-By: Anna Henningsen --- src/node_watchdog.cc | 1 + src/node_watchdog.h | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index 29f0bc18c0d..22b09e83b79 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -34,6 +34,7 @@ namespace node { using v8::Context; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; +using v8::Local; using v8::Object; using v8::Value; diff --git a/src/node_watchdog.h b/src/node_watchdog.h index ec44c09f517..8bccfceef3c 100644 --- a/src/node_watchdog.h +++ b/src/node_watchdog.h @@ -83,10 +83,10 @@ class SigintWatchdog : public SigintWatchdogBase { class TraceSigintWatchdog : public HandleWrap, public SigintWatchdogBase { public: - static void Init(Environment* env, Local target); + static void Init(Environment* env, v8::Local target); static void New(const v8::FunctionCallbackInfo& args); - static void Start(const v8::FunctionCallbackInfo& args); - static void Stop(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void Stop(const v8::FunctionCallbackInfo& args); SignalPropagation HandleSigint() override; @@ -99,7 +99,7 @@ class TraceSigintWatchdog : public HandleWrap, public SigintWatchdogBase { private: enum class SignalFlags { None, FromIdle, FromInterrupt }; - TraceSigintWatchdog(Environment* env, Local object); + TraceSigintWatchdog(Environment* env, v8::Local object); void HandleInterrupt(); bool interrupting = false; From cee145251eef709d73a88d457d04b029f9feffd3 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Thu, 13 Feb 2020 20:24:18 +0200 Subject: [PATCH 156/192] src: improve KVStore API This adds `const char*` based APIs to KVStore to avoid multiple string conversions (char -> Utf8 -> Local -> char etc.) when possible. PR-URL: https://github.com/nodejs/node/pull/31773 Reviewed-By: Anna Henningsen Reviewed-By: Joyee Cheung Reviewed-By: James M Snell --- src/env.h | 2 ++ src/node_env_var.cc | 66 +++++++++++++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/env.h b/src/env.h index f48f74ca700..9afe5e8d8a3 100644 --- a/src/env.h +++ b/src/env.h @@ -608,11 +608,13 @@ class KVStore { virtual v8::MaybeLocal Get(v8::Isolate* isolate, v8::Local key) const = 0; + virtual v8::Maybe Get(const char* key) const = 0; virtual void Set(v8::Isolate* isolate, v8::Local key, v8::Local value) = 0; virtual int32_t Query(v8::Isolate* isolate, v8::Local key) const = 0; + virtual int32_t Query(const char* key) const = 0; virtual void Delete(v8::Isolate* isolate, v8::Local key) = 0; virtual v8::Local Enumerate(v8::Isolate* isolate) const = 0; diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 02f24fff205..6928b595ae5 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -30,8 +30,10 @@ using v8::Value; class RealEnvStore final : public KVStore { public: MaybeLocal Get(Isolate* isolate, Local key) const override; + Maybe Get(const char* key) const override; void Set(Isolate* isolate, Local key, Local value) override; int32_t Query(Isolate* isolate, Local key) const override; + int32_t Query(const char* key) const override; void Delete(Isolate* isolate, Local key) override; Local Enumerate(Isolate* isolate) const override; }; @@ -39,8 +41,10 @@ class RealEnvStore final : public KVStore { class MapKVStore final : public KVStore { public: MaybeLocal Get(Isolate* isolate, Local key) const override; + Maybe Get(const char* key) const override; void Set(Isolate* isolate, Local key, Local value) override; int32_t Query(Isolate* isolate, Local key) const override; + int32_t Query(const char* key) const override; void Delete(Isolate* isolate, Local key) override; Local Enumerate(Isolate* isolate) const override; @@ -72,26 +76,36 @@ void DateTimeConfigurationChangeNotification(Isolate* isolate, const T& key) { } } -MaybeLocal RealEnvStore::Get(Isolate* isolate, - Local property) const { +Maybe RealEnvStore::Get(const char* key) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - node::Utf8Value key(isolate, property); size_t init_sz = 256; MaybeStackBuffer val; - int ret = uv_os_getenv(*key, *val, &init_sz); + int ret = uv_os_getenv(key, *val, &init_sz); if (ret == UV_ENOBUFS) { // Buffer is not large enough, reallocate to the updated init_sz // and fetch env value again. val.AllocateSufficientStorage(init_sz); - ret = uv_os_getenv(*key, *val, &init_sz); + ret = uv_os_getenv(key, *val, &init_sz); } if (ret >= 0) { // Env key value fetch success. - MaybeLocal value_string = - String::NewFromUtf8(isolate, *val, NewStringType::kNormal, init_sz); - return value_string; + return v8::Just(std::string(*val, init_sz)); + } + + return v8::Nothing(); +} + +MaybeLocal RealEnvStore::Get(Isolate* isolate, + Local property) const { + node::Utf8Value key(isolate, property); + Maybe value = Get(*key); + + if (value.IsJust()) { + std::string val = value.FromJust(); + return String::NewFromUtf8( + isolate, val.data(), NewStringType::kNormal, val.size()); } return MaybeLocal(); @@ -112,14 +126,12 @@ void RealEnvStore::Set(Isolate* isolate, DateTimeConfigurationChangeNotification(isolate, key); } -int32_t RealEnvStore::Query(Isolate* isolate, Local property) const { +int32_t RealEnvStore::Query(const char* key) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - node::Utf8Value key(isolate, property); - char val[2]; size_t init_sz = sizeof(val); - int ret = uv_os_getenv(*key, val, &init_sz); + int ret = uv_os_getenv(key, val, &init_sz); if (ret == UV_ENOENT) { return -1; @@ -136,6 +148,11 @@ int32_t RealEnvStore::Query(Isolate* isolate, Local property) const { return 0; } +int32_t RealEnvStore::Query(Isolate* isolate, Local property) const { + node::Utf8Value key(isolate, property); + return Query(*key); +} + void RealEnvStore::Delete(Isolate* isolate, Local property) { Mutex::ScopedLock lock(per_process::env_var_mutex); @@ -190,13 +207,19 @@ std::shared_ptr KVStore::Clone(v8::Isolate* isolate) const { return copy; } -MaybeLocal MapKVStore::Get(Isolate* isolate, Local key) const { +Maybe MapKVStore::Get(const char* key) const { Mutex::ScopedLock lock(mutex_); + auto it = map_.find(key); + return it == map_.end() ? v8::Nothing() : v8::Just(it->second); +} + +MaybeLocal MapKVStore::Get(Isolate* isolate, Local key) const { Utf8Value str(isolate, key); - auto it = map_.find(std::string(*str, str.length())); - if (it == map_.end()) return Local(); - return String::NewFromUtf8(isolate, it->second.data(), - NewStringType::kNormal, it->second.size()); + Maybe value = Get(*str); + if (value.IsNothing()) return Local(); + std::string val = value.FromJust(); + return String::NewFromUtf8( + isolate, val.data(), NewStringType::kNormal, val.size()); } void MapKVStore::Set(Isolate* isolate, Local key, Local value) { @@ -209,11 +232,14 @@ void MapKVStore::Set(Isolate* isolate, Local key, Local value) { } } -int32_t MapKVStore::Query(Isolate* isolate, Local key) const { +int32_t MapKVStore::Query(const char* key) const { Mutex::ScopedLock lock(mutex_); + return map_.find(key) == map_.end() ? -1 : 0; +} + +int32_t MapKVStore::Query(Isolate* isolate, Local key) const { Utf8Value str(isolate, key); - auto it = map_.find(std::string(*str, str.length())); - return it == map_.end() ? -1 : 0; + return Query(*str); } void MapKVStore::Delete(Isolate* isolate, Local key) { From 98f44296dcf5ba526f1a517effaeb309b10b9ea1 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Thu, 13 Feb 2020 21:42:42 +0200 Subject: [PATCH 157/192] src: simplify node_worker.cc using new KVStore API PR-URL: https://github.com/nodejs/node/pull/31773 Reviewed-By: Anna Henningsen Reviewed-By: Joyee Cheung Reviewed-By: James M Snell --- src/node_worker.cc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 1496b937d0c..6ff1a0afe99 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -31,6 +31,7 @@ using v8::Integer; using v8::Isolate; using v8::Local; using v8::Locker; +using v8::Maybe; using v8::MaybeLocal; using v8::Null; using v8::Number; @@ -496,14 +497,9 @@ void Worker::New(const FunctionCallbackInfo& args) { if (args[1]->IsObject() || args[2]->IsArray()) { per_isolate_opts.reset(new PerIsolateOptions()); - HandleEnvOptions( - per_isolate_opts->per_env, [isolate, &env_vars](const char* name) { - MaybeLocal value = - env_vars->Get(isolate, OneByteString(isolate, name)); - return value.IsEmpty() ? std::string{} - : std::string(*String::Utf8Value( - isolate, value.ToLocalChecked())); - }); + HandleEnvOptions(per_isolate_opts->per_env, [&env_vars](const char* name) { + return env_vars->Get(name).FromMaybe(""); + }); #ifndef NODE_WITHOUT_NODE_OPTIONS MaybeLocal maybe_node_opts = From 077f9dc6036b97e9e12e3681e3c3bdf29d44de2b Mon Sep 17 00:00:00 2001 From: Vita Batrla Date: Thu, 13 Feb 2020 19:17:42 +0100 Subject: [PATCH 158/192] test: allow EAI_FAIL in test-net-dns-error.js Test test-net-dns-error.js causes assertion failure on SunOS, test expects ENOTFOUND, but OS returns EAI_FAIL. Maximum length of a host name is 63 characters. Test test-net-dns-error.js makes a connection attempt to invalid host name (longer than maximum). Such connection attempt on SunOS returns permanent failure (EAI_FAIL) as invalid hostname won't be ever resolved. PR-URL: https://github.com/nodejs/node/pull/31780 Reviewed-By: Ben Noordhuis Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott Reviewed-By: Luigi Pinca --- test/parallel/test-net-dns-error.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-net-dns-error.js b/test/parallel/test-net-dns-error.js index bb90eafc3d9..7232ef10ebb 100644 --- a/test/parallel/test-net-dns-error.js +++ b/test/parallel/test-net-dns-error.js @@ -26,15 +26,16 @@ const assert = require('assert'); const net = require('net'); const host = '*'.repeat(64); -const errCode = common.isOpenBSD ? 'EAI_FAIL' : 'ENOTFOUND'; +// Resolving hostname > 63 characters may return EAI_FAIL (permanent failure). +const errCodes = ['ENOTFOUND', 'EAI_FAIL']; const socket = net.connect(42, host, common.mustNotCall()); socket.on('error', common.mustCall(function(err) { - assert.strictEqual(err.code, errCode); + assert(errCodes.includes(err.code), err); })); socket.on('lookup', common.mustCall(function(err, ip, type) { assert(err instanceof Error); - assert.strictEqual(err.code, errCode); + assert(errCodes.includes(err.code), err); assert.strictEqual(ip, undefined); assert.strictEqual(type, undefined); })); From ef95de3492152290321f1f8a811401146c464138 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 4 Mar 2020 13:45:45 +0000 Subject: [PATCH 159/192] doc: link setRawMode() from signal docs Fixes: https://github.com/nodejs/node/issues/32065 PR-URL: https://github.com/nodejs/node/pull/32088 Reviewed-By: Colin Ihrig Reviewed-By: Richard Lau Reviewed-By: James M Snell Reviewed-By: Luigi Pinca Reviewed-By: Jeremiah Senkpiel --- doc/api/process.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/process.md b/doc/api/process.md index dfd98517829..59e64de17fe 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -502,7 +502,7 @@ process.on('SIGTERM', handle); * `'SIGTERM'` is not supported on Windows, it can be listened on. * `'SIGINT'` from the terminal is supported on all platforms, and can usually be generated with `+C` (though this may be configurable). It is not - generated when terminal raw mode is enabled. + generated when [terminal raw mode][] is enabled and `+C` is used. * `'SIGBREAK'` is delivered on Windows when `+` is pressed, on non-Windows platforms it can be listened on, but there is no way to send or generate it. @@ -2573,6 +2573,7 @@ cases: [process_emit_warning]: #process_process_emitwarning_warning_type_code_ctor [process_warning]: #process_event_warning [report documentation]: report.html +[terminal raw mode]: tty.html#tty_readstream_setrawmode_mode [uv_rusage_t]: http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t [wikipedia_minor_fault]: https://en.wikipedia.org/wiki/Page_fault#Minor [wikipedia_major_fault]: https://en.wikipedia.org/wiki/Page_fault#Major From d3af52715271920294d244f2e7aea02757899be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Mon, 2 Mar 2020 12:17:09 +0000 Subject: [PATCH 160/192] build: allow passing multiple libs to pkg_config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes it's necessary to pass multiple library names to pkg-config, e.g. the brotli shared libraries can be pulled in with pkg-config libbrotlienc libbrotlidec Update the code to handle both, strings (as used so far), and lists of strings. Signed-off-by: André Draszik PR-URL: https://github.com/nodejs/node/pull/32046 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Ben Noordhuis Reviewed-By: Richard Lau --- configure.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index beb08df0884..e3f78f2fed0 100755 --- a/configure.py +++ b/configure.py @@ -680,7 +680,11 @@ def pkg_config(pkg): retval = () for flag in ['--libs-only-l', '--cflags-only-I', '--libs-only-L', '--modversion']: - args += [flag, pkg] + args += [flag] + if isinstance(pkg, list): + args += pkg + else: + args += [pkg] try: proc = subprocess.Popen(shlex.split(pkg_config) + args, stdout=subprocess.PIPE) From 616b7fbcb1a7f53ea21f5ee4e3f80c61951c5728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Mon, 2 Mar 2020 12:17:35 +0000 Subject: [PATCH 161/192] build: allow use of system-installed brotli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit brotli is available as a shared library since 2016, so it makes sense to allow its use as a system-installed version. Some of the infrastructure was in place already (node.gyp and node.gypi), but some bits in the configure script here were missing. Add them, keeping the default as before, to use the bundled version. Refs: https://github.com/google/brotli/pull/421 Signed-off-by: André Draszik PR-URL: https://github.com/nodejs/node/pull/32046 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Ben Noordhuis Reviewed-By: Richard Lau --- configure.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/configure.py b/configure.py index e3f78f2fed0..0190e31b41a 100755 --- a/configure.py +++ b/configure.py @@ -301,6 +301,27 @@ dest='shared_zlib_libpath', help='a directory to search for the shared zlib DLL') +shared_optgroup.add_option('--shared-brotli', + action='store_true', + dest='shared_brotli', + help='link to a shared brotli DLL instead of static linking') + +shared_optgroup.add_option('--shared-brotli-includes', + action='store', + dest='shared_brotli_includes', + help='directory containing brotli header files') + +shared_optgroup.add_option('--shared-brotli-libname', + action='store', + dest='shared_brotli_libname', + default='brotlidec,brotlienc', + help='alternative lib name to link to [default: %default]') + +shared_optgroup.add_option('--shared-brotli-libpath', + action='store', + dest='shared_brotli_libpath', + help='a directory to search for the shared brotli DLL') + shared_optgroup.add_option('--shared-cares', action='store_true', dest='shared_cares', @@ -1692,6 +1713,7 @@ def make_bin_override(): configure_library('zlib', output) configure_library('http_parser', output) configure_library('libuv', output) +configure_library('brotli', output, pkgname=['libbrotlidec', 'libbrotlienc']) configure_library('cares', output, pkgname='libcares') configure_library('nghttp2', output, pkgname='libnghttp2') configure_v8(output) From d5a06e73b6b543ba7a0f3ec62878f132aeae48b0 Mon Sep 17 00:00:00 2001 From: ProdipRoy89 Date: Mon, 2 Mar 2020 04:58:27 -0800 Subject: [PATCH 162/192] test: changed function to arrow function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/32045 Reviewed-By: James M Snell Reviewed-By: Rich Trott Reviewed-By: Tobias Nießen --- test/parallel/test-stream3-pause-then-read.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-stream3-pause-then-read.js b/test/parallel/test-stream3-pause-then-read.js index bfd8a203732..1a385472205 100644 --- a/test/parallel/test-stream3-pause-then-read.js +++ b/test/parallel/test-stream3-pause-then-read.js @@ -108,7 +108,7 @@ function pipeLittle() { console.error('pipe a little'); const w = new Writable(); let written = 0; - w.on('finish', function() { + w.on('finish', () => { assert.strictEqual(written, 200); setImmediate(read1234); }); @@ -160,7 +160,7 @@ function pipe() { written += chunk.length; cb(); }; - w.on('finish', function() { + w.on('finish', () => { console.error('written', written, totalPushed); assert.strictEqual(written, expectEndingData); assert.strictEqual(totalPushed, expectTotalData); From 8ce6315e8896dd30f5d21caca542b031f33e6b8d Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 2 Mar 2020 12:21:14 +0100 Subject: [PATCH 163/192] test: use index.js if package.json "main" is empty Verify that the module loader uses index.js when the "main" property of package.json is the empty string. Refs: https://github.com/nodejs/node/issues/32013 PR-URL: https://github.com/nodejs/node/pull/32040 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Denys Otrishko Reviewed-By: Richard Lau Reviewed-By: Luigi Pinca --- test/fixtures/require-empty-main/index.js | 2 ++ test/fixtures/require-empty-main/package.json | 1 + test/parallel/test-require-empty-main.js | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 test/fixtures/require-empty-main/index.js create mode 100644 test/fixtures/require-empty-main/package.json create mode 100644 test/parallel/test-require-empty-main.js diff --git a/test/fixtures/require-empty-main/index.js b/test/fixtures/require-empty-main/index.js new file mode 100644 index 00000000000..d2ed2dd2202 --- /dev/null +++ b/test/fixtures/require-empty-main/index.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = 42; diff --git a/test/fixtures/require-empty-main/package.json b/test/fixtures/require-empty-main/package.json new file mode 100644 index 00000000000..3f0b7c677ac --- /dev/null +++ b/test/fixtures/require-empty-main/package.json @@ -0,0 +1 @@ +{"main":""} diff --git a/test/parallel/test-require-empty-main.js b/test/parallel/test-require-empty-main.js new file mode 100644 index 00000000000..73f141d1f9e --- /dev/null +++ b/test/parallel/test-require-empty-main.js @@ -0,0 +1,25 @@ +'use strict'; +require('../common'); + +// A package.json with an empty "main" property should use index.js if present. +// require.resolve() should resolve to index.js for the same reason. +// +// In fact, any "main" property that doesn't resolve to a file should result +// in index.js being used, but that's already checked for by other tests. +// This test only concerns itself with the empty string. + +const assert = require('assert'); +const path = require('path'); +const fixtures = require('../common/fixtures'); + +const where = fixtures.path('require-empty-main'); +const expected = path.join(where, 'index.js'); + +test(); +setImmediate(test); + +function test() { + assert.strictEqual(require.resolve(where), expected); + assert.strictEqual(require(where), 42); + assert.strictEqual(require.resolve(where), expected); +} From e9fa5ae3a1198213f8f58d4b97eb6233c05a28b4 Mon Sep 17 00:00:00 2001 From: Harshitha KP Date: Fri, 21 Feb 2020 02:32:51 -0500 Subject: [PATCH 164/192] src: handle NULL env scenario Convert hard assertion into a throw with a useful error message in src/module_wrap.cc. PR-URL: https://github.com/nodejs/node/pull/31899 Reviewed-By: Anna Henningsen --- doc/api/errors.md | 7 +++++++ src/module_wrap.cc | 13 +++++++++++-- src/node_errors.h | 47 ++++++++++++++++++++++++---------------------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 26685f0ebcd..b0ef9b52064 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -889,6 +889,13 @@ provided. Encoding provided to `TextDecoder()` API was not one of the [WHATWG Supported Encodings][]. + +### `ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE` + +The JS execution context is not associated with a Node.js environment. +This may occur when Node.js is used as an embedded library and some hooks +for the JS engine are not set up properly. + ### `ERR_FALSY_VALUE_REJECTION` diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 2b1708088bf..b2afefce17e 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -451,7 +451,12 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, Local specifier, Local referrer) { Environment* env = Environment::GetCurrent(context); - CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. + if (env == nullptr) { + Isolate* isolate = context->GetIsolate(); + THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); + return MaybeLocal(); + } + Isolate* isolate = env->isolate(); ModuleWrap* dependent = GetFromModule(env, referrer); @@ -1443,7 +1448,11 @@ static MaybeLocal ImportModuleDynamically( Local specifier) { Isolate* iso = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); - CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. + if (env == nullptr) { + THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(iso); + return MaybeLocal(); + } + EscapableHandleScope handle_scope(iso); Local import_callback = diff --git a/src/node_errors.h b/src/node_errors.h index a05ce8f6bfb..960cb725323 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -39,6 +39,7 @@ void OnFatalError(const char* location, const char* message); V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \ V(ERR_CRYPTO_UNKNOWN_CIPHER, Error) \ V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \ + V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \ V(ERR_INVALID_ARG_VALUE, TypeError) \ V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \ V(ERR_INVALID_ARG_TYPE, TypeError) \ @@ -86,28 +87,30 @@ void OnFatalError(const char* location, const char* message); // Errors with predefined static messages -#define PREDEFINED_ERROR_MESSAGES(V) \ - V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \ - "Buffer is not available for the current Context") \ - V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ - V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \ - V(ERR_CRYPTO_UNKNOWN_CIPHER, "Unknown cipher") \ - V(ERR_CRYPTO_UNKNOWN_DH_GROUP, "Unknown DH group") \ - V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \ - V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \ - V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \ - V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \ - "MessagePort was found in message but not listed in transferList") \ - V(ERR_MISSING_PLATFORM_FOR_WORKER, \ - "The V8 platform used by this instance of Node does not support " \ - "creating Workers") \ - V(ERR_NON_CONTEXT_AWARE_DISABLED, \ - "Loading non context-aware native modules has been disabled") \ - V(ERR_SCRIPT_EXECUTION_INTERRUPTED, \ - "Script execution was interrupted by `SIGINT`") \ - V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, \ - "Cannot serialize externalized SharedArrayBuffer") \ - V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, "Failed to set PSK identity hint") \ +#define PREDEFINED_ERROR_MESSAGES(V) \ + V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \ + "Buffer is not available for the current Context") \ + V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ + V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \ + V(ERR_CRYPTO_UNKNOWN_CIPHER, "Unknown cipher") \ + V(ERR_CRYPTO_UNKNOWN_DH_GROUP, "Unknown DH group") \ + V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, \ + "Context not associated with Node.js environment") \ + V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \ + V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \ + V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \ + V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \ + "MessagePort was found in message but not listed in transferList") \ + V(ERR_MISSING_PLATFORM_FOR_WORKER, \ + "The V8 platform used by this instance of Node does not support " \ + "creating Workers") \ + V(ERR_NON_CONTEXT_AWARE_DISABLED, \ + "Loading non context-aware native modules has been disabled") \ + V(ERR_SCRIPT_EXECUTION_INTERRUPTED, \ + "Script execution was interrupted by `SIGINT`") \ + V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, \ + "Cannot serialize externalized SharedArrayBuffer") \ + V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, "Failed to set PSK identity hint") #define V(code, message) \ inline v8::Local code(v8::Isolate* isolate) { \ From 41ac1921920b736f812cc03f6df98ff169d32c07 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 10 Feb 2020 13:07:30 +0100 Subject: [PATCH 165/192] test: increase test timeout to prevent flakiness This increases the waiting time for each event from 500 to 750 ms. The former timeout could be hit on very slow machines with high load. PR-URL: https://github.com/nodejs/node/pull/31716 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen --- test/parallel/test-repl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index b0dad397ccc..67ec86cc2c5 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -861,7 +861,7 @@ function event(ee, expected) { const data = inspect(expected, { compact: false }); const msg = `The REPL did not reply as expected for:\n\n${data}`; reject(new Error(msg)); - }, common.platformTimeout(500)); + }, common.platformTimeout(1000)); ee.once('data', common.mustCall((...args) => { clearTimeout(timeout); resolve(...args); From 2a7d66200b53f645b4f7e412ed080f249e2e667e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 05:11:17 -0800 Subject: [PATCH 166/192] doc: revise tools/icu/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Simplify introductory sentence. * Remove some passive voice. * Make style in bulleted list a little more consistent. * Remove superfluous list entry. PR-URL: https://github.com/nodejs/node/pull/32136 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater --- tools/icu/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tools/icu/README.md b/tools/icu/README.md index 1af39941b55..01b0178c454 100644 --- a/tools/icu/README.md +++ b/tools/icu/README.md @@ -1,9 +1,8 @@ # Notes about the `tools/icu` subdirectory -This directory contains tools, data, and information about the -International Components for Unicode integration. [ICU][] is used -both by V8 and also by -Node.js itself to provide internationalization functionality. +This directory contains tools and information about the +[International Components for Unicode][ICU] (ICU) integration. +Both V8 and Node.js use ICU to provide internationalization functionality. * `patches/` are one-off patches, actually entire source file replacements, organized by ICU version number. @@ -15,9 +14,8 @@ Node.js itself to provide internationalization functionality. is invoked. It builds against the `pkg-config` located ICU. * `iculslocs.cc` is source for the `iculslocs` utility, invoked by `icutrim.py` as part of repackaging. Not used separately. See source for more details. -* `no-op.cc` — empty function to convince gyp to use a C++ compiler. -* `README.md` — you are here -* `shrink-icu-src.py` — this is used during upgrade (see guide below) +* `no-op.cc` contains an empty function to convince gyp to use a C++ compiler. +* `shrink-icu-src.py` is used during upgrade (see guide below). Note: > The files in this directory were written for the Node.js v0.12 effort. From d368dcc63af2eb75d5dbef5c6669e5e8ab3be5d2 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Tue, 25 Feb 2020 13:46:46 +0800 Subject: [PATCH 167/192] async_hooks: add sync enterWith to ALS This allows transitioning the entire following sync and async execution sub-tree to the given async storage context. With this one can be sure the context binding will remain for any following sync activity and all descending async execution whereas the `run*(...)` methods must wrap everything that is intended to exist within the context. This is helpful for scenarios such as prepending a `'connection'` event to an http server which binds everything that occurs within each request to the given context. This is helpful for APMs to minimize the need for patching and especially adding closures. PR-URL: https://github.com/nodejs/node/pull/31945 Reviewed-By: Vladimir de Turckheim Reviewed-By: Matteo Collina Reviewed-By: Michael Dawson --- doc/api/async_hooks.md | 42 +++++++++++++++++++ lib/async_hooks.js | 6 +-- .../test-async-local-storage-enter-with.js | 20 +++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/async-hooks/test-async-local-storage-enter-with.js diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 59902917169..f02d3ff7556 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -950,6 +950,48 @@ If this method is called outside of an asynchronous context initialized by calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will return `undefined`. +### `asyncLocalStorage.enterWith(store)` + + +* `store` {any} + +Calling `asyncLocalStorage.enterWith(store)` will transition into the context +for the remainder of the current synchronous execution and will persist +through any following asynchronous calls. + +Example: + +```js +const store = { id: 1 }; +asyncLocalStorage.enterWith(store); +asyncLocalStorage.getStore(); // Returns the store object +someAsyncOperation(() => { + asyncLocalStorage.getStore(); // Returns the same object +}); +``` + +This transition will continue for the _entire_ synchronous execution. +This means that if, for example, the context is entered within an event +handler subsequent event handlers will also run within that context unless +specifically bound to another context with an `AsyncResource`. + +```js +const store = { id: 1 }; + +emitter.on('my-event', () => { + asyncLocalStorage.enterWith(store); +}); +emitter.on('my-event', () => { + asyncLocalStorage.getStore(); // Returns the same object +}); + +asyncLocalStorage.getStore(); // Returns undefined +emitter.emit('my-event'); +asyncLocalStorage.getStore(); // Returns the same object +``` + ### `asyncLocalStorage.run(store, callback[, ...args])` ```C @@ -401,6 +402,7 @@ by the previous call, it will not be called. ### napi_get_instance_data ```C @@ -1663,10 +1665,9 @@ the `napi_value` in question is of the JavaScript type expected by the API. #### napi_key_collection_mode -> Stability: 1 - Experimental - ```C typedef enum { napi_key_include_prototypes, @@ -1685,10 +1686,9 @@ of the objects's prototype chain as well. #### napi_key_filter -> Stability: 1 - Experimental - ```C typedef enum { napi_key_all_properties = 0, @@ -1705,10 +1705,9 @@ Property filter bits. They can be or'ed to build a composite filter. #### napi_key_conversion -> Stability: 1 - Experimental - ```C typedef enum { napi_key_keep_numbers, @@ -2262,10 +2261,9 @@ The JavaScript `Number` type is described in #### napi_create_bigint_int64 -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_int64(napi_env env, int64_t value, @@ -2283,10 +2281,9 @@ This API converts the C `int64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_uint64 -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, @@ -2304,10 +2301,9 @@ This API converts the C `uint64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_words -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_words(napi_env env, int sign_bit, @@ -2653,10 +2649,9 @@ This API returns the C double primitive equivalent of the given JavaScript #### napi_get_value_bigint_int64 -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, @@ -2680,10 +2675,9 @@ This API returns the C `int64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_uint64 -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_uint64(napi_env env, napi_value value, @@ -2707,10 +2701,9 @@ This API returns the C `uint64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_words -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_words(napi_env env, napi_value value, @@ -3595,10 +3588,9 @@ included. #### napi_get_all_property_names -> Stability: 1 - Experimental - ```C napi_get_all_property_names(napi_env env, napi_value object, diff --git a/src/js_native_api.h b/src/js_native_api.h index cb69fde5d19..2675da505c2 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -4,7 +4,6 @@ // This file needs to be compatible with C compilers. #include // NOLINT(modernize-deprecated-headers) #include // NOLINT(modernize-deprecated-headers) -#include "js_native_api_types.h" // Use INT_MAX, this should only be consumed by the pre-processor anyway. #define NAPI_VERSION_EXPERIMENTAL 2147483647 @@ -18,10 +17,12 @@ // functions available in a new version of N-API that is not yet ported in all // LTS versions, they can set NAPI_VERSION knowing that they have specifically // depended on that version. -#define NAPI_VERSION 5 +#define NAPI_VERSION 6 #endif #endif +#include "js_native_api_types.h" + // If you need __declspec(dllimport), either include instead, or // define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. #ifndef NAPI_EXTERN @@ -478,7 +479,7 @@ NAPI_EXTERN napi_status napi_add_finalizer(napi_env env, #endif // NAPI_VERSION >= 5 -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION >= 6 // BigInt NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env, @@ -523,7 +524,9 @@ NAPI_EXTERN napi_status napi_set_instance_data(napi_env env, NAPI_EXTERN napi_status napi_get_instance_data(napi_env env, void** data); +#endif // NAPI_VERSION >= 6 +#ifdef NAPI_EXPERIMENTAL // ArrayBuffer detaching NAPI_EXTERN napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index ef44dd457db..7a49fc9f719 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -115,7 +115,7 @@ typedef struct { napi_status error_code; } napi_extended_error_info; -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION >= 6 typedef enum { napi_key_include_prototypes, napi_key_own_only @@ -134,6 +134,6 @@ typedef enum { napi_key_keep_numbers, napi_key_numbers_to_strings } napi_key_conversion; -#endif +#endif // NAPI_VERSION >= 6 #endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/src/node_version.h b/src/node_version.h index ebd3cff606f..8f14c6dbfae 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -93,6 +93,6 @@ // The NAPI_VERSION provided by this version of the runtime. This is the version // which the Node binary being built supports. -#define NAPI_VERSION 5 +#define NAPI_VERSION 6 #endif // SRC_NODE_VERSION_H_ diff --git a/test/js-native-api/test_bigint/test_bigint.c b/test/js-native-api/test_bigint/test_bigint.c index 4befc171baa..c62a0a6a6c2 100644 --- a/test/js-native-api/test_bigint/test_bigint.c +++ b/test/js-native-api/test_bigint/test_bigint.c @@ -1,5 +1,3 @@ -#define NAPI_EXPERIMENTAL - #include #include #include diff --git a/test/js-native-api/test_general/test.js b/test/js-native-api/test_general/test.js index 9b847f4f339..a4b3df5535a 100644 --- a/test/js-native-api/test_general/test.js +++ b/test/js-native-api/test_general/test.js @@ -33,7 +33,7 @@ assert.notStrictEqual(test_general.testGetPrototype(baseObject), test_general.testGetPrototype(extendedObject)); // Test version management functions. The expected version is currently 4. -assert.strictEqual(test_general.testGetVersion(), 5); +assert.strictEqual(test_general.testGetVersion(), 6); [ 123, diff --git a/test/js-native-api/test_instance_data/test_instance_data.c b/test/js-native-api/test_instance_data/test_instance_data.c index a64ebec0c1a..5255c3e4a02 100644 --- a/test/js-native-api/test_instance_data/test_instance_data.c +++ b/test/js-native-api/test_instance_data/test_instance_data.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include #include "../common.h" diff --git a/test/js-native-api/test_object/test_null.c b/test/js-native-api/test_object/test_null.c index 523217f3c0a..b6bf4df31cc 100644 --- a/test/js-native-api/test_object/test_null.c +++ b/test/js-native-api/test_object/test_null.c @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include #include "../common.h" diff --git a/test/js-native-api/test_object/test_object.c b/test/js-native-api/test_object/test_object.c index 9d9589238d4..08f619bf7ff 100644 --- a/test/js-native-api/test_object/test_object.c +++ b/test/js-native-api/test_object/test_object.c @@ -1,5 +1,3 @@ -#define NAPI_EXPERIMENTAL - #include #include "../common.h" #include diff --git a/test/node-api/test_general/test_general.c b/test/node-api/test_general/test_general.c index 05bccaf5c2c..be805f782be 100644 --- a/test/node-api/test_general/test_general.c +++ b/test/node-api/test_general/test_general.c @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include #include #include "../../js-native-api/common.h" diff --git a/test/node-api/test_instance_data/addon.c b/test/node-api/test_instance_data/addon.c index 928b4dfaf8e..7cf27bf28ab 100644 --- a/test/node-api/test_instance_data/addon.c +++ b/test/node-api/test_instance_data/addon.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include static void addon_free(napi_env env, void* data, void* hint) { diff --git a/test/node-api/test_instance_data/test_instance_data.c b/test/node-api/test_instance_data/test_instance_data.c index 1a814e91c06..24fd502e836 100644 --- a/test/node-api/test_instance_data/test_instance_data.c +++ b/test/node-api/test_instance_data/test_instance_data.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include #include "../../js-native-api/common.h" diff --git a/test/node-api/test_instance_data/test_ref_then_set.c b/test/node-api/test_instance_data/test_ref_then_set.c index a0df1e5b9f8..10c779d3241 100644 --- a/test/node-api/test_instance_data/test_ref_then_set.c +++ b/test/node-api/test_instance_data/test_ref_then_set.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include napi_value addon_new(napi_env env, napi_value exports, bool ref_first); diff --git a/test/node-api/test_instance_data/test_set_then_ref.c b/test/node-api/test_instance_data/test_set_then_ref.c index 6ebed2d1e86..9a1b31aeed3 100644 --- a/test/node-api/test_instance_data/test_set_then_ref.c +++ b/test/node-api/test_instance_data/test_set_then_ref.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include napi_value addon_new(napi_env env, napi_value exports, bool ref_first); From 9a78c82c55f4c4665533f3e50b6ee236aeb79dd4 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Fri, 6 Mar 2020 16:13:00 -0800 Subject: [PATCH 169/192] test: warn when inspector process crashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the subprocess being inspected hard crashes, there will be no information on the log, and the parent process will just wait until timeout. Logging the error signal when it happens can help developers understand failures faster. Signed-off-by: Matheus Marchini PR-URL: https://github.com/nodejs/node/pull/32133 Reviewed-By: Michaël Zasso Reviewed-By: Jiawen Geng Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- test/common/inspector-helper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index 42d6baed441..d430137746d 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -344,6 +344,9 @@ class NodeInstance extends EventEmitter { this._shutdownPromise = new Promise((resolve) => { this._process.once('exit', (exitCode, signal) => { + if (signal) { + console.error(`[err] child process crashed, signal ${signal}`); + } resolve({ exitCode, signal }); this._running = false; }); From 9e69d97c60234a601ca4712a52c45c6b7588afa6 Mon Sep 17 00:00:00 2001 From: Andrew Neitsch Date: Wed, 4 Mar 2020 16:34:28 -0700 Subject: [PATCH 170/192] cli: allow --jitless V8 flag in NODE_OPTIONS This work is modeled on #30094 which allowed `--disallow-code-generation-from-strings` in `NODE_OPTIONS`. The `--jitless` v8 option has been supported since 12.0.0. As a v8 option, node automatically picks it up, but there have been a few issues that were resolved by simply telling users about the option: #26758, This PR: - allows `--jitless` in `NODE_OPTIONS` - documents `--jitless` - moves `--experimental-loader=module` to locally restore alphabetical order in option documentation Refs: https://github.com/nodejs/node/pull/30094 Refs: https://github.com/nodejs/node/issues/26758 Refs: https://github.com/nodejs/node/issues/28800 PR-URL: https://github.com/nodejs/node/pull/32100 Reviewed-By: Richard Lau Reviewed-By: Colin Ihrig Reviewed-By: Sam Roberts Reviewed-By: Gus Caplan Reviewed-By: Shelley Vohr Reviewed-By: David Carlier Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Ruben Bridgewater --- doc/api/cli.md | 30 +++++++++++++++++++------- doc/node.1 | 19 +++++++++++----- src/node_options.cc | 4 ++++ test/parallel/test-cli-node-options.js | 1 + 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 8b45a4b5bb3..66cb665fa82 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -170,6 +170,14 @@ added: v12.9.0 Enable experimental JSON support for the ES Module loader. +### `--experimental-loader=module` + + +Specify the `module` of a custom [experimental ECMAScript Module loader][]. +`module` may be either a path to a file, or an ECMAScript Module name. + ### `--experimental-modules` - -Specify the `module` of a custom [experimental ECMAScript Module loader][]. -`module` may be either a path to a file, or an ECMAScript Module name. - ### `--insecure-http-parser` + +Disable [runtime allocation of executable memory][jitless]. This may be +required on some platforms for security reasons. It can also reduce attack +surface on other platforms, but the performance impact may be severe. + +This flag is inherited from V8 and is subject to change upstream. It may +disappear in a non-semver-major release. + ### `--max-http-header-size=size` The TLS socket must be connected and securily established. Ensure the 'secure' -event is emitted, before you continue. +event is emitted before continuing. ### `ERR_TLS_INVALID_PROTOCOL_METHOD` From fb36266f28379fc7270a3d3e7423296e59e022b1 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 182/192] doc: remove personal pronoun usage in fs.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/fs.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 24daaaaa53c..a70f56f3c92 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1710,9 +1710,9 @@ By default, the stream will not emit a `'close'` event after it has been destroyed. This is the opposite of the default for other `Readable` streams. Set the `emitClose` option to `true` to change this behavior. -By providing the `fs` option it is possible to override the corresponding `fs` -implementations for `open`, `read` and `close`. When providing the `fs` option, -you must override `open`, `close` and `read`. +By providing the `fs` option, it is possible to override the corresponding `fs` +implementations for `open`, `read`, and `close`. When providing the `fs` option, +overrides for `open`, `read`, and `close` are required. ```js const fs = require('fs'); @@ -1805,8 +1805,8 @@ Set the `emitClose` option to `true` to change this behavior. By providing the `fs` option it is possible to override the corresponding `fs` implementations for `open`, `write`, `writev` and `close`. Overriding `write()` without `writev()` can reduce performance as some optimizations (`_writev()`) -will be disabled. When providing the `fs` option, you must override `open`, -`close` and at least one of `write` and `writev`. +will be disabled. When providing the `fs` option, overrides for `open`, +`close`, and at least one of `write` and `writev` are required. Like [`ReadStream`][], if `fd` is specified, [`WriteStream`][] will ignore the `path` argument and will use the specified file descriptor. This means that no @@ -4256,8 +4256,8 @@ in that they provide an object oriented API for working with files. If a `FileHandle` is not closed using the `filehandle.close()` method, it might automatically close the file descriptor and will emit a process warning, thereby helping to prevent memory leaks. -Please do not rely on this behavior in your code because it is unreliable and -your file may not be closed. Instead, always explicitly close `FileHandle`s. +Please do not rely on this behavior because it is unreliable and +the file may not be closed. Instead, always explicitly close `FileHandle`s. Node.js may change this behavior in the future. Instances of the `FileHandle` object are created internally by the From 73f2dbc911e16a3b50fc9a1236d9bed7d9e04d7a Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 183/192] doc: remove personal pronoun usage in policy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/policy.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/policy.md b/doc/api/policy.md index bf4cc214552..35ea48b40b4 100644 --- a/doc/api/policy.md +++ b/doc/api/policy.md @@ -166,9 +166,9 @@ only with care after auditing a module to ensure its behavior is valid. #### Example: Patched Dependency -Since a dependency can be redirected, you can provide attenuated or modified -forms of dependencies as fits your application. For example, you could log -data about timing of function durations by wrapping the original: +Redirected dependencies can provide attenuated or modified functionality as fits +the application. For example, log data about timing of function durations by +wrapping the original: ```js const original = require('fn'); From 811b3a9931b658b2fc2ced2574462069abfa5aa2 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Fri, 6 Mar 2020 16:27:19 -0800 Subject: [PATCH 184/192] src: use C++ style for struct with initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes warning on clang 11: In file included from ../../src/node_http2.cc:6: ../../src/node_http2.h:508:15: warning: anonymous non-C-compatible type given name for linkage purposes by typedef declaration; add a tag name here [-Wnon-c-typedef-for-linkage] typedef struct { ^ SessionJSFields ../../src/node_http2.h:512:33: note: type is not C-compatible due to this default member initializer uint32_t max_invalid_frames = 1000; ^~~~ ../../src/node_http2.h:514:3: note: type is given name 'SessionJSFields' for linkage purposes by this typedef declaration } SessionJSFields; ^ PR-URL: https://github.com/nodejs/node/pull/32134 Reviewed-By: Anna Henningsen Reviewed-By: Tobias Nießen Reviewed-By: Colin Ihrig Reviewed-By: David Carlier --- src/node_http2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_http2.h b/src/node_http2.h index 97560c8a5bf..e0a4e404491 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -505,13 +505,13 @@ class Http2Stream::Provider::Stream : public Http2Stream::Provider { void* user_data); }; -typedef struct { +struct SessionJSFields { uint8_t bitfield; uint8_t priority_listener_count; uint8_t frame_error_listener_count; uint32_t max_invalid_frames = 1000; uint32_t max_rejected_streams = 100; -} SessionJSFields; +}; // Indices for js_fields_, which serves as a way to communicate data with JS // land fast. In particular, we store information about the number/presence From 6db6af405729d47d675edc0a5e87eb2aeb39df7b Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 22 Jan 2020 14:38:11 +1100 Subject: [PATCH 185/192] build: macOS package notarization Includes hardened-runtime patch from gdams from https://github.com/nodejs/node/issues/29216#issuecomment-546932966 PR-URL: https://github.com/nodejs/node/pull/31459 Refs: https://github.com/nodejs/node/issues/29216 Reviewed-By: Christian Clauss Reviewed-By: Michael Dawson Reviewed-By: Ash Cripps Signed-off-by: Rod Vagg --- .gitignore | 1 + Makefile | 1 + tools/osx-codesign.sh | 11 +++++++++- tools/osx-entitlements.plist | 16 +++++++++++++++ tools/osx-gon-config.json.tmpl | 12 +++++++++++ tools/osx-notarize.sh | 37 ++++++++++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tools/osx-entitlements.plist create mode 100644 tools/osx-gon-config.json.tmpl create mode 100755 tools/osx-notarize.sh diff --git a/.gitignore b/.gitignore index 160b96f74a5..425a5ddbec0 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ /doc/api.xml /node /node_g +/gon-config.json /*.exe /*.swp /out diff --git a/Makefile b/Makefile index 4ebb16cdfd0..3a97f15fc3c 100644 --- a/Makefile +++ b/Makefile @@ -1003,6 +1003,7 @@ $(PKG): release-only --resources $(MACOSOUTDIR)/installer/productbuild/Resources \ --package-path $(MACOSOUTDIR)/pkgs ./$(PKG) SIGN="$(PRODUCTSIGN_CERT)" PKG="$(PKG)" bash tools/osx-productsign.sh + bash tools/osx-notarize.sh $(FULLVERSION) .PHONY: pkg # Builds the macOS installer for releases. diff --git a/tools/osx-codesign.sh b/tools/osx-codesign.sh index 6a954c737fa..7ca80ca7462 100644 --- a/tools/osx-codesign.sh +++ b/tools/osx-codesign.sh @@ -8,4 +8,13 @@ if [ "X$SIGN" == "X" ]; then exit 0 fi -codesign -s "$SIGN" "$PKGDIR"/bin/node +# All macOS executable binaries in the bundle must be codesigned with the +# hardened runtime enabled. +# See https://github.com/nodejs/node/pull/31459 + +codesign \ + --sign "$SIGN" \ + --entitlements tools/osx-entitlements.plist \ + --options runtime \ + --timestamp \ + "$PKGDIR"/bin/node diff --git a/tools/osx-entitlements.plist b/tools/osx-entitlements.plist new file mode 100644 index 00000000000..555c10f7ff8 --- /dev/null +++ b/tools/osx-entitlements.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/tools/osx-gon-config.json.tmpl b/tools/osx-gon-config.json.tmpl new file mode 100644 index 00000000000..3ea16465fc1 --- /dev/null +++ b/tools/osx-gon-config.json.tmpl @@ -0,0 +1,12 @@ +{ + "notarize": [{ + "path": "node-{{pkgid}}.pkg", + "bundle_id": "org.nodejs.pkg.{{pkgid}}", + "staple": true + }], + + "apple_id": { + "username": "{{appleid}}", + "password": "@env:NOTARIZATION_PASSWORD" + } +} diff --git a/tools/osx-notarize.sh b/tools/osx-notarize.sh new file mode 100755 index 00000000000..97bb0912722 --- /dev/null +++ b/tools/osx-notarize.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Uses gon, from https://github.com/mitchellh/gon, to notarize a generated node-.pkg file +# with Apple for installation on macOS Catalina and later as validated by Gatekeeper. + +set -e + +gon_version="0.2.2" +gon_exe="${HOME}/.gon/gon_${gon_version}" + +__dirname="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +pkgid="$1" + +if [ "X${pkgid}" == "X" ]; then + echo "Usage: $0 " + exit 1 +fi + +if [ "X$NOTARIZATION_ID" == "X" ]; then + echo "No NOTARIZATION_ID environment var. Skipping notarization." + exit 0 +fi + +set -x + +mkdir -p "${HOME}/.gon/" + +if [ ! -f "${gon_exe}" ]; then + curl -sL "https://github.com/mitchellh/gon/releases/download/v${gon_version}/gon_${gon_version}_macos.zip" -o "${gon_exe}.zip" + (cd "${HOME}/.gon/" && rm -f gon && unzip "${gon_exe}.zip" && mv gon "${gon_exe}") +fi + +cat tools/osx-gon-config.json.tmpl \ + | sed -e "s/{{appleid}}/${NOTARIZATION_ID}/" -e "s/{{pkgid}}/${pkgid}/" \ + > gon-config.json + +"${gon_exe}" -log-level=info gon-config.json From d2f08a1bdb78655c4a3fc49825986c148d14117e Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 30 Jan 2020 21:29:43 +1100 Subject: [PATCH 186/192] deps: update term-size with signed version PR-URL: https://github.com/nodejs/node/pull/31459 Refs: https://github.com/nodejs/node/issues/29216 Refs: https://github.com/sindresorhus/macos-term-size/pull/3 Reviewed-By: Christian Clauss Reviewed-By: Michael Dawson Reviewed-By: Ash Cripps Signed-off-by: Rod Vagg --- .../term-size/vendor/macos/term-size | Bin 8760 -> 27264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/deps/npm/node_modules/term-size/vendor/macos/term-size b/deps/npm/node_modules/term-size/vendor/macos/term-size index e383cc737f8e232aecd06a8492657b28d5fb9f1c..c32a1fdc1b073bb41e9ddbabbd694a741ed67abc 100755 GIT binary patch literal 27264 zcmeHNcUV(N*H1!dp-T}d5~@hc4Mn8)CRHgSh#@2qA|){i0ydhWC<>xjKoP~d0v155 z*s!uH_Kt|af+#8$%A$NTA(6nkyYKV;vHO+Glesfz=FFM7zqxbf%$eLCeD_@(g+hy< zP#G8$3MCIBUKE8|iNb>%3N;BtZxBhOAQvx>2#;BTuykxdk0pOfK{<~BBZ(B^5gsy@ zh1(u>EEsq+7z&CXVKs@wp`~(08OO_$mjy2asvq-MAdMYmheU=TNg}a1$uaC7MdRgl z^5p69IQTTbfdo&U7_VSN9x0BNz@)K2*?4)GACRFFu?Nh$OsDv6#npTVWa&c_j+JUHhtKzh84k(!YwoY(2xd3ARQ zaS^Bi^EYN*`SZ(5!S{yZN7#x)N=hKH(-LDC2_z1}qyC9z&e?Sz@DF<_O~{^W4$AN#qCrH-0>nUs`4KE9V2nUsus=ia{!k8F=Yj$D zI~_z=2j2lb*ap-9dUPycJi%#f4lU8l)eMY_9s`&_FaQzCzlI;db1IL9c)_3vBCL1T z0g=B*V1+OU5fCCEL_mmu5CI_qLInPoA`t4G+wL;kB`i3|d!2(du)ONA87LI-eCIvz zLfQSp9G~1v9>lNS>q1-V<2Vw|xmQ8mQBdcSdo}lEX9>(@4-a#^z1NB!0nNSFPM50- zZI!F@Z}DDdWd%wA{}RB}O#&P_fc9I9bj||>YgIdE!(@7A5=a2^MaZQGxT1Mn(4GTa z#1SrNd;zZWAo0uX%YEvddjW{X1Jb1)G(frMe(=t2kcf1d=R%6QG^}q93WxPAxzB0b zCp@)Udl_TcHaPV*gX9z3l74WtEe;DPGu7#<#ugQu#l=P;)Mb9Z6R80N0Q92{6z zJQr02J{yPfqH#EohZ9G` zv+0Xzf8@}o;=m6G2g5|cA;2J*LK_#_z?L9|v5RL9!#uQ|r9n%1Zfg{Zj23}n{-dD5 zI}aN0QiPY<@=|wR8o*2cBcVbjAp$}Kga`-`5F#K%K!|`40U-iH1cV3(5fCCEMBpDq zKtTu3y*gHKCc&#~DviZrqGqxr3V1XlnZraCIK!Je?oBx<23#J4o9zPLC|P)JOVEq~ z3t5JMq$DudG)N$Tn9l^a*PQu~oDN7jgThIGgfk!*z&8T(-XITdx%oo4qR}L9?~frR z#*t{LG)gju2JY0!uxIT=GCc{U4;>yNr~0y8=Ssle2ZWVy4-o2MT$#G9LuSs9&=KTJIs(P$WKmOhU{>BFJ(ueR}8HE9FI)Z#H;E5jLgY9&j@fhw^F(~4O1^mokq-Ydw#L2~>u@X(8 z-wfp)Zf$L0<2Btg993H<_1@v5!az=`XZ?&<1$Wvia0$6-N!7DV7VaB(Wo7Xfg1J@K zpX}9pBatjw!musost3Nfuzn*|F0?`0blw}KH<2Fd$I2_Z zyH%_|h?(8gyskg=+1HJ;(E0Y(ly3_M=XRY-OYyxj7!j1o*jcXH7qfve=~Ng11wv5) zB1|E8N}voRsI4HvGWbovd6yi0K|qa^19uy@9wGpFaYi}bIeiqW7-sZe5HQDc1CG|C zWkBgjfOq~d;MmDA3uqLMSzsua=E@U*5;fQ?1akMMMD-I7OTblbJxn`y^4!hz5urER<5z94U08u{iYkbPAb6XC&cW zk~wh<7M+tuR6&#=pNOK&2;VFQgM)W-K_*X>A(|uR2+`b}Xil_>m?&dzEkGh^L{r3= z+frK3owkUU0CW@!@8gdDvyS)!lKz_k@!v*T@NER&7s*2F2qFTY6v;x%p+GqflZ8g3 zau)@PMHajC?A0~Kl#?E@oz7bwS-S$~B&L_XzsRXOP!Pf0(XrB$A%*Y zTAFW{9G+LUz`^Ta_ynR8evx6KrpA$r0Sa`3wT%y(6tb=d$bAgsG(C~nK|$4^YTxZn1`4_1}*FqL?6*D(=OA> z(TwA8n6?B0g_U4NhToqVg^@^LfzQYc6d|&ZUk>(w1s`<|A}=Pe0uyB z1P$GVrldQUH`e!*tYOl!cBs5olb@Jtdf?_a>C|u^X~T4j9@Jv>nne}IYflHhJfObg z+N3Qrq{3ne7afmRH)Pv>NJ->0nCZ^!Dc&o>i2l6Sd%xJl(-Ey&TAm)u+qQg}B43Bs z0Xz58D?!PZ9rW{~%28cAr{#Iur0gEgLG0!ufE8s(0uSYzC;baud%`e&tM*RLL+%~Znx!jID9hpF;u(u z{Z{Qs*(dWf9yU7E+E#v+lSLIu9o1Gb<2($Ihdj@p-&xr-c%2SIVaA zrXwpo3%wZVzD47fn%Ex~_oy=X>C*n&wV#EC8;vF&n}wDDJ%X(YjpFT06sjm1$?Qdp z#3YQx(4wN^7_`b{s2SKmHKe4Z2&e|GlAw?YNam#<%orr|xLRiQELsAcoJ67F!4}1d zqqFgpAG;O}ACrbBC#B(;WEKbH*zDv)8XHfhP-slfNSpcGdqHqFn1qLYfW0mT?1pSm zOC__YcxW7e21!&rg^@(1!+nsAXUxYZvuP${I$|h{b zc2(rFGo!DlM@kY3kM+BIe~6x5s{F;lAQr#IVnLrw*6rr*a>79Hf=KFlibYDFt++{8 z_ft7-*{7%2lLBj>OiRD8D)h0bWNjo%^axO4TX|-TQexGV`8~fe1fhav%)e+t0PDsafwm1| zVTmAI>&8leL^5|0)eu!NoMfRv?#5mdDjF?~8#VNZu81>O2v`wmn=z&jw4itvmMuPj zL8X~3VB96rT3v>tBBt`KHmNa!)sab1Fiv{HADfRc`oPlrYa^(RINLBy+QZo7 zO4C`=j(+*h0r|tIm#d5y-_$_SD~Y64B2c z9++8Zy!Q3^)2ns5cQj^q#G&X#6*BYX%U18bm8Y+>1l#!S#=N41Mjg+>3MoksA6tDt z)nRQ}L|i#{#j6+opPBVuLq|N0Y8#?lC0Hx6mp&Ayi7UTyZ5ax^?vkdQS3mfo>~{79 zvc)yI)OM3^N7Edw-ZQN~EM@GTe)RC?Ck7=>HMXB9*C>4zXjhKhRLZNO;+%XUo4jco zO}sK^Pp|KwjhLVB@|pQtGz?yyY}L_TrYe2)KISuKgYDh3YNEyS%?IgE6ff{pBSsO} z({hM3)LUFT2ZNx-7&$*Ri=6q235~{Df=v(M(+E08k7w^NC~T&HR)ZvBVG91uK&OIz zGYidCwop|n0YV-`=`pJE`*`&y{l8QFP2)oY6uuD;nz8zRREN^Y{9^|JQGqj}5A8x} z1jm;lA|hb0uCn;HE@dORVhe48XGe7R4e9dgaeJJ22AIkp<|JAhs~{~0PApc)baNYo z8zK-z0R|b^-Jh788SnF*q<@phpq6z<`0Eq>v9fB1=_Ld@idz}Yn$Dpyr` z-slb4mL0#USiQFDWWWluNowy8#vAmNlq-6Y(eqjt4)5}tu_kX&M2m(ZVumHVLs`513G1jA1f-iu&DOJiot=JoU^gA9h4dCBqn%1Dflizs9v|S zYR~CeAQn8EbJF~PFjLULjLMXL$e=sxq@|M$P+et3o8H z+G|ORQUW}DSZ5T2?^lGyon2=2bcNB}M(y`2B8KL#JH*g7&e-x`L+#C5_bMZv^m;K8 zzD`WKtD$7~2A@)-S>NK+aAA%au|iDwlxnlt&X_X0xho6hBi^tVz3WnFF_~FHc;C5Y z19CIW^MH{qW8;%`uY*gsQl2GVXsi3SuN&un>YS_EiqDE#K~j~p?yO>R!R-0lRPKc< z7q&<4{h|?Sb8BhDSGiI<8=PY!YyE%?b!K=bLc2|Gum0=FDo#zF5Zu?Fac0WYnYUr*7_v8Cumksq~Et z#p%h7884bq{TrvNh*o~1z6!Y9lpLLXzFaN4@L<%epy&P0Rp+~P6TkT1Rc#ACId$`G zv-c)n28Ys24{Kd&E$ziD-JzKk^u%tKkBZsu{^Fd{M;v;6%rDt{e*f5c*JIzC;EO8H z-fA9lYCZ8(6?G+j3UO84fXu^*%R^JvS3GcC)Uwg%kYDlM^WtB=8Wg+Tc6hmDFS!fr zwdvMkc{P7E&l#c`sgA1V4jzF!ruE#bwN&VTtx-D7?;2;+&So&5_d&GM#xz|CgsmD6{H z8}tvfPHpbh*a(_%olDTh5(NaSy6`mm0Wsto7Qi^s_^E<~2Spl%{C~t4`?*mi zfmlFeOABj)7$Jsb24#9V{M}-#(~iSmeO%AosJ9^G#k0V~k8>X$D)K&1ay!__W&6=( zW4#T)5*zEI=AMJ)KKI)GT5_SGG*3nM3eINBim8^KKFznH6g?OJe!xaGfAZVL{*u@$ zTOwmB({B~VUa;sitDNta6Qy(L%U$DmQ)#0x?eJ#UY`b)?tLK88qfnxV^FpJ<_X{@mhf z@>$&u|BB0UbtTQ)s~cp$cJ*jinNG<2TJ~$gOBHh3s@+qi&em-vuqHP~D~DyoI@}Bh zYMQ0n$2_mTAU3p7v`BaR$^g?$<-IAF?AL_ct6gELu5$VPq%-%~;-PN~EK6dV1v(gj!y0MN!uC0>GaBEJe4zZmHXB!>|U7m97`w2^C znT(?SS&4w+jSL&>r=_$nHI&Z<*L)JA@b_%KNuK&_`{~vUI#qO=XUkKo2Ab;RD=Os^ z$TenPKIF>eJ$LvjcAJr-?U9@%PMp$KEVy?JLQ?nzYllh z`Lx;L2ITj#EKZv=%!pv&Y=Ss143pAG%l98px*b*>maw<$LqpZT8~l3JtA?Cb3IALJ z*R*+GqhEW3@A`eF&%DAJ{wLqxXV*T(E_pp`XJh%KsEYWl38k%XIi~K8Eo4V=kL8xh z23ws=l1r~4?|jdt(s33+J4WYG0CZY{fQyZ2Xn6~%Z-4(a`%`C_3pYL}JI_+sQ z$f&iAe|V{Vz&!Js`EKu5>&40^wN*FT`MzxVRrc87t#`789{rJP#lKtWLXSSYyAyi! zg&zI?Q67C_7RCu!GIm_=(CUBJTEIyBI8TnAc*`>L5VapCW8fQ-8FK>u8}E?rXiF>+ z+?BuDxjIo^?{fZS5$x35%y8`&cq9<~+d<$oE=|-xzzqQcn9~E=`kLF-y_Y>cAv?cD zp?aHa(HTJ#kLS}kU3T?5{%o+8Ixed6`+=^gq2&buloA!BLrlF%wItF(6?^O!>(u)I zgWvWs)Rf4PSNb0obluHWvb*Hn@px{m*g=ABm)Ep&)j?b9Hx?|6oe~~jY}M(zwIQk9 zBV%cy+wA1sR>w%@MooV8GI{~W&_@QgCV0QY>F2$cJJ~G96uW$~g>ia)#R6(+mC4-Q zS53d-kP?^5$ENS^&)v7jX`fAPOV2{L4<(c%hj$#F7HSlxP$llYN2|$3uWHDyILfy% zKFz!*EqktKu^}SbF%6BeaaZwL=&>Jn!MXL~0-q~(j^WSSWsUF5NtpZGwMgn_QId`L zx%X06v`=KVcdvN1HhmxWA2Y&-5CI_qLIi{e2oVq>AVffjfDi#80zw3Y2nZ1nA|OOS uh=33QAp$}Kga`-`5F#K%K!|`40U-iH1cV3(5fCCEL_mmu5P|=n2>chn&~6a` delta 581 zcmZp;%DBTt^!%^4|JWHA7?~Lu8kiUu82EvhVWMa^Bm2aK=8U%|-gFmy!v~ZHimHI5 zfZ)w!Lq>Z+0ia5ds1I0-fk9w$BTzI3DjEb8jhTFraXBZ@0tTQ349t@+GO2Si2tafS zOy0<(Ik}5TPE`OV0y7K92dRevHXwszvMh6r1W*DD6d;rZ$jLy!K6xYad4=^_EX&;` z&8_yd3eT7OY#|A9FjxZ*7Xt$jPu{>{$z*(O@@p2?&EWzcI3^45@j6%oJ+Y6afq}vB zz>DiZ4V{OZfB!FWcQyRx(Hr_;Uq%UsgW-YB`!BWvOh5|P=%Hdg+>sC zo^XZxfGooUKAqn?Z@o~Qe34JwaKe98J)oxlss>=v2uzxQNi#5M0VI8TWf_5#WAJ~~ zkCP?%OD9j{cV~Pv`6j=+t^zb_w4k&Jl(vJ?9#A?6L~nL<-^@QrL7b6cvxGteqW~y2 USb!K9kRaeNxj|84@&UyP02%;+;{X5v From c933cbfd74a8ba3ba18a9aa30a9230bd21c0e4e0 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 4 Mar 2020 12:14:43 +1100 Subject: [PATCH 187/192] doc: official macOS builds now on 10.15 + Xcode 11 PR-URL: https://github.com/nodejs/node/pull/31459 Refs: https://github.com/nodejs/node/issues/29216 Reviewed-By: Christian Clauss Reviewed-By: Michael Dawson Reviewed-By: Ash Cripps Signed-off-by: Rod Vagg --- BUILDING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILDING.md b/BUILDING.md index 7a923850e6e..5c3923f2157 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -168,7 +168,7 @@ Binaries at are produced on: | Binary package | Platform and Toolchain | | --------------------- | ------------------------------------------------------------------------ | | aix-ppc64 | AIX 7.1 TL05 on PPC64BE with GCC 6 | -| darwin-x64 (and .pkg) | macOS 10.11, Xcode Command Line Tools 10 with -mmacosx-version-min=10.10 | +| darwin-x64 (and .pkg) | macOS 10.15, Xcode Command Line Tools 11 with -mmacosx-version-min=10.10 | | linux-arm64 | CentOS 7 with devtoolset-6 / GCC 6 | | linux-armv7l | Cross-compiled on Ubuntu 16.04 x64 with [custom GCC toolchain](https://github.com/rvagg/rpi-newer-crosstools) | | linux-ppc64le | CentOS 7 with devtoolset-6 / GCC 6 [7](#fn7) | From de8fab95a8525105b5611a0f815c72bb0f9c8a30 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Sat, 7 Mar 2020 12:14:18 -0800 Subject: [PATCH 188/192] build: workaround for gclient python3 issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gclient doesn't support Python 3 yet. To workaround that problem, add an enviroment variable to override the Python version used by ./configure. Signed-off-by: Matheus Marchini PR-URL: https://github.com/nodejs/node/pull/32140 Reviewed-By: Anna Henningsen Reviewed-By: Michaël Zasso Reviewed-By: Christian Clauss --- configure | 1 + 1 file changed, 1 insertion(+) diff --git a/configure b/configure index 39decd9a55c..bc0a01d9855 100755 --- a/configure +++ b/configure @@ -7,6 +7,7 @@ # pyenv will alert which shims are available and then will fail the build. _=[ 'exec' '/bin/sh' '-c' ''' test ${TRAVIS} && exec python "$0" "$@" # workaround for pyenv on Travis CI +test ${FORCE_PYTHON2} && exec python2 "$0" "$@" # workaround for gclient which python3.8 >/dev/null && exec python3.8 "$0" "$@" which python3.7 >/dev/null && exec python3.7 "$0" "$@" which python3.6 >/dev/null && exec python3.6 "$0" "$@" From 173d044d0976e30bc2ab0a40807ec617b0b8a55a Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 8 Mar 2020 12:39:50 +0100 Subject: [PATCH 189/192] http: align OutgoingMessage and ClientRequest destroy Added .destroyed property to OutgoingMessage and ClientRequest to align with streams. Fixed ClientRequest.destroy to dump res and re-use socket in agent pool aligning it with abort. PR-URL: https://github.com/nodejs/node/pull/32148 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- lib/_http_client.js | 47 ++++++++---- lib/_http_outgoing.js | 6 ++ lib/internal/streams/destroy.js | 1 - .../test-http-client-abort-destroy.js | 71 +++++++++++++++++++ ...ient-abort-keep-alive-queued-tcp-socket.js | 57 +++++++++------ test/parallel/test-http-client-close-event.js | 4 +- test/parallel/test-http-outgoing-proto.js | 7 ++ 7 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 test/parallel/test-http-client-abort-destroy.js diff --git a/lib/_http_client.js b/lib/_http_client.js index fef4b635a00..c447b695c8b 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -29,6 +29,7 @@ const { ObjectAssign, ObjectKeys, ObjectSetPrototypeOf, + Symbol } = primordials; const net = require('net'); @@ -65,6 +66,7 @@ const { } = require('internal/dtrace'); const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/; +const kError = Symbol('kError'); function validateHost(host, name) { if (host !== null && host !== undefined && typeof host !== 'string') { @@ -337,10 +339,19 @@ ClientRequest.prototype._implicitHeader = function _implicitHeader() { }; ClientRequest.prototype.abort = function abort() { - if (!this.aborted) { - process.nextTick(emitAbortNT, this); + if (this.aborted) { + return; } this.aborted = true; + process.nextTick(emitAbortNT, this); + this.destroy(); +}; + +ClientRequest.prototype.destroy = function destroy(err) { + if (this.destroyed) { + return; + } + this.destroyed = true; // If we're aborting, we don't care about any more response data. if (this.res) { @@ -350,11 +361,29 @@ ClientRequest.prototype.abort = function abort() { // In the event that we don't have a socket, we will pop out of // the request queue through handling in onSocket. if (this.socket) { - // in-progress - this.socket.destroy(); + _destroy(this, this.socket, err); + } else if (err) { + this[kError] = err; } }; +function _destroy(req, socket, err) { + // TODO (ronag): Check if socket was used at all (e.g. headersSent) and + // re-use it in that case. `req.socket` just checks whether the socket was + // assigned to the request and *might* have been used. + if (!req.agent || req.socket) { + socket.destroy(err); + } else { + socket.emit('free'); + if (!req.aborted && !err) { + err = connResetException('socket hang up'); + } + if (err) { + req.emit('error', err); + } + req.emit('close'); + } +} function emitAbortNT(req) { req.emit('abort'); @@ -750,14 +779,8 @@ ClientRequest.prototype.onSocket = function onSocket(socket) { }; function onSocketNT(req, socket) { - if (req.aborted) { - // If we were aborted while waiting for a socket, skip the whole thing. - if (!req.agent) { - socket.destroy(); - } else { - req.emit('close'); - socket.emit('free'); - } + if (req.destroyed) { + _destroy(req, socket, req[kError]); } else { tickOnSocket(req, socket); } diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index b02edc7f34f..a7bd6af6007 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -93,6 +93,7 @@ function OutgoingMessage() { this.outputSize = 0; this.writable = true; + this.destroyed = false; this._last = false; this.chunkedEncoding = false; @@ -277,6 +278,11 @@ OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { // any messages, before ever calling this. In that case, just skip // it, since something else is destroying this connection anyway. OutgoingMessage.prototype.destroy = function destroy(error) { + if (this.destroyed) { + return; + } + this.destroyed = true; + if (this.socket) { this.socket.destroy(error); } else { diff --git a/lib/internal/streams/destroy.js b/lib/internal/streams/destroy.js index 3a953afd445..6eb46c7f7c4 100644 --- a/lib/internal/streams/destroy.js +++ b/lib/internal/streams/destroy.js @@ -163,7 +163,6 @@ function isRequest(stream) { // Normalize destroy for legacy. function destroyer(stream, err) { - // request.destroy just do .end - .abort is what we want if (isRequest(stream)) return stream.abort(); if (isRequest(stream.req)) return stream.req.abort(); if (typeof stream.destroy === 'function') return stream.destroy(err); diff --git a/test/parallel/test-http-client-abort-destroy.js b/test/parallel/test-http-client-abort-destroy.js new file mode 100644 index 00000000000..6db2ea5682e --- /dev/null +++ b/test/parallel/test-http-client-abort-destroy.js @@ -0,0 +1,71 @@ +'use strict'; +const common = require('../common'); +const http = require('http'); +const assert = require('assert'); + +{ + // abort + + const server = http.createServer(common.mustCall((req, res) => { + res.end('Hello'); + })); + + server.listen(0, common.mustCall(() => { + const options = { port: server.address().port }; + const req = http.get(options, common.mustCall((res) => { + res.on('data', (data) => { + req.abort(); + assert.strictEqual(req.aborted, true); + assert.strictEqual(req.destroyed, true); + server.close(); + }); + })); + req.on('error', common.mustNotCall()); + assert.strictEqual(req.aborted, false); + assert.strictEqual(req.destroyed, false); + })); +} + +{ + // destroy + res + + const server = http.createServer(common.mustCall((req, res) => { + res.end('Hello'); + })); + + server.listen(0, common.mustCall(() => { + const options = { port: server.address().port }; + const req = http.get(options, common.mustCall((res) => { + res.on('data', (data) => { + req.destroy(); + assert.strictEqual(req.aborted, false); + assert.strictEqual(req.destroyed, true); + server.close(); + }); + })); + req.on('error', common.mustNotCall()); + assert.strictEqual(req.aborted, false); + assert.strictEqual(req.destroyed, false); + })); +} + +{ + // destroy + + const server = http.createServer(common.mustNotCall((req, res) => { + })); + + server.listen(0, common.mustCall(() => { + const options = { port: server.address().port }; + const req = http.get(options, common.mustNotCall()); + req.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + server.close(); + })); + assert.strictEqual(req.aborted, false); + assert.strictEqual(req.destroyed, false); + req.destroy(); + assert.strictEqual(req.aborted, false); + assert.strictEqual(req.destroyed, true); + })); +} diff --git a/test/parallel/test-http-client-abort-keep-alive-queued-tcp-socket.js b/test/parallel/test-http-client-abort-keep-alive-queued-tcp-socket.js index 6282aa3da7c..c9614f01c3d 100644 --- a/test/parallel/test-http-client-abort-keep-alive-queued-tcp-socket.js +++ b/test/parallel/test-http-client-abort-keep-alive-queued-tcp-socket.js @@ -3,34 +3,45 @@ const common = require('../common'); const assert = require('assert'); const http = require('http'); -let socketsCreated = 0; -class Agent extends http.Agent { - createConnection(options, oncreate) { - const socket = super.createConnection(options, oncreate); - socketsCreated++; - return socket; +for (const destroyer of ['destroy', 'abort']) { + let socketsCreated = 0; + + class Agent extends http.Agent { + createConnection(options, oncreate) { + const socket = super.createConnection(options, oncreate); + socketsCreated++; + return socket; + } } -} -const server = http.createServer((req, res) => res.end()); + const server = http.createServer((req, res) => res.end()); -server.listen(0, common.mustCall(() => { - const port = server.address().port; - const agent = new Agent({ - keepAlive: true, - maxSockets: 1 - }); + server.listen(0, common.mustCall(() => { + const port = server.address().port; + const agent = new Agent({ + keepAlive: true, + maxSockets: 1 + }); - http.get({ agent, port }, (res) => res.resume()); + http.get({ agent, port }, (res) => res.resume()); - const req = http.get({ agent, port }, common.mustNotCall()); - req.abort(); + const req = http.get({ agent, port }, common.mustNotCall()); + req[destroyer](); - http.get({ agent, port }, common.mustCall((res) => { - res.resume(); - assert.strictEqual(socketsCreated, 1); - agent.destroy(); - server.close(); + if (destroyer === 'destroy') { + req.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); + } else { + req.on('error', common.mustNotCall()); + } + + http.get({ agent, port }, common.mustCall((res) => { + res.resume(); + assert.strictEqual(socketsCreated, 1); + agent.destroy(); + server.close(); + })); })); -})); +} diff --git a/test/parallel/test-http-client-close-event.js b/test/parallel/test-http-client-close-event.js index 7573931ac48..b539423a80f 100644 --- a/test/parallel/test-http-client-close-event.js +++ b/test/parallel/test-http-client-close-event.js @@ -14,12 +14,12 @@ server.listen(0, common.mustCall(() => { const req = http.get({ port: server.address().port }, common.mustNotCall()); let errorEmitted = false; - req.on('error', (err) => { + req.on('error', common.mustCall((err) => { errorEmitted = true; assert.strictEqual(err.constructor, Error); assert.strictEqual(err.message, 'socket hang up'); assert.strictEqual(err.code, 'ECONNRESET'); - }); + })); req.on('close', common.mustCall(() => { assert.strictEqual(errorEmitted, true); diff --git a/test/parallel/test-http-outgoing-proto.js b/test/parallel/test-http-outgoing-proto.js index 3c62eadc003..4a07d18c601 100644 --- a/test/parallel/test-http-outgoing-proto.js +++ b/test/parallel/test-http-outgoing-proto.js @@ -122,3 +122,10 @@ assert.throws(() => { name: 'TypeError', message: 'Invalid character in trailer content ["404"]' }); + +{ + const outgoingMessage = new OutgoingMessage(); + assert.strictEqual(outgoingMessage.destroyed, false); + outgoingMessage.destroy(); + assert.strictEqual(outgoingMessage.destroyed, true); +} From 28fae8bff1949c5008b518fa24fbc5ff5c468267 Mon Sep 17 00:00:00 2001 From: Gerhard Stoebich <18708370+Flarna@users.noreply.github.com> Date: Tue, 3 Mar 2020 08:52:27 +0100 Subject: [PATCH 190/192] doc: change worker.takeHeapSnapshot to getHeapSnapshot Adapt doc to match implementation which exports getHeapSnapshot(). PR-URL: https://github.com/nodejs/node/pull/32061 Refs: https://github.com/nodejs/node/pull/31569 Refs: https://github.com/nodejs/node/blob/987a67339518d0380177a2e589f2bbd274230d0e/lib/internal/worker.js#L323 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Shelley Vohr Reviewed-By: Matheus Marchini Reviewed-By: James M Snell Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott Reviewed-By: Ruben Bridgewater --- doc/api/worker_threads.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index e773949b49d..406728995c9 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -612,6 +612,21 @@ added: v10.5.0 The `'online'` event is emitted when the worker thread has started executing JavaScript code. +### `worker.getHeapSnapshot()` + + +* Returns: {Promise} A promise for a Readable Stream containing + a V8 heap snapshot + +Returns a readable stream for a V8 snapshot of the current state of the Worker. +See [`v8.getHeapSnapshot()`][] for more details. + +If the Worker thread is no longer running, which may occur before the +[`'exit'` event][] is emitted, the returned `Promise` will be rejected +immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. + ### `worker.postMessage(value[, transferList])` - -* Returns: {Promise} A promise for a Readable Stream containing - a V8 heap snapshot - -Returns a readable stream for a V8 snapshot of the current state of the Worker. -See [`v8.getHeapSnapshot()`][] for more details. - -If the Worker thread is no longer running, which may occur before the -[`'exit'` event][] is emitted, the returned `Promise` will be rejected -immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. - ### `worker.terminate()`