Skip to content

Commit f830a7d

Browse files
BridgeARMylesBorins
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. PR-URL: #30225 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent b6b917d commit f830a7d

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
@@ -10,6 +10,7 @@ const {
1010
DatePrototypeToString,
1111
ErrorPrototypeToString,
1212
JSONStringify,
13+
MapPrototype,
1314
MapPrototypeEntries,
1415
MathFloor,
1516
MathMax,
@@ -21,10 +22,8 @@ const {
2122
NumberPrototypeValueOf,
2223
ObjectAssign,
2324
ObjectCreate,
24-
ObjectDefineProperties,
2525
ObjectDefineProperty,
2626
ObjectGetOwnPropertyDescriptor,
27-
ObjectGetOwnPropertyDescriptors,
2827
ObjectGetOwnPropertyNames,
2928
ObjectGetOwnPropertySymbols,
3029
ObjectGetPrototypeOf,
@@ -34,6 +33,7 @@ const {
3433
ObjectPrototypePropertyIsEnumerable,
3534
ObjectSeal,
3635
RegExpPrototypeToString,
36+
SetPrototype,
3737
SetPrototypeValues,
3838
StringPrototypeValueOf,
3939
SymbolPrototypeToString,
@@ -113,6 +113,11 @@ const assert = require('internal/assert');
113113

114114
const { NativeModule } = require('internal/bootstrap/loaders');
115115

116+
const setSizeGetter = uncurryThis(
117+
ObjectGetOwnPropertyDescriptor(SetPrototype, 'size').get);
118+
const mapSizeGetter = uncurryThis(
119+
ObjectGetOwnPropertyDescriptor(MapPrototype, 'size').get);
120+
116121
let hexSlice;
117122

118123
const builtInObjects = new Set(
@@ -646,51 +651,6 @@ function findTypedConstructor(value) {
646651
}
647652
}
648653

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

795755
// Iterators and the rest are split to reduce checks.
796-
if (value[SymbolIterator]) {
756+
// We have to check all values in case the constructor is set to null.
757+
// Otherwise it would not possible to identify all types properly.
758+
if (value[SymbolIterator] || constructor === null) {
797759
noIterator = false;
798760
if (ArrayIsArray(value)) {
799761
keys = getOwnNonIndexProperties(value, filter);
@@ -805,37 +767,66 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
805767
extrasType = kArrayExtrasType;
806768
formatter = formatArray;
807769
} else if (isSet(value)) {
770+
const size = setSizeGetter(value);
808771
keys = getKeys(value, ctx.showHidden);
809-
const prefix = getPrefix(constructor, tag, 'Set');
810-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
772+
let prefix = '';
773+
if (constructor !== null) {
774+
if (constructor === tag)
775+
tag = '';
776+
prefix = getPrefix(`${constructor}`, tag, '');
777+
formatter = formatSet.bind(null, value, size);
778+
} else {
779+
prefix = getPrefix(constructor, tag, 'Set');
780+
formatter = formatSet.bind(null, SetPrototypeValues(value), size);
781+
}
782+
if (size === 0 && keys.length === 0 && protoProps === undefined)
811783
return `${prefix}{}`;
812784
braces = [`${prefix}{`, '}'];
813-
formatter = formatSet;
814785
} else if (isMap(value)) {
786+
const size = mapSizeGetter(value);
815787
keys = getKeys(value, ctx.showHidden);
816-
const prefix = getPrefix(constructor, tag, 'Map');
817-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
788+
let prefix = '';
789+
if (constructor !== null) {
790+
if (constructor === tag)
791+
tag = '';
792+
prefix = getPrefix(`${constructor}`, tag, '');
793+
formatter = formatMap.bind(null, value, size);
794+
} else {
795+
prefix = getPrefix(constructor, tag, 'Map');
796+
formatter = formatMap.bind(null, MapPrototypeEntries(value), size);
797+
}
798+
if (size === 0 && keys.length === 0 && protoProps === undefined)
818799
return `${prefix}{}`;
819800
braces = [`${prefix}{`, '}'];
820-
formatter = formatMap;
821801
} else if (isTypedArray(value)) {
822802
keys = getOwnNonIndexProperties(value, filter);
823-
const prefix = constructor !== null ?
824-
getPrefix(constructor, tag) :
825-
getPrefix(constructor, tag, findTypedConstructor(value).name);
803+
let bound = value;
804+
let prefix = '';
805+
if (constructor === null) {
806+
const constr = findTypedConstructor(value);
807+
prefix = getPrefix(constructor, tag, constr.name);
808+
// Reconstruct the array information.
809+
bound = new constr(value);
810+
} else {
811+
prefix = getPrefix(constructor, tag);
812+
}
826813
braces = [`${prefix}[`, ']'];
827814
if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
828815
return `${braces[0]}]`;
829-
formatter = formatTypedArray;
816+
// Special handle the value. The original value is required below. The
817+
// bound function is required to reconstruct missing information.
818+
formatter = formatTypedArray.bind(null, bound);
830819
extrasType = kArrayExtrasType;
831820
} else if (isMapIterator(value)) {
832821
keys = getKeys(value, ctx.showHidden);
833822
braces = getIteratorBraces('Map', tag);
834-
formatter = formatIterator;
823+
// Add braces to the formatter parameters.
824+
formatter = formatIterator.bind(null, braces);
835825
} else if (isSetIterator(value)) {
836826
keys = getKeys(value, ctx.showHidden);
837827
braces = getIteratorBraces('Set', tag);
838-
formatter = formatIterator;
828+
// Add braces to the formatter parameters.
829+
formatter = formatIterator.bind(null, braces);
839830
} else {
840831
noIterator = true;
841832
}
@@ -913,36 +904,20 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
913904
formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
914905
} else if (isModuleNamespaceObject(value)) {
915906
braces[0] = `[${tag}] {`;
916-
formatter = formatNamespaceObject;
907+
// Special handle keys for namespace objects.
908+
formatter = formatNamespaceObject.bind(null, keys);
917909
} else if (isBoxedPrimitive(value)) {
918910
base = getBoxedBase(value, ctx, keys, constructor, tag);
919911
if (keys.length === 0 && protoProps === undefined) {
920912
return base;
921913
}
922914
} else {
923-
// The input prototype got manipulated. Special handle these. We have to
924-
// rebuild the information so we are able to display everything.
925-
if (constructor === null) {
926-
const specialIterator = noPrototypeIterator(ctx, value, recurseTimes);
927-
if (specialIterator) {
928-
return specialIterator;
929-
}
930-
}
931-
if (isMapIterator(value)) {
932-
braces = getIteratorBraces('Map', tag);
933-
formatter = formatIterator;
934-
} else if (isSetIterator(value)) {
935-
braces = getIteratorBraces('Set', tag);
936-
formatter = formatIterator;
937-
// Handle other regular objects again.
938-
} else {
939-
if (keys.length === 0 && protoProps === undefined) {
940-
if (isExternal(value))
941-
return ctx.stylize('[External]', 'special');
942-
return `${getCtxStyle(value, constructor, tag)}{}`;
943-
}
944-
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
915+
if (keys.length === 0 && protoProps === undefined) {
916+
if (isExternal(value))
917+
return ctx.stylize('[External]', 'special');
918+
return `${getCtxStyle(value, constructor, tag)}{}`;
945919
}
920+
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
946921
}
947922
}
948923

@@ -959,7 +934,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
959934
let output;
960935
const indentationLvl = ctx.indentationLvl;
961936
try {
962-
output = formatter(ctx, value, recurseTimes, keys, braces);
937+
output = formatter(ctx, value, recurseTimes);
963938
for (i = 0; i < keys.length; i++) {
964939
output.push(
965940
formatProperty(ctx, value, recurseTimes, keys[i], extrasType));
@@ -1317,7 +1292,7 @@ function formatPrimitive(fn, value, ctx) {
13171292
return fn(SymbolPrototypeToString(value), 'symbol');
13181293
}
13191294

1320-
function formatNamespaceObject(ctx, value, recurseTimes, keys) {
1295+
function formatNamespaceObject(keys, ctx, value, recurseTimes) {
13211296
const output = new Array(keys.length);
13221297
for (let i = 0; i < keys.length; i++) {
13231298
try {
@@ -1419,7 +1394,7 @@ function formatArray(ctx, value, recurseTimes) {
14191394
return output;
14201395
}
14211396

1422-
function formatTypedArray(ctx, value, recurseTimes) {
1397+
function formatTypedArray(value, ctx, ignored, recurseTimes) {
14231398
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), value.length);
14241399
const remaining = value.length - maxLength;
14251400
const output = new Array(maxLength);
@@ -1450,7 +1425,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
14501425
return output;
14511426
}
14521427

1453-
function formatSet(ctx, value, recurseTimes) {
1428+
function formatSet(value, size, ctx, ignored, recurseTimes) {
14541429
const output = [];
14551430
ctx.indentationLvl += 2;
14561431
for (const v of value) {
@@ -1461,11 +1436,11 @@ function formatSet(ctx, value, recurseTimes) {
14611436
// arrays. For consistency's sake, do the same for `size`, even though this
14621437
// property isn't selected by ObjectGetOwnPropertyNames().
14631438
if (ctx.showHidden)
1464-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1439+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14651440
return output;
14661441
}
14671442

1468-
function formatMap(ctx, value, recurseTimes) {
1443+
function formatMap(value, size, ctx, ignored, recurseTimes) {
14691444
const output = [];
14701445
ctx.indentationLvl += 2;
14711446
for (const [k, v] of value) {
@@ -1475,7 +1450,7 @@ function formatMap(ctx, value, recurseTimes) {
14751450
ctx.indentationLvl -= 2;
14761451
// See comment in formatSet
14771452
if (ctx.showHidden)
1478-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1453+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14791454
return output;
14801455
}
14811456

@@ -1553,7 +1528,7 @@ function formatWeakMap(ctx, value, recurseTimes) {
15531528
return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
15541529
}
15551530

1556-
function formatIterator(ctx, value, recurseTimes, keys, braces) {
1531+
function formatIterator(braces, ctx, value, recurseTimes) {
15571532
const [entries, isKeyValue] = previewEntries(value, true);
15581533
if (isKeyValue) {
15591534
// Mark entry iterators as such.
@@ -1588,7 +1563,7 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc) {
15881563
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
15891564
{ value: value[key], enumerable: true };
15901565
if (desc.value !== undefined) {
1591-
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
1566+
const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
15921567
ctx.indentationLvl += diff;
15931568
str = formatValue(ctx, desc.value, recurseTimes);
15941569
if (diff === 3) {

test/parallel/test-util-inspect.js

+13
Original file line numberDiff line numberDiff line change
@@ -2219,6 +2219,19 @@ assert.strictEqual(
22192219
configurable: true
22202220
});
22212221
assert.strictEqual(util.inspect(obj), '[Set: null prototype] { 1, 2 }');
2222+
Object.defineProperty(obj, Symbol.iterator, {
2223+
value: true,
2224+
configurable: true
2225+
});
2226+
Object.defineProperty(obj, 'size', {
2227+
value: NaN,
2228+
configurable: true,
2229+
enumerable: true
2230+
});
2231+
assert.strictEqual(
2232+
util.inspect(obj),
2233+
'[Set: null prototype] { 1, 2, size: NaN }'
2234+
);
22222235
}
22232236

22242237
// Check the getter option.

0 commit comments

Comments
 (0)