Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/codegen/builtin-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const jscNames = {
"getByIdDirect",
"getByIdDirectPrivate",
"getInternalField",
"getPrototypeOf",
"getProxyInternalField",
"isCallable",
"isJSArray",
Expand Down
1 change: 1 addition & 0 deletions src/codegen/builtins/host/bun-syntax.builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default b.declarations(

b.intrinsic("Array", "Value", builtin("Array")),
b.intrinsic("Buffer", "Value", builtin("Buffer")),
b.intrinsic("getPrototypeOf", "Call", builtin("Object.getPrototypeOf")),
b.intrinsic("isArray", "Call", builtin("Array.isArray")),
b.intrinsic("isArray", "Value", builtin(b.linkTimeConstantPath("isArray"))),
b.intrinsic("isFinite", "Call", builtin("isFinite")),
Expand Down
1 change: 1 addition & 0 deletions src/codegen/builtins/host/features-node.builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export default b.declarations(
"parseEnv",
"uvTranslateSysError",
),
...nativeBindings("Cpp", "UtilInspect.cpp", "createWellKnownPrototypes", "errorPrototype"),
...nativeBindings("NewCppFunction", "JSEventTarget.cpp", "jsEventTargetGetEventListenersCount"),
...nativeBindings("NewCppFunction", "JSEventTargetNode.cpp", "jsFunctionNodeEventsGetEventListeners"),
...nativeBindings("NewCppFunction", "NodeUtilTypesModule.cpp", "jsFunctionIsError"),
Expand Down
31 changes: 31 additions & 0 deletions src/codegen/builtins/host/native-bindings.builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ const vmBinding = "%NodeVMBinding%";
const undeclaredFunction = b.otherwiseBy(b.generic);
// The three functions lie in an array. No object stands for the holder, and each has the name that native code gives the function.
const urlFunctions = "%NodeURLFunctions%";
// The classes of `wellKnownPrototypes` in Node's lib/internal/util/inspect.js, as createWellKnownPrototypes of UtilInspect.cpp lists them.
const [firstWellKnownPrototype, ...otherWellKnownPrototypes] = [
"Array",
"ArrayBuffer",
"Function",
"Map",
"Set",
"Object",
"%TypedArray%",
"RegExp",
"Date",
"DataView",
"Error",
"AggregateError",
"RangeError",
"TypeError",
"Boolean",
"Number",
"String",
"Promise",
"WeakMap",
"WeakSet",
].map(name => b.builtin(`${name}.prototype`));

const convertsDomain = {
...operandCodeOnly,
Expand Down Expand Up @@ -137,4 +160,12 @@ export default b.declarations(

// Whether the process was started with --experimental-stream-iter.
answers("native:Cpp:NodeModuleModule.cpp:createStreamIterEnabledFlag", b.bool),

// The name under which util.inspect prints the values below each of these prototypes of the realm.
answers(
"native:Cpp:UtilInspect.cpp:createWellKnownPrototypes",
b.instance("Map").holding({ key: b.union(firstWellKnownPrototype, ...otherWellKnownPrototypes), value: b.string })
.fresh,
),
answers("native:Cpp:UtilInspect.cpp:errorPrototype", b.builtin("Error.prototype")),
);
3 changes: 3 additions & 0 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,9 @@ interface ObjectConstructor {
/** gets a property on an object */
declare function $getByIdDirect<T, K extends keyof T>(obj: T, key: K): T[K];

/** `Object.getPrototypeOf(value)`, as the `op_get_prototype_of` bytecode */
declare function $getPrototypeOf(value: unknown): object | null;

/**
* Gets a private property on an object.
* Translates to the `op_get_by_id_direct` bytecode.
Expand Down
57 changes: 30 additions & 27 deletions src/js/internal/util/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,35 +1079,38 @@ function isInstanceof(object: unknown, proto) {
}

// Special-case for some builtin prototypes in case their `constructor` property has been tampered.
let wellKnownPrototypes:
Map<unknown, { name: string; constructor: (new (...args: never[]) => unknown) | undefined }> | undefined;
let wellKnownFunction: FunctionConstructor;
function initializeWellKnownPrototypes() {
wellKnownPrototypes = new SafeMap();
wellKnownPrototypes.set(Array.prototype, { name: "Array", constructor: Array });
wellKnownPrototypes.set(ArrayBuffer.prototype, { name: "ArrayBuffer", constructor: ArrayBuffer });
wellKnownPrototypes.set(Function.prototype, { name: "Function", constructor: undefined });
wellKnownFunction = Function;
wellKnownPrototypes.set(Map.prototype, { name: "Map", constructor: Map });
wellKnownPrototypes.set(Object.prototype, { name: "Object", constructor: Object });
wellKnownPrototypes.set(Set.prototype, { name: "Set", constructor: Set });
// wellKnownPrototypes.set(TypedArray.prototype, { name: "TypedArray", constructor: TypedArray });
let wellKnownPrototypes: Map<unknown, string> | undefined;

// `x instanceof IntrinsicError` is Node's FunctionPrototypeSymbolHasInstance(Error, x): no program can reach these two.
function IntrinsicObject() {}
function IntrinsicError() {}
// Defined, not assigned: the program can have given Function.prototype a setter of `prototype`.
$putByValDirect(IntrinsicObject, "prototype", $getPrototypeOf({}));
$putByValDirect(IntrinsicError, "prototype", $cpp("UtilInspect.cpp", "errorPrototype"));

// FunctionPrototypeSymbolHasInstance of https://github.com/nodejs/node/blob/v26.12.1/lib/internal/util/inspect.js#L913
function hasInPrototypeChain(proto: object, value: object) {
for (let p = $getPrototypeOf(value); p !== null; p = $getPrototypeOf(p)) {
if (p === proto) return true;
}
return false;
}

function getConstructorName(obj, ctx: Context, recurseTimes: number, protoProps?: string[]): string | null {
let firstProto;
const tmp = obj;
if (!wellKnownPrototypes) initializeWellKnownPrototypes();
wellKnownPrototypes ??= $cpp("UtilInspect.cpp", "createWellKnownPrototypes") as Map<unknown, string>;
while (obj || isUndetectableObject(obj)) {
const wellKnownPrototypeNameAndConstructor = wellKnownPrototypes!.get(obj);
if (wellKnownPrototypeNameAndConstructor != null) {
const { name, constructor } = wellKnownPrototypeNameAndConstructor;
if (constructor === undefined ? tmp instanceof wellKnownFunction : tmp instanceof constructor) {
if (protoProps !== undefined && firstProto !== obj) {
addPrototypeProperties(ctx, tmp, firstProto || tmp, recurseTimes, protoProps);
}
return name;
const wellKnownName = wellKnownPrototypes.$get(obj);
// The walk would stop at the prototype that this loop just read from `tmp`. A Proxy is asked again, as in Node.js.
if (
wellKnownName !== undefined &&
((firstProto === obj && !$isProxyObject(tmp)) || hasInPrototypeChain(obj, tmp))
) {
if (protoProps !== undefined && firstProto !== obj) {
addPrototypeProperties(ctx, tmp, firstProto || tmp, recurseTimes, protoProps);
}
return wellKnownName;
}
const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor");
const descriptorValue = descriptor !== undefined ? descriptor.value : undefined;
Expand All @@ -1121,7 +1124,7 @@ function getConstructorName(obj, ctx: Context, recurseTimes: number, protoProps?
}
}

obj = ObjectGetPrototypeOf(obj);
obj = $getPrototypeOf(obj);
if (firstProto === undefined) {
firstProto = obj;
}
Expand Down Expand Up @@ -1311,7 +1314,7 @@ function formatValue(ctx: Context, value, recurseTimes: number, typedArray?: boo
// This makes sure the recurseTimes are reported as before while using
// a counter internally.
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
const isCrossContext = proxy !== undefined || !(context instanceof Object);
const isCrossContext = proxy !== undefined || !(context instanceof IntrinsicObject);
const ret = (maybeCustom as CustomInspect).$call(context, depth, getUserOptions(ctx, isCrossContext), inspect);
// If the custom inspection method returned `this`, don't go into infinite recursion.
if (ret !== context) {
Expand Down Expand Up @@ -1492,7 +1495,7 @@ function formatRaw(ctx: Context, value, recurseTimes: number, typedArray?: boole
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, "date");
}
} else if (isNativeError(value) || value instanceof Error) {
} else if (isNativeError(value) || value instanceof IntrinsicError) {
base = formatError(value, constructor, tag, ctx, keys);
if (keys.length === 0 && protoProps === undefined) return base;
} else if (isAnyArrayBuffer(value)) {
Expand Down Expand Up @@ -1826,8 +1829,8 @@ function getStackFrames(ctx: Context, err: Error, stack: string) {
}

// Remove stack frames identical to frames in cause.
if (cause != null && (isNativeError(cause) || cause instanceof Error)) {
const causeStack = getStackString(cause);
if (cause != null && (isNativeError(cause) || cause instanceof IntrinsicError)) {
const causeStack = getStackString(cause as Error);
const causeStackStart = StringPrototypeIndexOf.$call(causeStack, "\n at");
if (causeStackStart !== -1) {
const causeFrames = StringPrototypeSplit.$call(StringPrototypeSlice.$call(causeStack, causeStackStart + 1), "\n");
Expand Down
55 changes: 55 additions & 0 deletions src/jsc/bindings/UtilInspect.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
#include "root.h"
#include "headers.h"
#include "UtilInspect.h"
#include "JavaScriptCore/JSObject.h"
#include "JavaScriptCore/JSFunction.h"
#include "JavaScriptCore/JSString.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/JSGlobalObject.h"
#include "JavaScriptCore/JSGlobalObjectInlines.h"
#include "ZigGlobalObject.h"
#include "JSBuffer.h"
#include "JavaScriptCore/ObjectConstructor.h"
#include "JavaScriptCore/PropertyNameArray.h"
#include "JavaScriptCore/JSArray.h"
#include "JavaScriptCore/JSMap.h"
#include "JavaScriptCore/ArrayPrototype.h"
#include "JavaScriptCore/FunctionPrototype.h"
#include "JavaScriptCore/ObjectPrototype.h"
#include "JavaScriptCore/RegExpPrototype.h"
#include "JavaScriptCore/StringPrototype.h"
#include "JavaScriptCore/JSPromisePrototype.h"
#include "JavaScriptCore/JSTypedArrayViewPrototype.h"
#include "JavaScriptCore/IdentifierInlines.h"

namespace Bun {
Expand Down Expand Up @@ -40,6 +50,51 @@ JSObject* createInspectOptionsObject(VM& vm, Zig::GlobalObject* globalObject, un
return options;
}

// wellKnownPrototypes of https://github.com/nodejs/node/blob/v26.12.1/lib/internal/util/inspect.js#L882-L904
JSC::JSValue createWellKnownPrototypes(Zig::GlobalObject* globalObject)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);

// From the global object: the program can have replaced `globalThis.Date` or the prototype of `Uint8Array`.
const std::pair<JSObject*, ASCIILiteral> entries[] = {
{ globalObject->arrayPrototype(), "Array"_s },
{ globalObject->arrayBufferPrototype(ArrayBufferSharingMode::Default), "ArrayBuffer"_s },
{ globalObject->functionPrototype(), "Function"_s },
{ globalObject->mapPrototype(), "Map"_s },
{ globalObject->jsSetPrototype(), "Set"_s },
{ globalObject->objectPrototype(), "Object"_s },
{ globalObject->m_typedArrayProto.get(globalObject), "TypedArray"_s },
{ globalObject->regExpPrototype(), "RegExp"_s },
{ globalObject->datePrototype(), "Date"_s },
{ globalObject->typedArrayPrototype(TypeDataView), "DataView"_s },
{ globalObject->errorPrototype(), "Error"_s },
{ globalObject->errorStructure(ErrorType::AggregateError)->storedPrototypeObject(), "AggregateError"_s },
{ globalObject->errorStructure(ErrorType::RangeError)->storedPrototypeObject(), "RangeError"_s },
{ globalObject->errorStructure(ErrorType::TypeError)->storedPrototypeObject(), "TypeError"_s },
{ globalObject->booleanPrototype(), "Boolean"_s },
{ globalObject->numberPrototype(), "Number"_s },
{ globalObject->stringPrototype(), "String"_s },
{ globalObject->promisePrototype(), "Promise"_s },
{ globalObject->weakMapStructure()->storedPrototypeObject(), "WeakMap"_s },
{ globalObject->weakSetStructure()->storedPrototypeObject(), "WeakSet"_s },
};

JSMap* map = JSMap::create(vm, globalObject->mapStructure());
for (const auto& [prototype, name] : entries) {
// An atom, as a string literal of JavaScript is: `constructor === "Object"` then compares pointers.
map->set(globalObject, prototype, jsString(vm, makeAtomString(name)));
RETURN_IF_EXCEPTION(scope, {});
}
return map;
}

// The program can have replaced `globalThis.Error`, and built-in JavaScript has no private name for it.
JSC::JSValue errorPrototype(Zig::GlobalObject* globalObject)
{
return globalObject->errorPrototype();
}

extern "C" JSC::EncodedJSValue JSC__JSValue__callCustomInspectFunction(
Zig::GlobalObject* globalObject,
JSC::EncodedJSValue encodedFunctionValue,
Expand Down
9 changes: 9 additions & 0 deletions src/jsc/bindings/UtilInspect.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

#include "root.h"

namespace Zig {
class GlobalObject;
}

namespace Bun {

JSC::Structure* createUtilInspectOptionsStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);

// $cpp("UtilInspect.cpp", "createWellKnownPrototypes"): a Map from each well-known prototype of util.inspect to its name.
JSC::JSValue createWellKnownPrototypes(Zig::GlobalObject*);
// $cpp("UtilInspect.cpp", "errorPrototype"): the Error.prototype of the realm.
JSC::JSValue errorPrototype(Zig::GlobalObject*);

// internalBinding('util').getOwnNonIndexProperties(object, filter)
JSC_DECLARE_HOST_FUNCTION(jsFunctionGetOwnNonIndexProperties);

Expand Down
1 change: 1 addition & 0 deletions test/aot/built-ins-alone/fresh-results-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export const notCalled: Record<string, string> = {
ofOneModule("internal/util/inspect"),
"native:Bindgen:node_util_binding.bind.ts:internalErrorEntries": ofOneModule("node:util"),
"native:Cpp:NodeURL.cpp:Bun::createNodeURLBinding": ofOneModule("internal/url"),
"native:Cpp:UtilInspect.cpp:createWellKnownPrototypes": ofOneModule("internal/util/inspect"),
"native:NewCppFunction:BunCompiledModules.cpp:jsFunctionCallOwnFunctionsWithoutParameters": ofOneModule(
"ahead-of-time/internal/primordials",
),
Expand Down
Loading
Loading