Skip to content

Commit 19a3f8b

Browse files
BridgeARBethGriggs
authored andcommitted
util: refactor inspect code for constistency
This removes the special handling to inspect iterable objects with a null prototype. It is now handled together with the regular prototype. Backport-PR-URL: #31431 PR-URL: #30225 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 7686865 commit 19a3f8b

File tree

2 files changed

+80
-92
lines changed

2 files changed

+80
-92
lines changed

lib/internal/util/inspect.js

+67-92
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
ErrorPrototypeToString,
1212
JSONStringify,
1313
Map,
14+
MapPrototype,
1415
MapPrototypeEntries,
1516
MathFloor,
1617
MathMax,
@@ -22,10 +23,8 @@ const {
2223
NumberPrototypeValueOf,
2324
ObjectAssign,
2425
ObjectCreate,
25-
ObjectDefineProperties,
2626
ObjectDefineProperty,
2727
ObjectGetOwnPropertyDescriptor,
28-
ObjectGetOwnPropertyDescriptors,
2928
ObjectGetOwnPropertyNames,
3029
ObjectGetOwnPropertySymbols,
3130
ObjectGetPrototypeOf,
@@ -36,6 +35,7 @@ const {
3635
ObjectSeal,
3736
RegExpPrototypeToString,
3837
Set,
38+
SetPrototype,
3939
SetPrototypeValues,
4040
StringPrototypeValueOf,
4141
SymbolPrototypeToString,
@@ -115,6 +115,11 @@ const assert = require('internal/assert');
115115

116116
const { NativeModule } = require('internal/bootstrap/loaders');
117117

118+
const setSizeGetter = uncurryThis(
119+
ObjectGetOwnPropertyDescriptor(SetPrototype, 'size').get);
120+
const mapSizeGetter = uncurryThis(
121+
ObjectGetOwnPropertyDescriptor(MapPrototype, 'size').get);
122+
118123
let hexSlice;
119124

120125
const builtInObjects = new Set(
@@ -648,51 +653,6 @@ function findTypedConstructor(value) {
648653
}
649654
}
650655

651-
let lazyNullPrototypeCache;
652-
// Creates a subclass and name
653-
// the constructor as `${clazz} : null prototype`
654-
function clazzWithNullPrototype(clazz, name) {
655-
if (lazyNullPrototypeCache === undefined) {
656-
lazyNullPrototypeCache = new Map();
657-
} else {
658-
const cachedClass = lazyNullPrototypeCache.get(clazz);
659-
if (cachedClass !== undefined) {
660-
return cachedClass;
661-
}
662-
}
663-
class NullPrototype extends clazz {
664-
get [SymbolToStringTag]() {
665-
return '';
666-
}
667-
}
668-
ObjectDefineProperty(NullPrototype.prototype.constructor, 'name',
669-
{ value: `[${name}: null prototype]` });
670-
lazyNullPrototypeCache.set(clazz, NullPrototype);
671-
return NullPrototype;
672-
}
673-
674-
function noPrototypeIterator(ctx, value, recurseTimes) {
675-
let newVal;
676-
if (isSet(value)) {
677-
const clazz = clazzWithNullPrototype(Set, 'Set');
678-
newVal = new clazz(SetPrototypeValues(value));
679-
} else if (isMap(value)) {
680-
const clazz = clazzWithNullPrototype(Map, 'Map');
681-
newVal = new clazz(MapPrototypeEntries(value));
682-
} else if (ArrayIsArray(value)) {
683-
const clazz = clazzWithNullPrototype(Array, 'Array');
684-
newVal = new clazz(value.length);
685-
} else if (isTypedArray(value)) {
686-
const constructor = findTypedConstructor(value);
687-
const clazz = clazzWithNullPrototype(constructor, constructor.name);
688-
newVal = new clazz(value);
689-
}
690-
if (newVal !== undefined) {
691-
ObjectDefineProperties(newVal, ObjectGetOwnPropertyDescriptors(value));
692-
return formatRaw(ctx, newVal, recurseTimes);
693-
}
694-
}
695-
696656
// Note: using `formatValue` directly requires the indentation level to be
697657
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
698658
// value afterwards again.
@@ -795,7 +755,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
795755
let extrasType = kObjectType;
796756

797757
// Iterators and the rest are split to reduce checks.
798-
if (value[SymbolIterator]) {
758+
// We have to check all values in case the constructor is set to null.
759+
// Otherwise it would not possible to identify all types properly.
760+
if (value[SymbolIterator] || constructor === null) {
799761
noIterator = false;
800762
if (ArrayIsArray(value)) {
801763
keys = getOwnNonIndexProperties(value, filter);
@@ -807,37 +769,66 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
807769
extrasType = kArrayExtrasType;
808770
formatter = formatArray;
809771
} else if (isSet(value)) {
772+
const size = setSizeGetter(value);
810773
keys = getKeys(value, ctx.showHidden);
811-
const prefix = getPrefix(constructor, tag, 'Set');
812-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
774+
let prefix = '';
775+
if (constructor !== null) {
776+
if (constructor === tag)
777+
tag = '';
778+
prefix = getPrefix(`${constructor}`, tag, '');
779+
formatter = formatSet.bind(null, value, size);
780+
} else {
781+
prefix = getPrefix(constructor, tag, 'Set');
782+
formatter = formatSet.bind(null, SetPrototypeValues(value), size);
783+
}
784+
if (size === 0 && keys.length === 0 && protoProps === undefined)
813785
return `${prefix}{}`;
814786
braces = [`${prefix}{`, '}'];
815-
formatter = formatSet;
816787
} else if (isMap(value)) {
788+
const size = mapSizeGetter(value);
817789
keys = getKeys(value, ctx.showHidden);
818-
const prefix = getPrefix(constructor, tag, 'Map');
819-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
790+
let prefix = '';
791+
if (constructor !== null) {
792+
if (constructor === tag)
793+
tag = '';
794+
prefix = getPrefix(`${constructor}`, tag, '');
795+
formatter = formatMap.bind(null, value, size);
796+
} else {
797+
prefix = getPrefix(constructor, tag, 'Map');
798+
formatter = formatMap.bind(null, MapPrototypeEntries(value), size);
799+
}
800+
if (size === 0 && keys.length === 0 && protoProps === undefined)
820801
return `${prefix}{}`;
821802
braces = [`${prefix}{`, '}'];
822-
formatter = formatMap;
823803
} else if (isTypedArray(value)) {
824804
keys = getOwnNonIndexProperties(value, filter);
825-
const prefix = constructor !== null ?
826-
getPrefix(constructor, tag) :
827-
getPrefix(constructor, tag, findTypedConstructor(value).name);
805+
let bound = value;
806+
let prefix = '';
807+
if (constructor === null) {
808+
const constr = findTypedConstructor(value);
809+
prefix = getPrefix(constructor, tag, constr.name);
810+
// Reconstruct the array information.
811+
bound = new constr(value);
812+
} else {
813+
prefix = getPrefix(constructor, tag);
814+
}
828815
braces = [`${prefix}[`, ']'];
829816
if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
830817
return `${braces[0]}]`;
831-
formatter = formatTypedArray;
818+
// Special handle the value. The original value is required below. The
819+
// bound function is required to reconstruct missing information.
820+
formatter = formatTypedArray.bind(null, bound);
832821
extrasType = kArrayExtrasType;
833822
} else if (isMapIterator(value)) {
834823
keys = getKeys(value, ctx.showHidden);
835824
braces = getIteratorBraces('Map', tag);
836-
formatter = formatIterator;
825+
// Add braces to the formatter parameters.
826+
formatter = formatIterator.bind(null, braces);
837827
} else if (isSetIterator(value)) {
838828
keys = getKeys(value, ctx.showHidden);
839829
braces = getIteratorBraces('Set', tag);
840-
formatter = formatIterator;
830+
// Add braces to the formatter parameters.
831+
formatter = formatIterator.bind(null, braces);
841832
} else {
842833
noIterator = true;
843834
}
@@ -915,36 +906,20 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
915906
formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
916907
} else if (isModuleNamespaceObject(value)) {
917908
braces[0] = `[${tag}] {`;
918-
formatter = formatNamespaceObject;
909+
// Special handle keys for namespace objects.
910+
formatter = formatNamespaceObject.bind(null, keys);
919911
} else if (isBoxedPrimitive(value)) {
920912
base = getBoxedBase(value, ctx, keys, constructor, tag);
921913
if (keys.length === 0 && protoProps === undefined) {
922914
return base;
923915
}
924916
} else {
925-
// The input prototype got manipulated. Special handle these. We have to
926-
// rebuild the information so we are able to display everything.
927-
if (constructor === null) {
928-
const specialIterator = noPrototypeIterator(ctx, value, recurseTimes);
929-
if (specialIterator) {
930-
return specialIterator;
931-
}
932-
}
933-
if (isMapIterator(value)) {
934-
braces = getIteratorBraces('Map', tag);
935-
formatter = formatIterator;
936-
} else if (isSetIterator(value)) {
937-
braces = getIteratorBraces('Set', tag);
938-
formatter = formatIterator;
939-
// Handle other regular objects again.
940-
} else {
941-
if (keys.length === 0 && protoProps === undefined) {
942-
if (isExternal(value))
943-
return ctx.stylize('[External]', 'special');
944-
return `${getCtxStyle(value, constructor, tag)}{}`;
945-
}
946-
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
917+
if (keys.length === 0 && protoProps === undefined) {
918+
if (isExternal(value))
919+
return ctx.stylize('[External]', 'special');
920+
return `${getCtxStyle(value, constructor, tag)}{}`;
947921
}
922+
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
948923
}
949924
}
950925

@@ -961,7 +936,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
961936
let output;
962937
const indentationLvl = ctx.indentationLvl;
963938
try {
964-
output = formatter(ctx, value, recurseTimes, keys, braces);
939+
output = formatter(ctx, value, recurseTimes);
965940
for (i = 0; i < keys.length; i++) {
966941
output.push(
967942
formatProperty(ctx, value, recurseTimes, keys[i], extrasType));
@@ -1316,7 +1291,7 @@ function formatPrimitive(fn, value, ctx) {
13161291
return fn(SymbolPrototypeToString(value), 'symbol');
13171292
}
13181293

1319-
function formatNamespaceObject(ctx, value, recurseTimes, keys) {
1294+
function formatNamespaceObject(keys, ctx, value, recurseTimes) {
13201295
const output = new Array(keys.length);
13211296
for (let i = 0; i < keys.length; i++) {
13221297
try {
@@ -1418,7 +1393,7 @@ function formatArray(ctx, value, recurseTimes) {
14181393
return output;
14191394
}
14201395

1421-
function formatTypedArray(ctx, value, recurseTimes) {
1396+
function formatTypedArray(value, ctx, ignored, recurseTimes) {
14221397
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), value.length);
14231398
const remaining = value.length - maxLength;
14241399
const output = new Array(maxLength);
@@ -1449,7 +1424,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
14491424
return output;
14501425
}
14511426

1452-
function formatSet(ctx, value, recurseTimes) {
1427+
function formatSet(value, size, ctx, ignored, recurseTimes) {
14531428
const output = [];
14541429
ctx.indentationLvl += 2;
14551430
for (const v of value) {
@@ -1460,11 +1435,11 @@ function formatSet(ctx, value, recurseTimes) {
14601435
// arrays. For consistency's sake, do the same for `size`, even though this
14611436
// property isn't selected by ObjectGetOwnPropertyNames().
14621437
if (ctx.showHidden)
1463-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1438+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14641439
return output;
14651440
}
14661441

1467-
function formatMap(ctx, value, recurseTimes) {
1442+
function formatMap(value, size, ctx, ignored, recurseTimes) {
14681443
const output = [];
14691444
ctx.indentationLvl += 2;
14701445
for (const [k, v] of value) {
@@ -1474,7 +1449,7 @@ function formatMap(ctx, value, recurseTimes) {
14741449
ctx.indentationLvl -= 2;
14751450
// See comment in formatSet
14761451
if (ctx.showHidden)
1477-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1452+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14781453
return output;
14791454
}
14801455

@@ -1552,7 +1527,7 @@ function formatWeakMap(ctx, value, recurseTimes) {
15521527
return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
15531528
}
15541529

1555-
function formatIterator(ctx, value, recurseTimes, keys, braces) {
1530+
function formatIterator(braces, ctx, value, recurseTimes) {
15561531
const [entries, isKeyValue] = previewEntries(value, true);
15571532
if (isKeyValue) {
15581533
// Mark entry iterators as such.
@@ -1587,7 +1562,7 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc) {
15871562
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
15881563
{ value: value[key], enumerable: true };
15891564
if (desc.value !== undefined) {
1590-
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
1565+
const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
15911566
ctx.indentationLvl += diff;
15921567
str = formatValue(ctx, desc.value, recurseTimes);
15931568
if (diff === 3) {

test/parallel/test-util-inspect.js

+13
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,19 @@ assert.strictEqual(
22162216
configurable: true
22172217
});
22182218
assert.strictEqual(util.inspect(obj), '[Set: null prototype] { 1, 2 }');
2219+
Object.defineProperty(obj, Symbol.iterator, {
2220+
value: true,
2221+
configurable: true
2222+
});
2223+
Object.defineProperty(obj, 'size', {
2224+
value: NaN,
2225+
configurable: true,
2226+
enumerable: true
2227+
});
2228+
assert.strictEqual(
2229+
util.inspect(obj),
2230+
'[Set: null prototype] { 1, 2, size: NaN }'
2231+
);
22192232
}
22202233

22212234
// Check the getter option.

0 commit comments

Comments
 (0)