Skip to content
Closed
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
29 changes: 19 additions & 10 deletions src/jsc/bindings/ErrorCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ namespace Bun {
using namespace JSC;
using namespace WTF;

static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code)
static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name)
{
JSC::JSObject* prototype;

Expand Down Expand Up @@ -135,9 +135,8 @@ static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* glo
break;
}

prototype->putDirect(vm, vm.propertyNames->name, jsString(vm, String(name)), 0);
prototype->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), jsString(vm, String(code)), 0);
prototype->putDirect(vm, vm.propertyNames->toString, JSC::JSFunction::create(vm, globalObject, 0, "toString"_s, NodeError_proto_toString, JSC::ImplementationVisibility::Private), 0);
prototype->putDirect(vm, vm.propertyNames->name, jsString(vm, String(name)), JSC::PropertyAttribute::DontEnum | 0);
prototype->putDirect(vm, vm.propertyNames->toString, JSC::JSFunction::create(vm, globalObject, 0, "toString"_s, NodeError_proto_toString, JSC::ImplementationVisibility::Private), JSC::PropertyAttribute::DontEnum | 0);

return prototype;
}
Expand Down Expand Up @@ -189,9 +188,9 @@ static ErrorCodeCache* errorCache(Zig::GlobalObject* globalObject)
}

// clang-format on
static Structure* createErrorStructure(JSC::VM& vm, JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code)
static Structure* createErrorStructure(JSC::VM& vm, JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name)
{
auto* prototype = createErrorPrototype(vm, globalObject, type, name, code);
auto* prototype = createErrorPrototype(vm, globalObject, type, name);
return ErrorInstance::createStructure(vm, globalObject, prototype);
}

Expand All @@ -201,7 +200,7 @@ JSObject* ErrorCodeCache::createError(VM& vm, Zig::GlobalObject* globalObject, E
auto* cache = errorCache(globalObject);
const auto& data = errors[static_cast<size_t>(code)];
if (!cache->internalField(static_cast<unsigned>(code))) {
auto* structure = createErrorStructure(vm, globalObject, data.type, data.name, data.code);
auto* structure = createErrorStructure(vm, globalObject, data.type, data.name);
cache->internalField(static_cast<unsigned>(code)).set(vm, cache, structure);
}

Expand All @@ -214,6 +213,7 @@ JSObject* ErrorCodeCache::createError(VM& vm, Zig::GlobalObject* globalObject, E
// exception were thrown by ErrorInstance::create)
return uncheckedDowncast<JSObject>(thrown_exception->value());
}
created_error->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), jsString(vm, String(data.code)), 0);
return created_error;
}

Expand All @@ -237,8 +237,16 @@ JSObject* createError(VM& vm, JSC::JSGlobalObject* globalObject, ErrorCode code,
if (auto* zigGlobalObject = dynamicDowncast<Zig::GlobalObject>(globalObject))
return createError(vm, zigGlobalObject, code, message, jsUndefined());

auto* structure = createErrorStructure(vm, globalObject, errors[static_cast<size_t>(code)].type, errors[static_cast<size_t>(code)].name, errors[static_cast<size_t>(code)].code);
return JSC::ErrorInstance::create(globalObject, structure, message, jsUndefined(), nullptr, JSC::RuntimeType::TypeNothing, errors[static_cast<size_t>(code)].type, true);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
const auto& data = errors[static_cast<size_t>(code)];
auto* structure = createErrorStructure(vm, globalObject, data.type, data.name);
auto* created_error = JSC::ErrorInstance::create(globalObject, structure, message, jsUndefined(), nullptr, JSC::RuntimeType::TypeNothing, data.type, true);
if (auto* thrown_exception = scope.exception()) [[unlikely]] {
(void)scope.tryClearException();
return uncheckedDowncast<JSObject>(thrown_exception->value());
}
created_error->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), jsString(vm, String(data.code)), 0);
return created_error;
Comment thread
robobun marked this conversation as resolved.
Resolved
}

JSC::JSObject* createError(VM& vm, Zig::GlobalObject* globalObject, ErrorCode code, JSValue message, JSValue options)
Expand Down Expand Up @@ -1008,8 +1016,9 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_RangeError(JSC::ThrowScope& throwScope, JS
JSValueToStringSafe(globalObject, builder, value, true);
RELEASE_RETURN_IF_EXCEPTION(throwScope, {});

auto* structure = createErrorStructure(vm, globalObject, ErrorType::RangeError, "RangeError"_s, "ERR_INVALID_ARG_VALUE"_s);
auto* structure = createErrorStructure(vm, globalObject, ErrorType::RangeError, "RangeError"_s);
auto error = JSC::ErrorInstance::create(vm, structure, builder.toString(), jsUndefined(), nullptr, JSC::RuntimeType::TypeNothing, ErrorType::RangeError, true);
error->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), jsString(vm, String("ERR_INVALID_ARG_VALUE"_s)), 0);
throwScope.throwException(globalObject, error);
throwScope.release();
return {};
Expand Down
32 changes: 15 additions & 17 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2376,7 +2376,7 @@ JSC::EncodedJSValue SystemError__toErrorInstance(const SystemError* arg0, JSC::J
if (scope.exception()) {
scope.clearException();
} else {
result->putDirect(vm, clientData->builtinNames().codePublicName(), code, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, clientData->builtinNames().codePublicName(), code, 0);
}
}

Expand All @@ -2385,7 +2385,7 @@ JSC::EncodedJSValue SystemError__toErrorInstance(const SystemError* arg0, JSC::J
if (scope.exception()) {
scope.clearException();
} else {
result->putDirect(vm, clientData->builtinNames().pathPublicName(), path, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, clientData->builtinNames().pathPublicName(), path, 0);
}
}

Expand All @@ -2394,21 +2394,21 @@ JSC::EncodedJSValue SystemError__toErrorInstance(const SystemError* arg0, JSC::J
if (scope.exception()) {
scope.clearException();
} else {
result->putDirect(vm, clientData->builtinNames().destPublicName(), dest, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, clientData->builtinNames().destPublicName(), dest, 0);
}
}

if (err.fd >= 0) {
JSC::JSValue fd = jsNumber(err.fd);
result->putDirect(vm, names.fdPublicName(), fd, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, names.fdPublicName(), fd, 0);
}

if (err.syscall.tag != BunStringTag::Empty) {
JSC::JSValue syscall = Bun::toJS(globalObject, err.syscall);
if (scope.exception()) {
scope.clearException();
} else {
result->putDirect(vm, names.syscallPublicName(), syscall, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, names.syscallPublicName(), syscall, 0);
}
}

Expand All @@ -2417,11 +2417,11 @@ JSC::EncodedJSValue SystemError__toErrorInstance(const SystemError* arg0, JSC::J
if (scope.exception()) {
scope.clearException();
} else {
result->putDirect(vm, names.hostnamePublicName(), hostname, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, names.hostnamePublicName(), hostname, 0);
}
}

result->putDirect(vm, names.errnoPublicName(), jsNumber(err.errno_), JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, names.errnoPublicName(), jsNumber(err.errno_), 0);

return JSC::JSValue::encode(result);
}
Expand All @@ -2444,21 +2444,19 @@ JSC::EncodedJSValue SystemError__toErrorInstanceWithInfoObject(const SystemError
auto clientData = WebCore::clientData(vm);

result->putDirect(vm, vm.propertyNames->name, jsString(vm, String("SystemError"_s)), JSC::PropertyAttribute::DontEnum | 0);
result->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, String("ERR_SYSTEM_ERROR"_s)), JSC::PropertyAttribute::DontEnum | 0);
result->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, String("ERR_SYSTEM_ERROR"_s)), 0);

info->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, codeString), JSC::PropertyAttribute::DontDelete | 0);

result->putDirect(vm, JSC::Identifier::fromString(vm, "info"_s), info, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, JSC::Identifier::fromString(vm, "info"_s), info, 0);

auto syscallJsString = jsString(vm, syscallString);
result->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString, JSC::PropertyAttribute::DontDelete | 0);
info->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString, JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString, 0);
info->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString, 0);

info->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, codeString), JSC::PropertyAttribute::DontDelete | 0);
info->putDirect(vm, vm.propertyNames->message, jsString(vm, messageString), JSC::PropertyAttribute::DontDelete | 0);
info->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, codeString), 0);
info->putDirect(vm, vm.propertyNames->message, jsString(vm, messageString), 0);

info->putDirect(vm, clientData->builtinNames().errnoPublicName(), jsNumber(err.errno_), JSC::PropertyAttribute::DontDelete | 0);
result->putDirect(vm, clientData->builtinNames().errnoPublicName(), jsNumber(err.errno_), JSC::PropertyAttribute::DontDelete | 0);
info->putDirect(vm, clientData->builtinNames().errnoPublicName(), jsNumber(err.errno_), 0);
result->putDirect(vm, clientData->builtinNames().errnoPublicName(), jsNumber(err.errno_), 0);

return JSC::JSValue::encode(result);
}
Expand Down
95 changes: 95 additions & 0 deletions test/js/node/errors/error-code-own-property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { describe, expect, test } from "bun:test";
import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import fs from "node:fs";
import os from "node:os";

// Node.js defines `code` on ERR_* errors as an own, enumerable, writable,
// configurable data property on the instance. That makes it visible to
// Object.keys, JSON.stringify, spread and hasOwnProperty, which structured
// error loggers rely on.

function capture(fn: () => void): Error & { code?: string } {
try {
fn();
} catch (e) {
return e as Error & { code?: string };
}
throw new Error("expected throw");
}

describe("Node.js ERR_* error .code is an own property", () => {
const cases: Array<[string, string, () => void]> = [
["fs options ERR_INVALID_ARG_TYPE", "ERR_INVALID_ARG_TYPE", () => fs.rmSync("/x", { recursive: "yes" as any })],
["fs ERR_OUT_OF_RANGE", "ERR_OUT_OF_RANGE", () => fs.mkdirSync("/x", { mode: -1 })],
["Buffer ERR_OUT_OF_RANGE", "ERR_OUT_OF_RANGE", () => Buffer.alloc(-1)],
["URL ERR_INVALID_URL", "ERR_INVALID_URL", () => new URL("nope")],
["events ERR_INVALID_ARG_TYPE", "ERR_INVALID_ARG_TYPE", () => new EventEmitter().setMaxListeners("x" as any)],
];

test.each(cases)("%s", (_label, expectedCode, fn) => {
const err = capture(fn);

expect(err.code).toBe(expectedCode);

const desc = Object.getOwnPropertyDescriptor(err, "code");
expect(desc).toEqual({
value: expectedCode,
writable: true,
enumerable: true,
configurable: true,
});

expect(Object.prototype.hasOwnProperty.call(err, "code")).toBe(true);
expect(Object.keys(err)).toContain("code");
expect({ ...err }.code).toBe(expectedCode);
expect(JSON.parse(JSON.stringify(err)).code).toBe(expectedCode);
});

test("for...in only enumerates own 'code', not prototype name/toString", () => {
const err = capture(() => Buffer.alloc(-1));
const keys: string[] = [];
for (const k in err) keys.push(k);
expect(keys).toContain("code");
expect(keys).not.toContain("name");
expect(keys).not.toContain("toString");
});

test("prototype does not carry 'code'", () => {
const err = capture(() => Buffer.alloc(-1));
const proto = Object.getPrototypeOf(err);
expect(Object.prototype.hasOwnProperty.call(proto, "code")).toBe(false);
});

test("toString() still includes the code", () => {
const err = capture(() => Buffer.alloc(-1));
expect(String(err)).toContain("[ERR_OUT_OF_RANGE]");
expect(err.name).toBe("RangeError");
});

test("errno errors have own 'code' with Node's descriptor", () => {
const err = capture(() => fs.openSync("/nonexistent-robobun-probe", "r"));
expect(err.code).toBe("ENOENT");
for (const prop of ["code", "errno", "syscall", "path"] as const) {
expect(Object.getOwnPropertyDescriptor(err, prop)).toEqual({
value: (err as any)[prop],
writable: true,
enumerable: true,
configurable: true,
});
}
});

test("ERR_SYSTEM_ERROR 'code' is own and enumerable", () => {
const err = capture(() => os.setPriority(-1, 0));
expect(err.code).toBe("ERR_SYSTEM_ERROR");
expect(Object.getOwnPropertyDescriptor(err, "code")).toEqual({
value: "ERR_SYSTEM_ERROR",
writable: true,
enumerable: true,
configurable: true,
});
expect(Object.keys(err)).toContain("code");
expect({ ...err }.code).toBe("ERR_SYSTEM_ERROR");
});
});
Loading