Skip to content

Commit 215700c

Browse files
committed
util: Move handling null logic to inspect file
1 parent d14c3e6 commit 215700c

File tree

1 file changed

+64
-23
lines changed

1 file changed

+64
-23
lines changed

lib/internal/util/inspect.js

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ function getEmptyFormatArray() {
326326
}
327327

328328
function getConstructorName(obj) {
329+
let firstProto;
329330
while (obj) {
330331
const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
331332
if (descriptor !== undefined &&
@@ -335,25 +336,35 @@ function getConstructorName(obj) {
335336
}
336337

337338
obj = Object.getPrototypeOf(obj);
339+
if (firstProto === undefined) {
340+
firstProto = obj;
341+
}
342+
}
343+
344+
if (firstProto === null) {
345+
return null;
338346
}
347+
// TODO(BridgeAR): Improve prototype inspection.
348+
// We could use inspect on the prototype itself to improve the output.
339349

340350
return '';
341351
}
342352

343353
function getPrefix(constructor, tag, fallback) {
354+
if (constructor === null) {
355+
if (tag !== '') {
356+
return `[${fallback}: null prototype] [${tag}] `;
357+
}
358+
return `[${fallback}: null prototype] `;
359+
}
360+
344361
if (constructor !== '') {
345362
if (tag !== '' && constructor !== tag) {
346363
return `${constructor} [${tag}] `;
347364
}
348365
return `${constructor} `;
349366
}
350367

351-
if (tag !== '')
352-
return `[${tag}] `;
353-
354-
if (fallback !== undefined)
355-
return `${fallback} `;
356-
357368
return '';
358369
}
359370

@@ -427,21 +438,49 @@ function findTypedConstructor(value) {
427438
}
428439
}
429440

441+
let lazyNullPrototypeCache;
442+
// Creates a subclass and name
443+
// the constructor as `${clazz} : null prototype`
444+
function clazzWithNullPrototype(clazz, name) {
445+
if (lazyNullPrototypeCache === undefined) {
446+
lazyNullPrototypeCache = new Map();
447+
} else {
448+
const cachedClass = lazyNullPrototypeCache.get(clazz);
449+
if (cachedClass !== undefined) {
450+
return cachedClass;
451+
}
452+
}
453+
class NullPrototype extends clazz {
454+
get [Symbol.toStringTag]() {
455+
return '';
456+
}
457+
}
458+
Object.defineProperty(NullPrototype.prototype.constructor, 'name',
459+
{ value: `[${name}: null prototype]` });
460+
lazyNullPrototypeCache.set(clazz, NullPrototype);
461+
return NullPrototype;
462+
}
463+
430464
function noPrototypeIterator(ctx, value, recurseTimes) {
431465
let newVal;
432-
// TODO: Create a Subclass in case there's no prototype and show
433-
// `null-prototype`.
434466
if (isSet(value)) {
435-
const clazz = Object.getPrototypeOf(value) || Set;
467+
const clazz = Object.getPrototypeOf(value) ||
468+
clazzWithNullPrototype(Set, 'Set');
436469
newVal = new clazz(setValues(value));
437470
} else if (isMap(value)) {
438-
const clazz = Object.getPrototypeOf(value) || Map;
471+
const clazz = Object.getPrototypeOf(value) ||
472+
clazzWithNullPrototype(Map, 'Map');
439473
newVal = new clazz(mapEntries(value));
440474
} else if (Array.isArray(value)) {
441-
const clazz = Object.getPrototypeOf(value) || Array;
475+
const clazz = Object.getPrototypeOf(value) ||
476+
clazzWithNullPrototype(Array, 'Array');
442477
newVal = new clazz(value.length || 0);
443478
} else if (isTypedArray(value)) {
444-
const clazz = findTypedConstructor(value) || Uint8Array;
479+
let clazz = Object.getPrototypeOf(value);
480+
if (!clazz) {
481+
const constructor = findTypedConstructor(value);
482+
clazz = clazzWithNullPrototype(constructor, constructor.name);
483+
}
445484
newVal = new clazz(value);
446485
}
447486
if (newVal) {
@@ -527,29 +566,32 @@ function formatRaw(ctx, value, recurseTimes) {
527566
if (Array.isArray(value)) {
528567
keys = getOwnNonIndexProperties(value, filter);
529568
// Only set the constructor for non ordinary ("Array [...]") arrays.
530-
const prefix = getPrefix(constructor, tag);
569+
const prefix = getPrefix(constructor, tag, 'Array');
531570
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
532571
if (value.length === 0 && keys.length === 0)
533572
return `${braces[0]}]`;
534573
extrasType = kArrayExtrasType;
535574
formatter = formatArray;
536575
} else if (isSet(value)) {
537576
keys = getKeys(value, ctx.showHidden);
538-
const prefix = getPrefix(constructor, tag);
577+
const prefix = getPrefix(constructor, tag, 'Set');
539578
if (value.size === 0 && keys.length === 0)
540579
return `${prefix}{}`;
541580
braces = [`${prefix}{`, '}'];
542581
formatter = formatSet;
543582
} else if (isMap(value)) {
544583
keys = getKeys(value, ctx.showHidden);
545-
const prefix = getPrefix(constructor, tag);
584+
const prefix = getPrefix(constructor, tag, 'Map');
546585
if (value.size === 0 && keys.length === 0)
547586
return `${prefix}{}`;
548587
braces = [`${prefix}{`, '}'];
549588
formatter = formatMap;
550589
} else if (isTypedArray(value)) {
551590
keys = getOwnNonIndexProperties(value, filter);
552-
braces = [`${getPrefix(constructor, tag)}[`, ']'];
591+
const prefix = constructor !== null ?
592+
getPrefix(constructor, tag) :
593+
getPrefix(constructor, tag, findTypedConstructor(value).name);
594+
braces = [`${prefix}[`, ']'];
553595
if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
554596
return `${braces[0]}]`;
555597
formatter = formatTypedArray;
@@ -575,7 +617,7 @@ function formatRaw(ctx, value, recurseTimes) {
575617
return '[Arguments] {}';
576618
braces[0] = '[Arguments] {';
577619
} else if (tag !== '') {
578-
braces[0] = `${getPrefix(constructor, tag)}{`;
620+
braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
579621
if (keys.length === 0) {
580622
return `${braces[0]}}`;
581623
}
@@ -622,13 +664,12 @@ function formatRaw(ctx, value, recurseTimes) {
622664
base = `[${base.slice(0, stackStart)}]`;
623665
}
624666
} else if (isAnyArrayBuffer(value)) {
625-
let prefix = getPrefix(constructor, tag);
626-
if (prefix === '') {
627-
prefix = isArrayBuffer(value) ? 'ArrayBuffer ' : 'SharedArrayBuffer ';
628-
}
629667
// Fast path for ArrayBuffer and SharedArrayBuffer.
630668
// Can't do the same for DataView because it has a non-primitive
631669
// .buffer property that we need to recurse for.
670+
const arrayType = isArrayBuffer(value) ? 'ArrayBuffer' :
671+
'SharedArrayBuffer';
672+
const prefix = getPrefix(constructor, tag, arrayType);
632673
if (keys.length === 0)
633674
return prefix +
634675
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
@@ -693,9 +734,9 @@ function formatRaw(ctx, value, recurseTimes) {
693734
} else if (keys.length === 0) {
694735
if (isExternal(value))
695736
return ctx.stylize('[External]', 'special');
696-
return `${getPrefix(constructor, tag)}{}`;
737+
return `${getPrefix(constructor, tag, 'Object')}{}`;
697738
} else {
698-
braces[0] = `${getPrefix(constructor, tag)}{`;
739+
braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
699740
}
700741
}
701742
}

0 commit comments

Comments
 (0)