lib,src: use V8 API for collection inspection

Use a new public V8 API for inspecting weak collections and
collection iterators, rather than using V8-internal functions
to achieve this. This currently comes with a slight modification of
the output for inspecting iterators generated by `Set().entries()`.

Fixes: https://github.com/nodejs/node/issues/20409

PR-URL: https://github.com/nodejs/node/pull/20719
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
Anna Henningsen 2018-05-14 17:30:27 +02:00
parent 143a2f8d67
commit 70cc5da0f1
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
8 changed files with 47 additions and 92 deletions

View File

@ -29,7 +29,7 @@ const {
ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_VALUE,
}, },
} = require('internal/errors'); } = require('internal/errors');
const { previewMapIterator, previewSetIterator } = require('internal/v8'); const { previewEntries } = process.binding('util');
const { Buffer: { isBuffer } } = require('buffer'); const { Buffer: { isBuffer } } = require('buffer');
const util = require('util'); const util = require('util');
const { const {
@ -345,7 +345,7 @@ Console.prototype.table = function(tabularData, properties) {
const mapIter = isMapIterator(tabularData); const mapIter = isMapIterator(tabularData);
if (mapIter) if (mapIter)
tabularData = previewMapIterator(tabularData); tabularData = previewEntries(tabularData);
if (mapIter || isMap(tabularData)) { if (mapIter || isMap(tabularData)) {
const keys = []; const keys = [];
@ -367,7 +367,7 @@ Console.prototype.table = function(tabularData, properties) {
const setIter = isSetIterator(tabularData); const setIter = isSetIterator(tabularData);
if (setIter) if (setIter)
tabularData = previewSetIterator(tabularData); tabularData = previewEntries(tabularData);
const setlike = setIter || isSet(tabularData); const setlike = setIter || isSet(tabularData);
if (setlike) { if (setlike) {

View File

@ -29,7 +29,6 @@
// Do this good and early, since it handles errors. // Do this good and early, since it handles errors.
setupProcessFatal(); setupProcessFatal();
setupV8();
setupProcessICUVersions(); setupProcessICUVersions();
setupGlobalVariables(); setupGlobalVariables();
@ -479,22 +478,6 @@
}; };
} }
function setupV8() {
// Warm up the map and set iterator preview functions. V8 compiles
// functions lazily (unless --nolazy is set) so we need to do this
// before we turn off --allow_natives_syntax again.
const v8 = NativeModule.require('internal/v8');
v8.previewMapIterator(new Map().entries());
v8.previewSetIterator(new Set().entries());
v8.previewWeakMap(new WeakMap(), 1);
v8.previewWeakSet(new WeakSet(), 1);
// Disable --allow_natives_syntax again unless it was explicitly
// specified on the command line.
const re = /^--allow[-_]natives[-_]syntax$/;
if (!process.execArgv.some((s) => re.test(s)))
process.binding('v8').setFlagsFromString('--noallow_natives_syntax');
}
function setupProcessICUVersions() { function setupProcessICUVersions() {
const icu = process.binding('config').hasIntl ? const icu = process.binding('config').hasIntl ?
process.binding('icu') : undefined; process.binding('icu') : undefined;

View File

@ -1,39 +0,0 @@
'use strict';
// This file provides access to some of V8's native runtime functions. See
// https://github.com/v8/v8/wiki/Built-in-functions for further information
// about their implementation.
// They have to be loaded before anything else to make sure we deactivate them
// before executing any other code. Gaining access is achieved by using a
// specific flag that is used internally in the startup phase.
// Clone the provided Map Iterator.
function previewMapIterator(it) {
return %MapIteratorClone(it);
}
// Clone the provided Set Iterator.
function previewSetIterator(it) {
return %SetIteratorClone(it);
}
// Retrieve all WeakMap instance key / value pairs up to `max`. `max` limits the
// number of key / value pairs returned. Make sure it is a positive number,
// otherwise V8 aborts. Passing through `0` returns all elements.
function previewWeakMap(weakMap, max) {
return %GetWeakMapEntries(weakMap, max);
}
// Retrieve all WeakSet instance values up to `max`. `max` limits the
// number of key / value pairs returned. Make sure it is a positive number,
// otherwise V8 aborts. Passing through `0` returns all elements.
function previewWeakSet(weakSet, max) {
return %GetWeakSetValues(weakSet, max);
}
module.exports = {
previewMapIterator,
previewSetIterator,
previewWeakMap,
previewWeakSet
};

View File

@ -30,18 +30,12 @@ const {
const { TextDecoder, TextEncoder } = require('internal/encoding'); const { TextDecoder, TextEncoder } = require('internal/encoding');
const { isBuffer } = require('buffer').Buffer; const { isBuffer } = require('buffer').Buffer;
const {
previewMapIterator,
previewSetIterator,
previewWeakMap,
previewWeakSet
} = require('internal/v8');
const { const {
getPromiseDetails, getPromiseDetails,
getProxyDetails, getProxyDetails,
kPending, kPending,
kRejected, kRejected,
previewEntries
} = process.binding('util'); } = process.binding('util');
const { internalBinding } = require('internal/bootstrap/loaders'); const { internalBinding } = require('internal/bootstrap/loaders');
@ -912,7 +906,7 @@ function formatMap(ctx, value, recurseTimes, keys) {
function formatWeakSet(ctx, value, recurseTimes, keys) { function formatWeakSet(ctx, value, recurseTimes, keys) {
const maxArrayLength = Math.max(ctx.maxArrayLength, 0); const maxArrayLength = Math.max(ctx.maxArrayLength, 0);
const entries = previewWeakSet(value, maxArrayLength + 1); const entries = previewEntries(value).slice(0, maxArrayLength + 1);
const maxLength = Math.min(maxArrayLength, entries.length); const maxLength = Math.min(maxArrayLength, entries.length);
let output = new Array(maxLength); let output = new Array(maxLength);
for (var i = 0; i < maxLength; ++i) for (var i = 0; i < maxLength; ++i)
@ -929,16 +923,14 @@ function formatWeakSet(ctx, value, recurseTimes, keys) {
function formatWeakMap(ctx, value, recurseTimes, keys) { function formatWeakMap(ctx, value, recurseTimes, keys) {
const maxArrayLength = Math.max(ctx.maxArrayLength, 0); const maxArrayLength = Math.max(ctx.maxArrayLength, 0);
const entries = previewWeakMap(value, maxArrayLength + 1); const entries = previewEntries(value).slice(0, maxArrayLength + 1);
// Entries exist as [key1, val1, key2, val2, ...] const remainder = entries.length > maxArrayLength;
const remainder = entries.length / 2 > maxArrayLength; const len = entries.length - (remainder ? 1 : 0);
const len = entries.length / 2 - (remainder ? 1 : 0);
const maxLength = Math.min(maxArrayLength, len); const maxLength = Math.min(maxArrayLength, len);
let output = new Array(maxLength); let output = new Array(maxLength);
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
const pos = i * 2; output[i] = `${formatValue(ctx, entries[i][0], recurseTimes)} => ` +
output[i] = `${formatValue(ctx, entries[pos], recurseTimes)} => ` + formatValue(ctx, entries[i][1], recurseTimes);
formatValue(ctx, entries[pos + 1], recurseTimes);
} }
// Sort all entries to have a halfway reliable output (if more entries than // Sort all entries to have a halfway reliable output (if more entries than
// retrieved ones exist, we can not reliably return the same output). // retrieved ones exist, we can not reliably return the same output).
@ -950,9 +942,9 @@ function formatWeakMap(ctx, value, recurseTimes, keys) {
return output; return output;
} }
function formatCollectionIterator(preview, ctx, value, recurseTimes, keys) { function formatCollectionIterator(ctx, value, recurseTimes, keys) {
const output = []; const output = [];
for (const entry of preview(value)) { for (const entry of previewEntries(value)) {
if (ctx.maxArrayLength === output.length) { if (ctx.maxArrayLength === output.length) {
output.push('... more items'); output.push('... more items');
break; break;
@ -966,13 +958,11 @@ function formatCollectionIterator(preview, ctx, value, recurseTimes, keys) {
} }
function formatMapIterator(ctx, value, recurseTimes, keys) { function formatMapIterator(ctx, value, recurseTimes, keys) {
return formatCollectionIterator(previewMapIterator, ctx, value, recurseTimes, return formatCollectionIterator(ctx, value, recurseTimes, keys);
keys);
} }
function formatSetIterator(ctx, value, recurseTimes, keys) { function formatSetIterator(ctx, value, recurseTimes, keys) {
return formatCollectionIterator(previewSetIterator, ctx, value, recurseTimes, return formatCollectionIterator(ctx, value, recurseTimes, keys);
keys);
} }
function formatPromise(ctx, value, recurseTimes, keys) { function formatPromise(ctx, value, recurseTimes, keys) {

View File

@ -146,7 +146,6 @@
'lib/internal/http2/core.js', 'lib/internal/http2/core.js',
'lib/internal/http2/compat.js', 'lib/internal/http2/compat.js',
'lib/internal/http2/util.js', 'lib/internal/http2/util.js',
'lib/internal/v8.js',
'lib/internal/v8_prof_polyfill.js', 'lib/internal/v8_prof_polyfill.js',
'lib/internal/v8_prof_processor.js', 'lib/internal/v8_prof_processor.js',
'lib/internal/validators.js', 'lib/internal/validators.js',

View File

@ -4355,12 +4355,6 @@ void Init(int* argc,
} }
#endif #endif
// Needed for access to V8 intrinsics. Disabled again during bootstrapping,
// see lib/internal/bootstrap/node.js.
const char allow_natives_syntax[] = "--allow_natives_syntax";
V8::SetFlagsFromString(allow_natives_syntax,
sizeof(allow_natives_syntax) - 1);
// We should set node_is_initialized here instead of in node::Start, // We should set node_is_initialized here instead of in node::Start,
// otherwise embedders using node::Init to initialize everything will not be // otherwise embedders using node::Init to initialize everything will not be
// able to set it and native modules will not load for them. // able to set it and native modules will not load for them.

View File

@ -49,6 +49,35 @@ static void GetProxyDetails(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(ret); args.GetReturnValue().Set(ret);
} }
static void PreviewEntries(const FunctionCallbackInfo<Value>& args) {
if (!args[0]->IsObject())
return;
bool is_key_value;
Local<Array> entries;
if (!args[0].As<Object>()->PreviewEntries(&is_key_value).ToLocal(&entries))
return;
if (!is_key_value)
return args.GetReturnValue().Set(entries);
uint32_t length = entries->Length();
CHECK_EQ(length % 2, 0);
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
Local<Array> pairs = Array::New(env->isolate(), length / 2);
for (uint32_t i = 0; i < length / 2; i++) {
Local<Array> pair = Array::New(env->isolate(), 2);
pair->Set(context, 0, entries->Get(context, i * 2).ToLocalChecked())
.FromJust();
pair->Set(context, 1, entries->Get(context, i * 2 + 1).ToLocalChecked())
.FromJust();
pairs->Set(context, i, pair).FromJust();
}
args.GetReturnValue().Set(pairs);
}
// Side effect-free stringification that will never throw exceptions. // Side effect-free stringification that will never throw exceptions.
static void SafeToString(const FunctionCallbackInfo<Value>& args) { static void SafeToString(const FunctionCallbackInfo<Value>& args) {
auto context = args.GetIsolate()->GetCurrentContext(); auto context = args.GetIsolate()->GetCurrentContext();
@ -188,6 +217,7 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "getPromiseDetails", GetPromiseDetails); env->SetMethod(target, "getPromiseDetails", GetPromiseDetails);
env->SetMethod(target, "getProxyDetails", GetProxyDetails); env->SetMethod(target, "getProxyDetails", GetProxyDetails);
env->SetMethod(target, "safeToString", SafeToString); env->SetMethod(target, "safeToString", SafeToString);
env->SetMethod(target, "previewEntries", PreviewEntries);
env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog); env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog);
env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog); env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog);

View File

@ -19,14 +19,13 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
// Flags: --expose_internals
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const JSStream = process.binding('js_stream').JSStream; const JSStream = process.binding('js_stream').JSStream;
const util = require('util'); const util = require('util');
const vm = require('vm'); const vm = require('vm');
const { previewMapIterator } = require('internal/v8'); const { previewEntries } = process.binding('util');
assert.strictEqual(util.inspect(1), '1'); assert.strictEqual(util.inspect(1), '1');
assert.strictEqual(util.inspect(false), 'false'); assert.strictEqual(util.inspect(false), 'false');
@ -448,7 +447,7 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324');
{ {
const map = new Map(); const map = new Map();
map.set(1, 2); map.set(1, 2);
const vals = previewMapIterator(map.entries()); const vals = previewEntries(map.entries());
const valsOutput = []; const valsOutput = [];
for (const o of vals) { for (const o of vals) {
valsOutput.push(o); valsOutput.push(o);
@ -935,8 +934,7 @@ if (typeof Symbol !== 'undefined') {
const aSet = new Set([1, 3]); const aSet = new Set([1, 3]);
assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }'); assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }');
assert.strictEqual(util.inspect(aSet.values()), '[Set Iterator] { 1, 3 }'); assert.strictEqual(util.inspect(aSet.values()), '[Set Iterator] { 1, 3 }');
assert.strictEqual(util.inspect(aSet.entries()), assert.strictEqual(util.inspect(aSet.entries()), '[Set Iterator] { 1, 3 }');
'[Set Iterator] { [ 1, 1 ], [ 3, 3 ] }');
// Make sure the iterator doesn't get consumed. // Make sure the iterator doesn't get consumed.
const keys = aSet.keys(); const keys = aSet.keys();
assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }'); assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');