util: fix inspection of class instance prototypes

To achieve this, some internal custom inspect functions had to be
changed. They relied upon the former behavior.

Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>

PR-URL: https://github.com/nodejs/node/pull/33449
Fixes: https://github.com/nodejs/node/issues/33419
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
Ruben Bridgewater 2020-05-17 19:07:04 +02:00
parent 2f00ca42bf
commit e24731cb70
8 changed files with 110 additions and 69 deletions

View File

@ -514,54 +514,53 @@ function makeTextDecoderJS() {
} }
// Mix in some shared properties. // Mix in some shared properties.
{ ObjectDefineProperties(
ObjectDefineProperties( TextDecoder.prototype,
TextDecoder.prototype, ObjectGetOwnPropertyDescriptors({
ObjectGetOwnPropertyDescriptors({ get encoding() {
get encoding() { validateDecoder(this);
validateDecoder(this); return this[kEncoding];
return this[kEncoding]; },
},
get fatal() { get fatal() {
validateDecoder(this); validateDecoder(this);
return (this[kFlags] & CONVERTER_FLAGS_FATAL) === CONVERTER_FLAGS_FATAL; return (this[kFlags] & CONVERTER_FLAGS_FATAL) === CONVERTER_FLAGS_FATAL;
}, },
get ignoreBOM() { get ignoreBOM() {
validateDecoder(this); validateDecoder(this);
return (this[kFlags] & CONVERTER_FLAGS_IGNORE_BOM) === return (this[kFlags] & CONVERTER_FLAGS_IGNORE_BOM) ===
CONVERTER_FLAGS_IGNORE_BOM; CONVERTER_FLAGS_IGNORE_BOM;
}, },
[inspect](depth, opts) { [inspect](depth, opts) {
validateDecoder(this); validateDecoder(this);
if (typeof depth === 'number' && depth < 0) if (typeof depth === 'number' && depth < 0)
return this; return this;
const ctor = getConstructorOf(this); const constructor = getConstructorOf(this) || TextDecoder;
const obj = ObjectCreate({ const obj = ObjectCreate({ constructor });
constructor: ctor === null ? TextDecoder : ctor obj.encoding = this.encoding;
}); obj.fatal = this.fatal;
obj.encoding = this.encoding; obj.ignoreBOM = this.ignoreBOM;
obj.fatal = this.fatal; if (opts.showHidden) {
obj.ignoreBOM = this.ignoreBOM; obj[kFlags] = this[kFlags];
if (opts.showHidden) { obj[kHandle] = this[kHandle];
obj[kFlags] = this[kFlags];
obj[kHandle] = this[kHandle];
}
// Lazy to avoid circular dependency
return require('internal/util/inspect').inspect(obj, opts);
} }
})); // Lazy to avoid circular dependency
ObjectDefineProperties(TextDecoder.prototype, { const { inspect } = require('internal/util/inspect');
decode: { enumerable: true }, return `${constructor.name} ${inspect(obj)}`;
[inspect]: { enumerable: false },
[SymbolToStringTag]: {
configurable: true,
value: 'TextDecoder'
} }
}); })
} );
ObjectDefineProperties(TextDecoder.prototype, {
decode: { enumerable: true },
[inspect]: { enumerable: false },
[SymbolToStringTag]: {
configurable: true,
value: 'TextDecoder'
}
});
module.exports = { module.exports = {
getEncodingFromLabel, getEncodingFromLabel,

View File

@ -358,11 +358,8 @@ class URL {
if (typeof depth === 'number' && depth < 0) if (typeof depth === 'number' && depth < 0)
return this; return this;
const ctor = getConstructorOf(this); const constructor = getConstructorOf(this) || URL;
const obj = ObjectCreate({ constructor });
const obj = ObjectCreate({
constructor: ctor === null ? URL : ctor
});
obj.href = this.href; obj.href = this.href;
obj.origin = this.origin; obj.origin = this.origin;
@ -383,7 +380,7 @@ class URL {
obj[context] = this[context]; obj[context] = this[context];
} }
return inspect(obj, opts); return `${constructor.name} ${inspect(obj, opts)}`;
} }
} }

View File

@ -498,7 +498,8 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) {
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor'); const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined && if (descriptor !== undefined &&
typeof descriptor.value === 'function' && typeof descriptor.value === 'function' &&
descriptor.value.name !== '') { descriptor.value.name !== '' &&
tmp instanceof descriptor.value) {
if (protoProps !== undefined && if (protoProps !== undefined &&
(firstProto !== obj || (firstProto !== obj ||
!builtInObjects.has(descriptor.value.name))) { !builtInObjects.has(descriptor.value.name))) {

View File

@ -5,6 +5,8 @@ const {
ArrayIsArray, ArrayIsArray,
ObjectCreate, ObjectCreate,
ObjectDefineProperty, ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
SafePromise, SafePromise,
Symbol, Symbol,
WeakMap, WeakMap,
@ -223,17 +225,27 @@ class Module {
} }
[customInspectSymbol](depth, options) { [customInspectSymbol](depth, options) {
let ctor = getConstructorOf(this); if (this[kWrap] === undefined) {
ctor = ctor === null ? Module : ctor; throw new ERR_VM_MODULE_NOT_MODULE();
}
if (typeof depth === 'number' && depth < 0) if (typeof depth === 'number' && depth < 0)
return options.stylize(`[${ctor.name}]`, 'special'); return this;
const o = ObjectCreate({ constructor: ctor }); const constructor = getConstructorOf(this) || Module;
const o = ObjectCreate({ constructor });
o.status = this.status; o.status = this.status;
o.identifier = this.identifier; o.identifier = this.identifier;
o.context = this.context; o.context = this.context;
return require('internal/util/inspect').inspect(o, options);
ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this));
ObjectDefineProperty(o, Symbol.toStringTag, {
value: constructor.name,
configurable: true
});
// Lazy to avoid circular dependency
const { inspect } = require('internal/util/inspect');
return inspect(o, { ...options, customInspect: false });
} }
} }

View File

@ -266,7 +266,7 @@ assert.strictEqual(
' func: <ref *1> [Function: func] {\n' + ' func: <ref *1> [Function: func] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'func\',\n' + ' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
'}'); '}');
assert.strictEqual( assert.strictEqual(
@ -279,7 +279,7 @@ assert.strictEqual(
' a: <ref *1> [Function: a] {\n' + ' a: <ref *1> [Function: a] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'a\',\n' + ' [name]: \'a\',\n' +
' [prototype]: a { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
' },\n' + ' },\n' +
' [length]: 1\n' + ' [length]: 1\n' +
@ -294,7 +294,7 @@ assert.strictEqual(
' func: <ref *1> [Function: func] {\n' + ' func: <ref *1> [Function: func] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'func\',\n' + ' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
' }\n' + ' }\n' +
'}'); '}');
@ -306,7 +306,7 @@ assert.strictEqual(
' func: <ref *1> [Function: func] {\n' + ' func: <ref *1> [Function: func] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'func\',\n' + ' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
'} {\n' + '} {\n' +
' foo: \'bar\',\n' + ' foo: \'bar\',\n' +
@ -314,7 +314,7 @@ assert.strictEqual(
' func: <ref *1> [Function: func] {\n' + ' func: <ref *1> [Function: func] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'func\',\n' + ' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
'}'); '}');
assert.strictEqual( assert.strictEqual(
@ -325,7 +325,7 @@ assert.strictEqual(
' func: <ref *1> [Function: func] {\n' + ' func: <ref *1> [Function: func] {\n' +
' [length]: 0,\n' + ' [length]: 0,\n' +
' [name]: \'func\',\n' + ' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular *1] }\n' + ' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' + ' }\n' +
'} %o'); '} %o');

View File

@ -2827,6 +2827,37 @@ assert.strictEqual(
'{ \x1B[2mabc: \x1B[33mtrue\x1B[39m\x1B[22m, ' + '{ \x1B[2mabc: \x1B[33mtrue\x1B[39m\x1B[22m, ' +
'\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }' '\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }'
); );
assert.strictEqual(
inspect(Object.getPrototypeOf(bar), { showHidden: true, getters: true }),
'<ref *1> Foo [Map] {\n' +
' [constructor]: [class Bar extends Foo] {\n' +
' [length]: 0,\n' +
' [prototype]: [Circular *1],\n' +
" [name]: 'Bar',\n" +
' [Symbol(Symbol.species)]: [Getter: <Inspection threw ' +
"(Symbol.prototype.toString requires that 'this' be a Symbol)>]\n" +
' },\n' +
" [xyz]: [Getter: 'YES!'],\n" +
' [Symbol(nodejs.util.inspect.custom)]: ' +
'[Function: [nodejs.util.inspect.custom]] {\n' +
' [length]: 0,\n' +
" [name]: '[nodejs.util.inspect.custom]'\n" +
' },\n' +
' [abc]: [Getter: true],\n' +
' [def]: [Getter/Setter: false]\n' +
' }'
);
assert.strictEqual(
inspect(Object.getPrototypeOf(bar)),
'Foo [Map] {}'
);
assert.strictEqual(
inspect(Object.getPrototypeOf(new Foo())),
'Map {}'
);
} }
// Test changing util.inspect.colors colors and aliases. // Test changing util.inspect.colors colors and aliases.

View File

@ -83,9 +83,12 @@ const util = require('util');
assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]'); assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]');
assert.strictEqual( assert.throws(
m[util.inspect.custom].call(Object.create(null)), () => m[util.inspect.custom].call(Object.create(null)),
'Module { status: undefined, identifier: undefined, context: undefined }', {
code: 'ERR_VM_MODULE_NOT_MODULE',
message: 'Provided module is not an instance of Module'
},
); );
} }

View File

@ -85,6 +85,7 @@ if (common.hasIntl) {
assert.strictEqual(dec.encoding, 'utf-8'); assert.strictEqual(dec.encoding, 'utf-8');
assert.strictEqual(dec.fatal, false); assert.strictEqual(dec.fatal, false);
assert.strictEqual(dec.ignoreBOM, false); assert.strictEqual(dec.ignoreBOM, false);
assert.strictEqual(dec[Symbol.toStringTag], 'TextDecoder');
} }
// Test TextDecoder, UTF-16le // Test TextDecoder, UTF-16le
@ -125,10 +126,7 @@ if (common.hasIntl) {
' [Symbol(flags)]: 4,\n' + ' [Symbol(flags)]: 4,\n' +
' [Symbol(handle)]: StringDecoder {\n' + ' [Symbol(handle)]: StringDecoder {\n' +
" encoding: 'utf8',\n" + " encoding: 'utf8',\n" +
' [Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>,\n' + ' [Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>\n' +
' lastChar: [Getter],\n' +
' lastNeed: [Getter],\n' +
' lastTotal: [Getter]\n' +
' }\n' + ' }\n' +
'}' '}'
); );