From a245f3fb9d582b6b69f385306d4a65c1551139df Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 9 Dec 2024 12:03:45 +0100 Subject: [PATCH] util: do not rely on mutable `Object` and `Function`' `constructor` prop --- lib/internal/util/inspect.js | 30 +++++++++++++++++++++++++----- test/parallel/test-util-inspect.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index c1eac52336f18e..88bb084fb7fa41 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -22,8 +22,11 @@ const { DatePrototypeToISOString, DatePrototypeToString, ErrorPrototypeToString, + Function, + FunctionPrototype, FunctionPrototypeBind, FunctionPrototypeCall, + FunctionPrototypeSymbolHasInstance, FunctionPrototypeToString, JSONStringify, MapPrototypeEntries, @@ -50,6 +53,7 @@ const { ObjectGetPrototypeOf, ObjectIs, ObjectKeys, + ObjectPrototype, ObjectPrototypeHasOwnProperty, ObjectPrototypePropertyIsEnumerable, ObjectSeal, @@ -593,10 +597,26 @@ function isInstanceof(object, proto) { } } +// Special-case for some builtin prototypes in case their `constructor` property has been tampered. +const wellKnownPrototypes = new SafeMap(); +wellKnownPrototypes.set(ObjectPrototype, { name: 'Object', constructor: Object }); +wellKnownPrototypes.set(FunctionPrototype, { name: 'Function', constructor: Function }); + function getConstructorName(obj, ctx, recurseTimes, protoProps) { let firstProto; const tmp = obj; while (obj || isUndetectableObject(obj)) { + const wellKnownPrototypeNameAndConstructor = wellKnownPrototypes.get(obj); + if (wellKnownPrototypeNameAndConstructor != null) { + const { name, constructor } = wellKnownPrototypeNameAndConstructor; + if (FunctionPrototypeSymbolHasInstance(constructor, tmp)) { + if (protoProps !== undefined && firstProto !== obj) { + addPrototypeProperties( + ctx, tmp, firstProto || tmp, recurseTimes, protoProps); + } + return name; + } + } const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor'); if (descriptor !== undefined && typeof descriptor.value === 'function' && @@ -954,7 +974,11 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (noIterator) { keys = getKeys(value, ctx.showHidden); braces = ['{', '}']; - if (constructor === 'Object') { + if (typeof value === 'function') { + base = getFunctionBase(value, constructor, tag); + if (keys.length === 0 && protoProps === undefined) + return ctx.stylize(base, 'special'); + } else if (constructor === 'Object') { if (isArgumentsObject(value)) { braces[0] = '[Arguments] {'; } else if (tag !== '') { @@ -963,10 +987,6 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (keys.length === 0 && protoProps === undefined) { return `${braces[0]}}`; } - } else if (typeof value === 'function') { - base = getFunctionBase(value, constructor, tag); - if (keys.length === 0 && protoProps === undefined) - return ctx.stylize(base, 'special'); } else if (isRegExp(value)) { // Make RegExps say that they are RegExps base = RegExpPrototypeToString( diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 79f0c3bef1377e..8c8b99ffc3839c 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -3355,3 +3355,33 @@ assert.strictEqual( '}', ); } + +{ + const o = {}; + const { prototype: BuiltinPrototype } = Object; + const desc = Reflect.getOwnPropertyDescriptor(BuiltinPrototype, 'constructor'); + Object.defineProperty(BuiltinPrototype, 'constructor', { + get: () => BuiltinPrototype, + configurable: true, + }); + assert.strictEqual( + util.inspect(o), + '{}', + ); + Object.defineProperty(BuiltinPrototype, 'constructor', desc); +} + +{ + const o = { f() {} }; + const { prototype: BuiltinPrototype } = Function; + const desc = Reflect.getOwnPropertyDescriptor(BuiltinPrototype, 'constructor'); + Object.defineProperty(BuiltinPrototype, 'constructor', { + get: () => BuiltinPrototype, + configurable: true, + }); + assert.strictEqual( + util.inspect(o), + '{ f: [Function: f] }', + ); + Object.defineProperty(BuiltinPrototype, 'constructor', desc); +}