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/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ declare function $ERR_INVALID_ARG_TYPE(argName: string, expectedType: string, ac
declare function $ERR_INVALID_ARG_TYPE(argName: string, expectedTypes: string[], actualValue: any): TypeError;
declare function $ERR_INVALID_ARG_VALUE(name: string, value: any, reason?: string): TypeError;
declare function $ERR_UNKNOWN_ENCODING(enc: string): TypeError;
declare function $ERR_UNKNOWN_SIGNAL(signal: string | number): TypeError;
declare function $ERR_STREAM_DESTROYED(method: string): Error;
declare function $ERR_METHOD_NOT_IMPLEMENTED(method: string): Error;
declare function $ERR_STREAM_ALREADY_FINISHED(method: string): Error;
Expand Down
8 changes: 1 addition & 7 deletions src/js/node/child_process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ function convertToValidSignal(signal) {
if (signalName) return signalName;
}

throw ERR_UNKNOWN_SIGNAL(signal);
throw $ERR_UNKNOWN_SIGNAL(signal);
}

function sanitizeKillSignal(killSignal) {
Expand Down Expand Up @@ -1955,12 +1955,6 @@ function genericNodeError(message, errorProperties) {
return err;
}

function ERR_UNKNOWN_SIGNAL(name) {
const err = new TypeError(`Unknown signal: ${name}`);
err.code = "ERR_UNKNOWN_SIGNAL";
return err;
}

function ERR_INVALID_OPT_VALUE(name, value) {
const err = new TypeError(`The value "${value}" is invalid for option "${name}"`);
err.code = "ERR_INVALID_OPT_VALUE";
Expand Down
3 changes: 2 additions & 1 deletion src/js/node/http2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3852,7 +3852,8 @@ function receivedValueLabel(value) {
if (typeof value === "function") return `function ${value.name}`;
if (typeof value === "string") return `type string ('${value}')`;
if (typeof value === "symbol") return `type symbol (${String(value)})`;
if (typeof value === "number") return `type number (${String(value)})`;
if (typeof value === "number") return `type number (${value === 0 && 1 / value < 0 ? "-0" : String(value)})`;
if (typeof value === "bigint") return `type bigint (${value}n)`;
return `type ${typeof value} (${JSON.stringify(value)})`;
}

Comment thread
robobun marked this conversation as resolved.
Expand Down
149 changes: 133 additions & 16 deletions src/jsc/bindings/ErrorCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
#include "helpers.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/ErrorInstance.h"
#include "JavaScriptCore/JSFunction.h"
#include "JavaScriptCore/JSString.h"
#include "JavaScriptCore/JSType.h"
#include "JavaScriptCore/MathCommon.h"
#include "JavaScriptCore/ProxyObject.h"
#include "JavaScriptCore/Symbol.h"
#include "wtf/Assertions.h"
#include "wtf/Vector.h"
Expand Down Expand Up @@ -351,6 +354,11 @@ void JSValueToStringSafe(JSC::JSGlobalObject* globalObject, MessageBuilder& buil
{
ASSERT(!arg.isEmpty());
if (!arg.isCell()) {
// util.inspect and %s keep the sign of -0 (lib/internal/util/inspect.js, formatNumber). ToString drops it.
if (arg.isDouble() && JSC::isNegativeZero(arg.asDouble())) {
builder.append("-0"_s);
return;
}
builder.append(arg.toWTFStringForConsole(globalObject));
return;
}
Expand Down Expand Up @@ -412,6 +420,124 @@ void JSValueToStringSafe(JSC::JSGlobalObject* globalObject, MessageBuilder& buil
builder.append(Bun__inspect_singleline(defaultGlobalObject(globalObject), arg).transferToWTFString());
}

// builtInObjects in node's lib/internal/util/inspect.js: the ECMAScript globals only.
static bool isBuiltInObjectName(const WTF::String& name)
{
static constexpr ASCIILiteral names[] = {
"AggregateError"_s, "Array"_s, "ArrayBuffer"_s, "Atomics"_s, "BigInt"_s, "BigInt64Array"_s,
"BigUint64Array"_s, "Boolean"_s, "DataView"_s, "Date"_s, "Error"_s, "EvalError"_s,
"FinalizationRegistry"_s, "Float32Array"_s, "Float64Array"_s, "Function"_s, "Infinity"_s,
"Int16Array"_s, "Int32Array"_s, "Int8Array"_s, "Intl"_s, "Iterator"_s, "JSON"_s, "Map"_s,
"Math"_s, "NaN"_s, "Number"_s, "Object"_s, "Promise"_s, "Proxy"_s, "RangeError"_s,
"ReferenceError"_s, "Reflect"_s, "RegExp"_s, "Set"_s, "String"_s, "Symbol"_s, "SyntaxError"_s,
"TypeError"_s, "URIError"_s, "Uint16Array"_s, "Uint32Array"_s, "Uint8Array"_s,
"Uint8ClampedArray"_s, "WeakMap"_s, "WeakRef"_s, "WeakSet"_s
};
for (const auto& candidate : names) {
if (name == candidate)
return true;
}
return false;
}

// hasBuiltInToString in node's lib/internal/util/inspect.js.
static bool hasBuiltInToString(JSC::JSGlobalObject* globalObject, JSC::JSObject* object)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
while (auto* proxy = dynamicDowncast<JSC::ProxyObject>(object)) {
if (proxy->isRevoked())
return true;
object = proxy->target();
}

const JSC::Identifier& toStringName = vm.propertyNames->toString;
const JSC::Identifier& toPrimitiveName = vm.propertyNames->toPrimitiveSymbol;
bool walkToString = true;
bool walkToPrimitive = true;

JSValue toString = object->get(globalObject, toStringName);
RETURN_IF_EXCEPTION(scope, true);
JSValue toPrimitive = object->get(globalObject, toPrimitiveName);
RETURN_IF_EXCEPTION(scope, true);
if (!toString.isCallable()) {
if (!toPrimitive.isCallable())
return true;
walkToString = false;
} else {
bool ownToString = object->hasOwnProperty(globalObject, toStringName);
RETURN_IF_EXCEPTION(scope, true);
if (ownToString)
return false;
walkToPrimitive = toPrimitive.isCallable();
}
if (walkToPrimitive) {
bool ownToPrimitive = object->hasOwnProperty(globalObject, toPrimitiveName);
RETURN_IF_EXCEPTION(scope, true);
if (ownToPrimitive)
return false;
}

// Find the prototype that owns the method, then ask whether its constructor is a built-in.
JSObject* pointer = object;
while (true) {
JSValue prototype = pointer->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, true);
if (!prototype.isObject())
return true;
pointer = JSC::asObject(prototype);
bool owns = false;
if (walkToString) {
owns = pointer->hasOwnProperty(globalObject, toStringName);
RETURN_IF_EXCEPTION(scope, true);
}
if (!owns && walkToPrimitive) {
owns = pointer->hasOwnProperty(globalObject, toPrimitiveName);
RETURN_IF_EXCEPTION(scope, true);
}
if (owns)
break;
}

JSC::PropertySlot slot(pointer, JSC::PropertySlot::InternalMethodType::GetOwnProperty);
bool hasConstructor = pointer->getOwnPropertySlot(pointer, globalObject, vm.propertyNames->constructor, slot);
RETURN_IF_EXCEPTION(scope, true);
if (!hasConstructor || slot.isAccessor())
return false;
JSValue constructor = slot.getValue(globalObject, vm.propertyNames->constructor);
RETURN_IF_EXCEPTION(scope, true);
if (!constructor.isCallable())
return false;
JSValue name = constructor.get(globalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, true);
if (!name.isString())
return false;
auto nameString = name.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, true);
return isBuiltInObjectName(nameString);
}

void JSValueToStringLikeFormatS(JSC::JSGlobalObject* globalObject, MessageBuilder& builder, JSValue arg)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
if (arg.isObject()) {
Comment thread
robobun marked this conversation as resolved.
// typeof "function" values go through String() in node, so a function prints its source.
bool stringify = arg.isCallable();
if (!stringify) {
stringify = !hasBuiltInToString(globalObject, arg.getObject());
RETURN_IF_EXCEPTION(scope, );
}
if (stringify) {
auto string = arg.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, );
builder.append(string);
return;
}
}
RELEASE_AND_RETURN(scope, JSValueToStringSafe(globalObject, builder, arg));
Comment thread
robobun marked this conversation as resolved.
}

void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, MessageBuilder& builder, JSValue value)
{
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
Expand Down Expand Up @@ -1260,20 +1386,11 @@ JSC::EncodedJSValue INVALID_FILE_URL_PATH(JSC::ThrowScope& throwScope, JSC::JSGl
return {};
}

JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView encoding)
{
MessageBuilder message;
message.append("Unknown encoding: "_s, encoding);
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_ENCODING, message));
throwScope.release();
return {};
}

JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& scope, JSGlobalObject* globalObject, JSValue encodingValue)
{
MessageBuilder builder;
builder.append("Unknown encoding: "_s);
JSValueToStringSafe(globalObject, builder, encodingValue);
JSValueToStringLikeFormatS(globalObject, builder, encodingValue);
RELEASE_RETURN_IF_EXCEPTION(scope, {});
scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_ENCODING, builder));
scope.release();
Expand Down Expand Up @@ -1878,9 +1995,9 @@ JSC::EncodedJSValue Bun::throwError(JSC::JSGlobalObject* globalObject, JSC::Thro

namespace Bun {

// Error codes whose message is fixed text around one or two stringified
// arguments; `jsFunctionMakeErrorWithCode` builds these from the table instead
// of a switch case each.
// Error codes whose message is fixed text around one or two `%s` arguments;
// `jsFunctionMakeErrorWithCode` builds these from the table instead of a switch
// case each.
Comment thread
robobun marked this conversation as resolved.
struct SimpleErrorMessage {
Bun::ErrorCode code;
uint8_t argumentCount;
Expand Down Expand Up @@ -1926,16 +2043,16 @@ static constexpr SimpleErrorMessage simpleErrorMessages[] = {
{ ErrorCode::ERR_VM_MODULE_STATUS, 1, { "Module status "_s, ""_s, ""_s } },
{ ErrorCode::ERR_ZSTD_INVALID_PARAM, 1, { ""_s, " is not a valid zstd parameter"_s, ""_s } },
{ ErrorCode::ERR_INSPECTOR_COMMAND, 1, { "Inspector error "_s, ""_s, ""_s } },
{ ErrorCode::ERR_UNKNOWN_SIGNAL, 1, { "Unknown signal: "_s, ""_s, ""_s } },
};

static JSC::EncodedJSValue makeSimpleErrorMessage(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame, JSC::ThrowScope& scope, const SimpleErrorMessage& entry)
{
MessageBuilder builder;
builder.append(entry.pieces[0]);
for (unsigned i = 0; i < entry.argumentCount; ++i) {
auto string = callFrame->argument(i + 1).toWTFString(globalObject);
JSValueToStringLikeFormatS(globalObject, builder, callFrame->argument(i + 1));
RETURN_IF_EXCEPTION(scope, {});
builder.append(string);
builder.append(entry.pieces[i + 1]);
}
return JSC::JSValue::encode(createError(globalObject, entry.code, builder));
Expand Down Expand Up @@ -2055,7 +2172,7 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Bun::jsFunctionMakeErrorWithCode, __att
auto arg0 = callFrame->argument(1);
MessageBuilder builder;
builder.append("Unknown encoding: "_s);
JSValueToStringSafe(globalObject, builder, arg0);
JSValueToStringLikeFormatS(globalObject, builder, arg0);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(createError(globalObject, error, builder));
}
Expand Down
5 changes: 4 additions & 1 deletion src/jsc/bindings/ErrorCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, Messa
// stringified, everything else through util.inspect. `quotesLikeInspect` quotes strings.
void JSValueToStringSafe(JSC::JSGlobalObject* globalObject, MessageBuilder& builder, JSC::JSValue arg, bool quotesLikeInspect);

// Appends the value the way util.format's `%s` renders it: a function and an object without a
// built-in toString go through String(), any other object through util.inspect.
Comment thread
robobun marked this conversation as resolved.
void JSValueToStringLikeFormatS(JSC::JSGlobalObject* globalObject, MessageBuilder& builder, JSC::JSValue arg);

enum Bound {
LOWER,
UPPER,
Expand Down Expand Up @@ -134,7 +138,6 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal
JSC::EncodedJSValue INVALID_ARG_VALUE_RangeError(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s);
JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s);
JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s);
JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView encoding);
JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope&, JSC::JSGlobalObject*, JSValue encodingValue);
JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& statemsg);
JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject);
Expand Down
10 changes: 3 additions & 7 deletions src/jsc/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ static WebCore::BufferEncodingType parseEncoding(JSC::ThrowScope& scope, JSC::JS
Bun::V::validateString(scope, lexicalGlobalObject, arg, "encoding"_s);
RETURN_IF_EXCEPTION(scope, WebCore::BufferEncodingType::utf8);
}
Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, view);
Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, arg);
return WebCore::BufferEncodingType::utf8;
}

Expand Down Expand Up @@ -675,11 +675,7 @@ static JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGlobalObj
std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, arg1);
RETURN_IF_EXCEPTION(scope, {});
if (!encoded) {
auto* encodingString = arg1.toString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
const auto& view = encodingString->view(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, view);
return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, arg1);
}

encoding = encoded.value();
Expand Down Expand Up @@ -1850,7 +1846,7 @@ static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, ThrowScope& sco

if (valueValue.isString()) {
if (!encoding.has_value()) {
return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, encodingString);
return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, encodingValue);
}
auto* str = valueValue.toString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, -1);
Expand Down
6 changes: 1 addition & 5 deletions src/jsc/bindings/JSStringDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,7 @@ static JSStringDecoder* createDecoder(JSC::JSGlobalObject* lexicalGlobalObject,
if (opt.has_value()) {
encoding = opt.value();
} else {
auto* encodingString = jsEncoding.toString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, nullptr);
const auto& view = encodingString->view(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, nullptr);
Bun::ERR::UNKNOWN_ENCODING(throwScope, lexicalGlobalObject, view);
Bun::ERR::UNKNOWN_ENCODING(throwScope, lexicalGlobalObject, jsEncoding);
return nullptr;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ JSReadableStream* createReadableByteStream(JSGlobalObject* globalObject, SourceK
static void throwNotIterable(JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSValue iterable)
{
Bun::MessageBuilder builder;
Bun::JSValueToStringSafe(globalObject, builder, iterable, false);
Bun::JSValueToStringLikeFormatS(globalObject, builder, iterable);
RETURN_IF_EXCEPTION(scope, );
builder.append(" must be iterable"_s);
Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ARG_NOT_ITERABLE, builder);
Expand Down
50 changes: 29 additions & 21 deletions src/runtime/node/node_crypto_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,28 @@ pub mod random {
};
const MAX_RANGE: i64 = 0xffff_ffff_ffff;

// addNumericalSeparator in node's lib/internal/errors.js, applied past 2^32.
fn received_integer(value: i64) -> String {
let digits = value.to_string();
if value.unsigned_abs() <= 1 << 32 {
return digits;
}
let (sign, digits) = match digits.strip_prefix('-') {
Some(rest) => ("-", rest),
None => ("", digits.as_str()),
};
let mut out = String::with_capacity(digits.len() + digits.len() / 3 + 1);
out.push_str(sign);
let lead = digits.len() % 3;
for (i, ch) in digits.chars().enumerate() {
if i != 0 && (i + 3 - lead) % 3 == 0 {
out.push('_');
}
out.push(ch);
}
out
}

impl JobContext for RandomFillJob {
type OffThread = Self;
type Js = RandomFillJs;
Expand Down Expand Up @@ -348,38 +370,24 @@ pub mod random {
let max: i64 = max_value.as_number().trunc() as i64;

if max <= min {
let received = if max_value.as_number().is_sign_negative() && max == 0 {
String::from("-0")
} else {
received_integer(max)
};
return Err(global
.err(
jsc::ErrorCode::OUT_OF_RANGE,
format_args!(
"The value of \"max\" is out of range. It must be greater than the value of \"min\" ({}). Received {}",
min, max
min, received
),
)
.throw());
Comment thread
robobun marked this conversation as resolved.
}

if max - min > MAX_RANGE {
// Node's ERR_OUT_OF_RANGE adds "_" numerical separators to integer
// "Received" values whose magnitude exceeds 2^32
// (lib/internal/errors.js, addNumericalSeparator).
let received = {
let digits = (max - min).to_string();
let (sign, digits) = match digits.strip_prefix('-') {
Some(rest) => ("-", rest),
None => ("", digits.as_str()),
};
let mut out = String::with_capacity(digits.len() + digits.len() / 3 + 1);
out.push_str(sign);
let lead = digits.len() % 3;
for (i, ch) in digits.chars().enumerate() {
if i != 0 && (i + 3 - lead) % 3 == 0 {
out.push('_');
}
out.push(ch);
}
out
};
let received = received_integer(max - min);
if min_specified {
return Err(global
.err(
Expand Down
Loading
Loading