diff --git a/src/bun.js/bindings/.clang-format b/src/bun.js/bindings/.clang-format index d8fb9d0b972a3..79808145f886f 100644 --- a/src/bun.js/bindings/.clang-format +++ b/src/bun.js/bindings/.clang-format @@ -11,7 +11,7 @@ AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 6bc97c7ed9b90..b214f1d75353a 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -4,6 +4,7 @@ #include #include #include "CommonJSModuleRecord.h" +#include "ErrorCode.h" #include "JavaScriptCore/CatchScope.h" #include "JavaScriptCore/JSCJSValue.h" #include "JavaScriptCore/JSCast.h" @@ -447,19 +448,14 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionUmask, } if (!numberValue.isAnyInt()) { - throwNodeRangeError(globalObject, throwScope, "The \"mask\" argument must be an integer"_s); - return JSValue::encode({}); + return Bun::ERR::OUT_OF_RANGE(throwScope, globalObject, "mask"_s, "an integer"_s, numberValue); } double number = numberValue.toNumber(globalObject); int64_t newUmask = isInt52(number) ? tryConvertToInt52(number) : numberValue.toInt32(globalObject); RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); if (newUmask < 0 || newUmask > 4294967295) { - StringBuilder messageBuilder; - messageBuilder.append("The \"mask\" value must be in range [0, 4294967295]. Received value: "_s); - messageBuilder.append(int52ToString(vm, newUmask, 10)->getString(globalObject)); - throwNodeRangeError(globalObject, throwScope, messageBuilder.toString()); - return JSValue::encode({}); + return Bun::ERR::OUT_OF_RANGE(throwScope, globalObject, "mask"_s, 0, 4294967295, numberValue); } return JSC::JSValue::encode(JSC::jsNumber(umask(newUmask))); @@ -2778,7 +2774,7 @@ JSC_DEFINE_CUSTOM_SETTER(setProcessDebugPort, JSValue value = JSValue::decode(encodedValue); if (!value.isInt32AsAnyInt()) { - throwNodeRangeError(globalObject, scope, "debugPort must be 0 or in range 1024 to 65535"_s); + Bun::ERR::OUT_OF_RANGE(scope, globalObject, "debugPort"_s, "0 or >= 1024 and <= 65535"_s, value); return false; } @@ -2786,7 +2782,7 @@ JSC_DEFINE_CUSTOM_SETTER(setProcessDebugPort, if (port != 0) { if (port < 1024 || port > 65535) { - throwNodeRangeError(globalObject, scope, "debugPort must be 0 or in range 1024 to 65535"_s); + Bun::ERR::OUT_OF_RANGE(scope, globalObject, "debugPort"_s, "0 or >= 1024 and <= 65535"_s, value); return false; } } @@ -2895,11 +2891,11 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionKill, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - int pid = callFrame->argument(0).toInt32(globalObject); + auto pid_value = callFrame->argument(0); + int pid = pid_value.toInt32(globalObject); RETURN_IF_EXCEPTION(scope, {}); if (pid < 0) { - throwNodeRangeError(globalObject, scope, "pid must be a positive integer"_s); - return JSValue::encode(jsUndefined()); + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, "pid"_s, "a positive integer"_s, pid_value); } JSC::JSValue signalValue = callFrame->argument(1); int signal = SIGTERM; @@ -2912,8 +2908,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionKill, signal = num; RETURN_IF_EXCEPTION(scope, {}); } else { - throwNodeRangeError(globalObject, scope, "Unknown signal name"_s); - return JSValue::encode(jsUndefined()); + return Bun::ERR::UNKNOWN_SIGNAL(scope, globalObject, signalValue); } RETURN_IF_EXCEPTION(scope, {}); } else if (!signalValue.isUndefinedOrNull()) { diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index a962c516df069..38a8aa2f5dbe5 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -7,7 +7,6 @@ #include "JavaScriptCore/ErrorType.h" #include "JavaScriptCore/ObjectConstructor.h" #include "JavaScriptCore/WriteBarrier.h" -#include "root.h" #include "headers-handwritten.h" #include "BunClientData.h" #include "helpers.h" @@ -17,6 +16,7 @@ #include "JavaScriptCore/JSType.h" #include "JavaScriptCore/Symbol.h" #include "wtf/Assertions.h" +#include "wtf/Forward.h" #include "wtf/text/ASCIIFastPath.h" #include "wtf/text/ASCIILiteral.h" #include "wtf/text/MakeString.h" @@ -27,6 +27,7 @@ #include "JSDOMException.h" #include "ErrorCode.h" +#include static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code, bool isDOMExceptionPrototype = false) { @@ -61,12 +62,6 @@ static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* glo return prototype; } -extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue val_arg_name, JSC::EncodedJSValue val_expected_type, JSC::EncodedJSValue val_actual_value); -extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE_static(JSC::JSGlobalObject* globalObject, const ZigString* val_arg_name, const ZigString* val_expected_type, JSC::EncodedJSValue val_actual_value); -extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3); -extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS_static(JSC::JSGlobalObject* globalObject, const ZigString* arg1, const ZigString* arg2, const ZigString* arg3); -extern "C" JSC::EncodedJSValue Bun__ERR_IPC_CHANNEL_CLOSED(JSC::JSGlobalObject* globalObject); - // clang-format on #define EXPECT_ARG_COUNT(count__) \ @@ -301,8 +296,158 @@ WTF::String ERR_OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* global auto input = JSValueToStringSafe(globalObject, val_input); RETURN_IF_EXCEPTION(scope, {}); - return makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received: \""_s, input, '"'); + return makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received "_s, input); +} +} + +namespace ERR { + +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance) +{ + auto arg_name = val_arg_name.span8(); + ASSERT(WTF::charactersAreAllASCII(arg_name)); + + auto expected_type = val_expected_type.span8(); + ASSERT(WTF::charactersAreAllASCII(expected_type)); + + auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + RETURN_IF_EXCEPTION(throwScope, {}); + + if (instance) { + auto message = makeString("The \""_s, arg_name, "\" argument must be an instance of "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; + } + + auto message = makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; +} +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance) +{ + auto arg_name = val_arg_name.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto expected_type = val_expected_type.span8(); + ASSERT(WTF::charactersAreAllASCII(expected_type)); + + auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + RETURN_IF_EXCEPTION(throwScope, {}); + + if (instance) { + auto message = makeString("The \""_s, arg_name, "\" argument must be an instance of "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; + } + + auto message = makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; +} + +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, size_t lower, size_t upper, JSC::JSValue actual) +{ + auto lowerStr = jsNumber(lower).toWTFString(globalObject); + auto upperStr = jsNumber(upper).toWTFString(globalObject); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be >= "_s, lowerStr, " and <= "_s, upperStr, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t lower, size_t upper, JSC::JSValue actual) +{ + auto arg_name = arg_name_val.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto lowerStr = jsNumber(lower).toWTFString(globalObject); + auto upperStr = jsNumber(upper).toWTFString(globalObject); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be >= "_s, lowerStr, " and <= "_s, upperStr, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, ASCIILiteral msg, JSC::JSValue actual) +{ + auto arg_name = arg_name_val.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, msg, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, ASCIILiteral msg, JSC::JSValue actual) +{ + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, msg, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} + +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSC::JSValue value, ASCIILiteral reason) +{ + ASCIILiteral type; + { + auto sp = name.span8(); + auto str = std::string_view((const char*)(sp.data()), sp.size()); + auto has = str.find('.') == std::string::npos; + type = has ? "property"_s : "argument"_s; + } + + auto value_string = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The "_s, type, " '"_s, name, "' "_s, reason, ". Received "_s, value_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, message)); + return {}; +} + +JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue encoding) +{ + auto encoding_string = JSValueToStringSafe(globalObject, encoding); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("Unknown encoding: "_s, encoding_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_ENCODING, message)); + return {}; +} + +JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral statemsg) +{ + auto message = makeString("Invalid state: "_s, statemsg); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_STATE, message)); + return {}; +} + +JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + auto message = makeString("Cannot create a string longer than "_s, WTF::String::MaxLength, " characters"_s); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_STRING_TOO_LONG, message)); + return {}; } + +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); + return {}; +} + +JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal) +{ + auto signal_string = JSValueToStringSafe(globalObject, signal); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("Unknown signal: "_s, signal_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_SIGNAL, message)); + return {}; +} + } static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2) @@ -326,15 +471,6 @@ static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalOb return createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, msg); } -extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue val_arg_name, JSC::EncodedJSValue val_expected_type, JSC::EncodedJSValue val_actual_value) -{ - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto message = Message::ERR_INVALID_ARG_TYPE(scope, globalObject, JSValue::decode(val_arg_name), JSValue::decode(val_expected_type), JSValue::decode(val_actual_value)); - RETURN_IF_EXCEPTION(scope, {}); - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); -} extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE_static(JSC::JSGlobalObject* globalObject, const ZigString* val_arg_name, const ZigString* val_expected_type, JSC::EncodedJSValue val_actual_value) { JSC::VM& vm = globalObject->vm(); @@ -372,41 +508,6 @@ extern "C" JSC::EncodedJSValue Bun__createErrorWithCode(JSC::JSGlobalObject* glo return JSValue::encode(createError(globalObject, code, message->toWTFString(BunString::ZeroCopy))); } -extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3) -{ - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (arg1 == 0) { - JSC::throwTypeError(globalObject, scope, "requires at least 1 argument"_s); - return {}; - } - - auto name1 = JSValue::decode(arg1).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (arg2 == 0) { - // 1 arg name passed - auto message = makeString("The \""_s, name1, "\" argument must be specified"_s); - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_MISSING_ARGS, message)); - } - - auto name2 = JSValue::decode(arg2).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (arg3 == 0) { - // 2 arg names passed - auto message = makeString("The \""_s, name1, "\" and \""_s, name2, "\" arguments must be specified"_s); - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_MISSING_ARGS, message)); - } - - auto name3 = JSValue::decode(arg3).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - // 3 arg names passed - auto message = makeString("The \""_s, name1, "\", \""_s, name2, "\", and \""_s, name3, "\" arguments must be specified"_s); - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_MISSING_ARGS, message)); -} extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS_static(JSC::JSGlobalObject* globalObject, const ZigString* arg1, const ZigString* arg2, const ZigString* arg3) { JSC::VM& vm = globalObject->vm(); @@ -445,11 +546,7 @@ extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS_static(JSC::JSGlobalObject* JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) { - return Bun__ERR_IPC_CHANNEL_CLOSED(globalObject); -} -extern "C" JSC::EncodedJSValue Bun__ERR_IPC_CHANNEL_CLOSED(JSC::JSGlobalObject* globalObject) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_CHANNEL_CLOSED, "Channel closed."_s)); + return JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_CHANNEL_CLOSED, "Channel closed."_s)); } JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) @@ -474,6 +571,11 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL, (JSC::JSGlobalObject * return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_PROTOCOL, message)); } +JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BUFFER_OUT_OF_BOUNDS, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) +{ + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); +} + JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 4e2d1d60108df..99e6de3221939 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -1,7 +1,9 @@ #pragma once -#include "ZigGlobalObject.h" #include "root.h" +#include "JavaScriptCore/ThrowScope.h" +#include "JavaScriptCore/JSCJSValue.h" +#include "ZigGlobalObject.h" #include #include #include "BunClientData.h" @@ -62,6 +64,27 @@ JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_SERVER_NOT_RUNNING); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL); +JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BUFFER_OUT_OF_BOUNDS); JSC_DECLARE_HOST_FUNCTION(jsFunctionMakeErrorWithCode); +extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE_static(JSC::JSGlobalObject* globalObject, const ZigString* val_arg_name, const ZigString* val_expected_type, JSC::EncodedJSValue val_actual_value); +extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS_static(JSC::JSGlobalObject* globalObject, const ZigString* arg1, const ZigString* arg2, const ZigString* arg3); + +namespace ERR { + +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance = false); +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance = false); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, size_t lower, size_t upper, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, size_t lower, size_t upper, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, ASCIILiteral msg, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name_val, ASCIILiteral msg, JSC::JSValue actual); +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSC::JSValue value, ASCIILiteral reason = "is invalid"_s); +JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue encoding); +JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral statemsg); +JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal); + +} + } diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index c8519af883efb..79cf61d7deedb 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -41,7 +41,11 @@ export default [ ["MODULE_NOT_FOUND", Error, "Error"], ["ERR_ILLEGAL_CONSTRUCTOR", TypeError, "TypeError"], ["ERR_INVALID_URL", TypeError, "TypeError"], + ["ERR_UNKNOWN_ENCODING", TypeError, "TypeError"], ["ERR_BUFFER_TOO_LARGE", RangeError, "RangeError"], + ["ERR_INVALID_STATE", Error, "Error"], + ["ERR_BUFFER_OUT_OF_BOUNDS", RangeError, "RangeError"], + ["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"], diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index da9a2e7c99555..d79efd7297801 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -9,6 +9,7 @@ #include "JavaScriptCore/JSGlobalObject.h" #include "BufferEncodingType.h" #include "JavaScriptCore/JSCJSValue.h" +#include "JavaScriptCore/PutPropertySlot.h" #include "JSBuffer.h" @@ -45,6 +46,8 @@ #include #include #include +#include +#include #include #include #include @@ -71,6 +74,7 @@ #include "DOMJITIDLTypeFilter.h" #include "DOMJITHelpers.h" #include +#include "ErrorCode.h" // #include #include @@ -245,29 +249,38 @@ static int normalizeCompareVal(int val, size_t a_length, size_t b_length) return val; } -static inline uint32_t parseIndex(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) -{ - if (auto num = arg.tryGetAsUint32Index()) - return num.value(); +const unsigned U32_MAX = std::numeric_limits().max(); - if (arg.isNumber()) - throwNodeRangeError(lexicalGlobalObject, scope, "Invalid array length"_s); - else - throwTypeError(lexicalGlobalObject, scope, "Expected number"_s); +static inline uint32_t parseIndex(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, ASCIILiteral name, JSValue arg, size_t upperBound) +{ + if (!arg.isNumber()) { + return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, name, "number"_s, arg); + } - return 0; + auto num = arg.asNumber(); + if (num < 0 || std::isinf(num)) { + return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, name, 0, upperBound, arg); + } + double intpart; + if (std::modf(num, &intpart) != 0) { + return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, name, "integer"_s, arg); + } + if (intpart < U32_MAX) { + return intpart; + } + return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, name, 0, upperBound, arg); } static inline WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) { if (UNLIKELY(!arg.isString())) { - throwTypeError(lexicalGlobalObject, scope, "Expected string"_s); + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "encoding"_s, "string"_s, arg); return WebCore::BufferEncodingType::utf8; } std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg); if (UNLIKELY(!encoded)) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); + Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, arg); return WebCore::BufferEncodingType::utf8; } @@ -434,20 +447,15 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocUnsafeBody(JS auto throwScope = DECLARE_THROW_SCOPE(vm); - if (callFrame->argumentCount() < 1) - return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); - - JSValue lengthValue = callFrame->uncheckedArgument(0); + JSValue lengthValue = callFrame->argument(0); if (UNLIKELY(!lengthValue.isNumber())) { - throwTypeError(lexicalGlobalObject, throwScope, "size argument must be a number"_s); - return {}; + return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "size"_s, "number"_s, lengthValue); } double lengthDouble = lengthValue.toIntegerWithTruncation(lexicalGlobalObject); - if (UNLIKELY(lengthDouble < 0 || lengthDouble > UINT_MAX)) { - throwNodeRangeError(lexicalGlobalObject, throwScope, "Buffer size must be 0...4294967295"_s); - return {}; + if (UNLIKELY(lengthDouble < 0 || lengthDouble > MAX_ARRAY_BUFFER_SIZE || lengthDouble != lengthDouble)) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "size"_s, 0, MAX_ARRAY_BUFFER_SIZE, lengthValue); } size_t length = static_cast(lengthDouble); @@ -541,13 +549,12 @@ static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGl auto* str = arg0.toString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); if (arg1 && arg1.isString()) { std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg1); if (!encoded) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return JSC::JSValue::encode(jsUndefined()); + return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, arg1); } encoding = encoded.value(); @@ -567,15 +574,13 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG auto scope = DECLARE_THROW_SCOPE(vm); JSValue lengthValue = callFrame->uncheckedArgument(0); if (UNLIKELY(!lengthValue.isNumber())) { - throwTypeError(lexicalGlobalObject, scope, "size argument must be a number"_s); - return {}; + return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "size"_s, "number"_s, lengthValue); } double lengthDouble = lengthValue.toIntegerWithTruncation(lexicalGlobalObject); - if (UNLIKELY(lengthDouble < 0 || lengthDouble > UINT_MAX)) { - throwNodeRangeError(lexicalGlobalObject, scope, "Buffer size must be 0...4294967295"_s); - return {}; + if (UNLIKELY(lengthDouble < 0 || lengthDouble > MAX_ARRAY_BUFFER_SIZE || lengthDouble != lengthDouble)) { + return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "size"_s, 0, MAX_ARRAY_BUFFER_SIZE, lengthValue); } size_t length = static_cast(lengthDouble); @@ -596,11 +601,15 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG EnsureStillAliveScope arg2 = callFrame->uncheckedArgument(2); if (!arg2.value().isUndefined()) { encoding = parseEncoding(lexicalGlobalObject, scope, arg2.value()); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); } } auto startPtr = uint8Array->typedVector() + start; auto str_ = value.toWTFString(lexicalGlobalObject); + if (str_.isEmpty()) { + memset(startPtr, 0, length); + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(uint8Array)); + } ZigString str = Zig::toZigString(str_); if (UNLIKELY(!Bun__Buffer_fill(&str, startPtr, end - start, encoding))) { @@ -640,7 +649,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG auto value_ = value.toInt32(lexicalGlobalObject) & 0xFF; auto value_uint8 = static_cast(value_); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); auto length = uint8Array->byteLength(); auto start = 0; @@ -693,14 +702,9 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_byteLengthBody(JSC auto scope = DECLARE_THROW_SCOPE(vm); - if (UNLIKELY(callFrame->argumentCount() == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Not enough arguments"_s); - return JSC::JSValue::encode(jsUndefined()); - } - EnsureStillAliveScope arg0 = callFrame->argument(0); - EnsureStillAliveScope arg1 = callFrame->argument(1); + if (callFrame->argumentCount() > 1) { if (arg1.value().isString()) { @@ -724,35 +728,28 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_byteLengthBody(JSC return JSValue::encode(jsNumber(arrayBuffer->impl()->byteLength())); } - throwTypeError(lexicalGlobalObject, scope, "Invalid input, must be a string, Buffer, or ArrayBuffer"_s); - return JSC::JSValue::encode(jsUndefined()); + return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "string"_s, "string or an instance of Buffer or ArrayBuffer"_s, callFrame->argument(0)); } static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { auto& vm = JSC::getVM(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); - if (callFrame->argumentCount() < 2) { - throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); - return JSValue::encode(jsUndefined()); - } - auto castedThisValue = callFrame->uncheckedArgument(0); + auto castedThisValue = callFrame->argument(0); JSC::JSArrayBufferView* castedThis = JSC::jsDynamicCast(castedThisValue); if (UNLIKELY(!castedThis)) { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (first argument)"_s); - return JSValue::encode(jsUndefined()); + return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "buf1"_s, "Buffer or Uint8Array"_s, castedThisValue, true); } if (UNLIKELY(castedThis->isDetached())) { throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array (first argument) is detached"_s); return JSValue::encode(jsUndefined()); } - auto buffer = callFrame->uncheckedArgument(1); + auto buffer = callFrame->argument(1); JSC::JSArrayBufferView* view = JSC::jsDynamicCast(buffer); if (UNLIKELY(!view)) { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (2nd argument)"_s); - return JSValue::encode(jsUndefined()); + return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "buf2"_s, "Buffer or Uint8Array"_s, buffer, true); } if (UNLIKELY(view->isDetached())) { throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array (second argument) is detached"_s); @@ -767,29 +764,6 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J size_t sourceEndInit = castedThis->byteLength(); size_t sourceEnd = sourceEndInit; - switch (callFrame->argumentCount()) { - default: - sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(5)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - FALLTHROUGH; - case 5: - sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(4)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - FALLTHROUGH; - case 4: - targetEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - FALLTHROUGH; - case 3: - targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - break; - case 2: - case 1: - case 0: - break; - } - targetStart = std::min(targetStart, std::min(targetEnd, targetEndInit)); sourceStart = std::min(sourceStart, std::min(sourceEnd, sourceEndInit)); @@ -957,16 +931,12 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG { auto& vm = JSC::getVM(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); - if (callFrame->argumentCount() < 1) { - throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); - return JSValue::encode(jsUndefined()); - } - JSC::JSUint8Array* view = JSC::jsDynamicCast(callFrame->uncheckedArgument(0)); + auto arg0 = callFrame->argument(0); + JSC::JSUint8Array* view = JSC::jsDynamicCast(arg0); if (UNLIKELY(!view)) { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s); - return JSValue::encode(jsUndefined()); + return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "target"_s, "Buffer or Uint8Array"_s, arg0, true); } if (UNLIKELY(view->isDetached())) { @@ -990,39 +960,44 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG switch (callFrame->argumentCount()) { default: sourceEndValue = callFrame->uncheckedArgument(4); + if (sourceEndValue != jsUndefined()) + sourceEnd = parseIndex(lexicalGlobalObject, throwScope, "sourceEnd"_s, sourceEndValue, sourceEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); FALLTHROUGH; case 4: sourceStartValue = callFrame->uncheckedArgument(3); + if (sourceStartValue != jsUndefined()) + sourceStart = parseIndex(lexicalGlobalObject, throwScope, "sourceStart"_s, sourceStartValue, sourceEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); FALLTHROUGH; case 3: targetEndValue = callFrame->uncheckedArgument(2); + if (targetEndValue != jsUndefined()) + targetEnd = parseIndex(lexicalGlobalObject, throwScope, "targetEnd"_s, targetEndValue, targetEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); FALLTHROUGH; case 2: targetStartValue = callFrame->uncheckedArgument(1); + if (targetStartValue != jsUndefined()) + targetStart = parseIndex(lexicalGlobalObject, throwScope, "targetStart"_s, targetStartValue, targetEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); break; case 1: case 0: break; } - if (!targetStartValue.isUndefined()) { - targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(1)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + if (targetStart > targetEndInit && targetStart <= targetEnd) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "targetStart"_s, 0, targetEndInit, targetStartValue); } - - if (!targetEndValue.isUndefined()) { - targetEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + if (targetEnd > targetEndInit && targetEnd >= targetStart) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "targetEnd"_s, 0, targetEndInit, targetEndValue); } - - if (!sourceStartValue.isUndefined()) { - sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + if (sourceStart > sourceEndInit && sourceStart <= sourceEnd) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "sourceStart"_s, 0, sourceEndInit, sourceStartValue); } - - if (!sourceEndValue.isUndefined()) { - sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(4)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + if (sourceEnd > sourceEndInit && sourceEnd >= sourceStart) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "sourceEnd"_s, 0, sourceEndInit, sourceEndValue); } targetStart = std::min(targetStart, std::min(targetEnd, targetEndInit)); @@ -1076,33 +1051,24 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob switch (callFrame->argumentCount()) { default: sourceEndValue = callFrame->uncheckedArgument(3); + sourceEnd = parseIndex(lexicalGlobalObject, throwScope, "sourceEnd"_s, callFrame->uncheckedArgument(3), sourceEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); FALLTHROUGH; case 3: sourceStartValue = callFrame->uncheckedArgument(2); + sourceStart = parseIndex(lexicalGlobalObject, throwScope, "sourceStart"_s, callFrame->uncheckedArgument(2), sourceEndInit); + RETURN_IF_EXCEPTION(throwScope, {}); FALLTHROUGH; case 2: targetStartValue = callFrame->uncheckedArgument(1); + targetStart = parseIndex(lexicalGlobalObject, throwScope, "targetStart"_s, callFrame->uncheckedArgument(1), targetEnd); + RETURN_IF_EXCEPTION(throwScope, {}); break; case 1: case 0: break; } - if (!targetStartValue.isUndefined()) { - targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(1)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - } - - if (!sourceStartValue.isUndefined()) { - sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - } - - if (!sourceEndValue.isUndefined()) { - sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); - RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); - } - targetStart = std::min(targetStart, targetEnd); sourceEnd = std::min(sourceEnd, sourceEndInit); sourceStart = std::min(sourceStart, sourceEnd); @@ -1197,17 +1163,17 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob if (!encodingValue.isUndefined()) { encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); } if (!offsetValue.isUndefined()) { - start = parseIndex(lexicalGlobalObject, scope, offsetValue); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + start = parseIndex(lexicalGlobalObject, scope, "start"_s, offsetValue, limit); + RETURN_IF_EXCEPTION(scope, {}); } if (!lengthValue.isUndefined()) { - end = parseIndex(lexicalGlobalObject, scope, lengthValue); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + end = parseIndex(lexicalGlobalObject, scope, "end"_s, lengthValue, limit - start); + RETURN_IF_EXCEPTION(scope, {}); } if (start >= end) { @@ -1227,8 +1193,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob if (str.len == 0) { memset(startPtr, 0, end - start); } else if (UNLIKELY(!Bun__Buffer_fill(&str, startPtr, end - start, encoding))) { - throwTypeError(lexicalGlobalObject, scope, "Failed to decode value"_s); - return JSC::JSValue::encode(jsUndefined()); + return Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "value"_s, value); } } else if (auto* view = JSC::jsDynamicCast(value)) { auto* startPtr = castedThis->typedVector() + start; @@ -1264,7 +1229,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob auto value_ = value.toInt32(lexicalGlobalObject) & 0xFF; auto value_uint8 = static_cast(value_); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); auto startPtr = castedThis->typedVector() + start; auto endPtr = castedThis->typedVector() + end; @@ -1522,6 +1487,9 @@ static inline JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObj if (UNLIKELY(length == 0)) { RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); } + if (length > WTF::String::MaxLength) { + return Bun::ERR::STRING_TOO_LONG(scope, lexicalGlobalObject); + } JSC::EncodedJSValue ret = 0; @@ -1622,12 +1590,12 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (!arg1.isUndefined()) { encoding = parseEncoding(lexicalGlobalObject, scope, arg1); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); } if (!arg2.isUndefined()) { int32_t istart = arg2.toInt32(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); if (istart < 0) { throwTypeError(lexicalGlobalObject, scope, "Start must be a positive integer"_s); @@ -1640,7 +1608,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (!arg3.isUndefined()) { // length is end end = std::min(byteLength, static_cast(arg3.toInt32(lexicalGlobalObject))); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); } return jsBufferToString(vm, lexicalGlobalObject, castedThis, start, end > start ? end - start : 0, encoding); @@ -1820,7 +1788,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1056 encodingValue = offsetValue; setEncoding(); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); } @@ -1830,7 +1798,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo } int32_t userOffset = offsetValue.toInt32(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); if (userOffset < 0 || userOffset > max) { throwNodeRangeError(lexicalGlobalObject, scope, "Offset is out of bounds"_s); return JSC::JSValue::encode(jsUndefined()); @@ -1844,13 +1812,13 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo } else if (lengthValue.isString()) { encodingValue = lengthValue; setEncoding(); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); length = remaining; } else { setEncoding(); int32_t userLength = lengthValue.toInt32(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + RETURN_IF_EXCEPTION(scope, {}); length = std::min(static_cast(userLength), remaining); } @@ -1953,6 +1921,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_concat, (JSGlobalObject * l return jsBufferConstructorFunction_concatBody(lexicalGlobalObject, callFrame); } +JSC::JSValue jsBufferConstructorPoolSize(VM& vm, JSObject* object) +{ + return jsNumber(8192); +} + extern "C" JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsBufferConstructorAllocWithoutTypeChecks, JSUint8Array*, (JSC::JSGlobalObject * lexicalGlobalObject, void* thisValue, int size)); extern "C" JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsBufferConstructorAllocUnsafeWithoutTypeChecks, JSUint8Array*, (JSC::JSGlobalObject * lexicalGlobalObject, void* thisValue, int size)); extern "C" JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsBufferConstructorAllocUnsafeSlowWithoutTypeChecks, JSUint8Array*, (JSC::JSGlobalObject * lexicalGlobalObject, void* thisValue, int size)); @@ -2171,16 +2144,6 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "readUInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt8CodeGenerator, 1 } }, { "readUIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } }, { "readUIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } }, - // name alias - { "readUintBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } }, - { "readUintLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } }, - { "readUint8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt8CodeGenerator, 1 } }, - { "readUint16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16BECodeGenerator, 1 } }, - { "readUint16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16LECodeGenerator, 1 } }, - { "readUint32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt32BECodeGenerator, 1 } }, - { "readUint32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt32LECodeGenerator, 1 } }, - { "readBigUint64BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadBigUInt64BECodeGenerator, 1 } }, - { "readBigUint64LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadBigUInt64LECodeGenerator, 1 } }, { "slice"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } }, { "subarray"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } }, @@ -2223,25 +2186,43 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "writeUInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt8CodeGenerator, 1 } }, { "writeUIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntBECodeGenerator, 1 } }, { "writeUIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntLECodeGenerator, 1 } }, - // name alias - { "writeUintBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntBECodeGenerator, 1 } }, - { "writeUintLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntLECodeGenerator, 1 } }, - { "writeUint8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt8CodeGenerator, 1 } }, - { "writeUint16"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, - { "writeUint16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16BECodeGenerator, 1 } }, - { "writeUint16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, - { "writeUint32"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32LECodeGenerator, 1 } }, - { "writeUint32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32BECodeGenerator, 1 } }, - { "writeUint32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32LECodeGenerator, 1 } }, - { "writeBigUint64BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteBigUInt64BECodeGenerator, 1 } }, - { "writeBigUint64LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteBigUInt64LECodeGenerator, 1 } }, }; +#define ALIAS(to, from) \ + do { \ + auto original_ident = Identifier::fromString(vm, ASCIILiteral::fromLiteralUnsafe(from)); \ + auto original = this->getDirect(vm, original_ident); \ + auto alias_ident = Identifier::fromString(vm, ASCIILiteral::fromLiteralUnsafe(to)); \ + this->putDirect(vm, alias_ident, original, PropertyAttribute::Builtin | 0); \ + } while (false); + void JSBufferPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalThis) { Base::finishCreation(vm); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); reifyStaticProperties(vm, JSBuffer::info(), JSBufferPrototypeTableValues, *this); + + ALIAS("readUintBE", "readUIntBE"); + ALIAS("readUintLE", "readUIntLE"); + ALIAS("readUint8", "readUInt8"); + ALIAS("readUint16BE", "readUInt16BE"); + ALIAS("readUint16LE", "readUInt16LE"); + ALIAS("readUint32BE", "readUInt32BE"); + ALIAS("readUint32LE", "readUInt32LE"); + ALIAS("readBigUint64BE", "readBigUInt64BE"); + ALIAS("readBigUint64LE", "readBigUInt64LE"); + + ALIAS("writeUintBE", "writeUIntBE"); + ALIAS("writeUintLE", "writeUIntLE"); + ALIAS("writeUint8", "writeUInt8"); + ALIAS("writeUint16", "writeUInt16"); + ALIAS("writeUint16BE", "writeUInt16BE"); + ALIAS("writeUint16LE", "writeUInt16LE"); + ALIAS("writeUint32", "writeUInt32"); + ALIAS("writeUint32BE", "writeUInt32BE"); + ALIAS("writeUint32LE", "writeUInt32LE"); + ALIAS("writeBigUint64BE", "writeBigUInt64BE"); + ALIAS("writeBigUint64LE", "writeBigUInt64LE"); } const ClassInfo JSBufferPrototype::s_info = { @@ -2263,6 +2244,7 @@ const ClassInfo JSBufferPrototype::s_info = { from JSBuiltin Builtin|Function 1 isBuffer JSBuiltin Builtin|Function 1 isEncoding jsBufferConstructorFunction_isEncoding Function 1 + poolSize jsBufferConstructorPoolSize PropertyCallback @end */ #include "JSBuffer.lut.h" @@ -2311,6 +2293,17 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi if (distinguishingArg.isAnyInt()) { throwScope.release(); return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()); + } else if (distinguishingArg.isNumber()) { + double lengthDouble = distinguishingArg.toIntegerWithTruncation(lexicalGlobalObject); + if (UNLIKELY(lengthDouble < 0 || lengthDouble > MAX_ARRAY_BUFFER_SIZE || lengthDouble != lengthDouble)) { + return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "size"_s, 0, MAX_ARRAY_BUFFER_SIZE, distinguishingArg); + } + return JSBuffer__bufferFromLength(lexicalGlobalObject, lengthDouble); + } else if (distinguishingArg.isUndefinedOrNull() || distinguishingArg.isBoolean()) { + auto arg_string = distinguishingArg.toWTFString(globalObject); + auto message = makeString("The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received "_s, arg_string); + throwTypeError(lexicalGlobalObject, throwScope, message); + return {}; } else if (distinguishingArg.isCell()) { auto type = distinguishingArg.asCell()->type(); diff --git a/src/bun.js/bindings/JSBufferEncodingType.cpp b/src/bun.js/bindings/JSBufferEncodingType.cpp index 336d7cfdda2fd..7002b7ca0812b 100644 --- a/src/bun.js/bindings/JSBufferEncodingType.cpp +++ b/src/bun.js/bindings/JSBufferEncodingType.cpp @@ -62,9 +62,7 @@ template<> std::optional parseEnumerationvalue(&lexicalGlobalObject); switch (encoding.length()) { - case 0: { - return BufferEncodingType::utf8; - } + case 0: case 1: case 2: { return std::nullopt; diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp new file mode 100644 index 0000000000000..6892aedec925a --- /dev/null +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -0,0 +1,99 @@ +#include "root.h" + +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include "JavaScriptCore/JSCJSValue.h" +#include "JavaScriptCore/ExceptionScope.h" +#include +#include + +using namespace JSC; + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInteger, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + auto min = callFrame->argument(2); + auto max = callFrame->argument(3); + + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (min.isUndefined()) min = jsNumber(-9007199254740991); // Number.MIN_SAFE_INTEGER + if (max.isUndefined()) max = jsNumber(9007199254740991); // Number.MAX_SAFE_INTEGER + if (!min.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "min"_s, "number"_s, value); + if (!max.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "max"_s, "number"_s, value); + + auto value_num = value.asNumber(); + auto min_num = min.asNumber(); + auto max_num = max.asNumber(); + max_num = std::max(min_num, max_num); + + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (value_num < min_num || value_num > max_num) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + + return JSValue::encode(jsUndefined()); +} + +// +// + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateIntRange, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + auto min = callFrame->argument(2); + auto max = callFrame->argument(3); + + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (!min.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "min"_s, "number"_s, value); + if (!max.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "max"_s, "number"_s, value); + + auto value_num = value.asNumber(); + auto min_num = min.asNumber(); + auto max_num = max.asNumber(); + + if (value_num < min_num || value_num > max_num || std::isinf(value_num)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBounds, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + auto min = callFrame->argument(2); + auto byteOffset = callFrame->argument(3); + auto byteLength = callFrame->argument(4); + auto width = callFrame->argument(5); + + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (!byteLength.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "byteLength"_s, "number"_s, byteLength); + if (!byteOffset.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "byteOffset"_s, "number"_s, byteOffset); + if (!min.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "min"_s, "number"_s, min); + if (!width.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "width"_s, "number"_s, width); + + auto value_num = value.asNumber(); + auto min_num = min.asNumber(); + auto width_num = width.asNumber(); + auto byteLength_num = byteLength.asNumber(); + auto byteOffset_num = byteOffset.asNumber(); + + auto max_num = byteLength_num - byteOffset_num - width_num; + max_num = std::max(min_num, max_num); + + if (std::isinf(value_num)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (byteOffset_num + value_num > byteLength_num || byteOffset_num + value_num + width_num - 1 > byteLength_num) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(scope, globalObject); + if (value_num < min_num || value_num > max_num) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + + return JSValue::encode(jsUndefined()); +} diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h new file mode 100644 index 0000000000000..6efe134659b5a --- /dev/null +++ b/src/bun.js/bindings/NodeValidator.h @@ -0,0 +1,13 @@ +#include "root.h" + +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include "JavaScriptCore/JSCJSValue.h" + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInteger, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); + +// +// + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBounds, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateIntRange, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); diff --git a/src/bun.js/modules/NodeBufferModule.h b/src/bun.js/modules/NodeBufferModule.h index b35dec34708b9..d7408973b0301 100644 --- a/src/bun.js/modules/NodeBufferModule.h +++ b/src/bun.js/modules/NodeBufferModule.h @@ -46,9 +46,8 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_isUtf8, } if (UNLIKELY(impl->isDetached())) { - throwTypeError(lexicalGlobalObject, throwScope, - "ArrayBuffer is detached"_s); - return JSValue::encode({}); + return Bun::ERR::INVALID_STATE(throwScope, lexicalGlobalObject, + "Cannot validate on a detached buffer"_s); } byteLength = impl->byteLength(); @@ -98,9 +97,8 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_isAscii, JSC::jsDynamicCast(buffer)) { auto *impl = arrayBuffer->impl(); if (UNLIKELY(impl->isDetached())) { - throwTypeError(lexicalGlobalObject, throwScope, - "ArrayBuffer is detached"_s); - return JSValue::encode({}); + return Bun::ERR::INVALID_STATE(throwScope, lexicalGlobalObject, + "Cannot validate on a detached buffer"_s); } if (!impl) { @@ -169,7 +167,7 @@ DEFINE_NATIVE_MODULE(NodeBuffer) { JSC::jsNumber(MAX_ARRAY_BUFFER_SIZE)); put(JSC::Identifier::fromString(vm, "kStringMaxLength"_s), - JSC::jsNumber(std::numeric_limits().max())); + JSC::jsNumber(WTF::String::MaxLength)); JSC::JSObject *constants = JSC::constructEmptyObject( lexicalGlobalObject, globalObject->objectPrototype(), 2); @@ -177,7 +175,7 @@ DEFINE_NATIVE_MODULE(NodeBuffer) { JSC::jsNumber(MAX_ARRAY_BUFFER_SIZE)); constants->putDirect(vm, JSC::Identifier::fromString(vm, "MAX_STRING_LENGTH"_s), - JSC::jsNumber(std::numeric_limits().max())); + JSC::jsNumber(WTF::String::MaxLength)); put(JSC::Identifier::fromString(vm, "constants"_s), constants); diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 69615d8dcc8b8..665dc9da51b37 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -29,6 +29,11 @@ export function from(items) { } } } + if (typeof items === "object") { + if (items.type === "Buffer" && Array.isArray(items.data)) { + return new $Buffer(items.data); + } + } var arrayLike = $toObject( items, diff --git a/src/js/builtins/JSBufferPrototype.ts b/src/js/builtins/JSBufferPrototype.ts index 986e0992ba321..a990fd91afbfc 100644 --- a/src/js/builtins/JSBufferPrototype.ts +++ b/src/js/builtins/JSBufferPrototype.ts @@ -9,45 +9,79 @@ interface BufferExt extends Buffer { } export function setBigUint64(this: BufferExt, offset, value, le) { + if (offset === undefined) offset = 0; return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64( offset, value, le, ); } + export function readInt8(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt8(offset); } + export function readUInt8(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint8(offset); } + export function readInt16LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, true); } + export function readInt16BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, false); } + export function readUInt16LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, true); } + export function readUInt16BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, false); } + export function readInt32LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, true); } + export function readInt32BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, false); } + export function readUInt32LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, true); } + export function readUInt32BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, false); } export function readIntLE(this: BufferExt, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { return view.getInt8(offset); @@ -71,10 +105,13 @@ export function readIntLE(this: BufferExt, offset, byteLength) { return (last | ((last & (2 ** 15)) * 0x1fffe)) * 2 ** 32 + view.getUint32(offset, true); } } - throw new RangeError("byteLength must be >= 1 and <= 6"); } + export function readIntBE(this: BufferExt, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { return view.getInt8(offset); @@ -98,10 +135,13 @@ export function readIntBE(this: BufferExt, offset, byteLength) { return (last | ((last & (2 ** 15)) * 0x1fffe)) * 2 ** 32 + view.getUint32(offset + 2, false); } } - throw new RangeError("byteLength must be >= 1 and <= 6"); } + export function readUIntLE(this: BufferExt, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { return view.getUint8(offset); @@ -122,10 +162,13 @@ export function readUIntLE(this: BufferExt, offset, byteLength) { return view.getUint16(offset + 4, true) * 2 ** 32 + view.getUint32(offset, true); } } - throw new RangeError("byteLength must be >= 1 and <= 6"); } + export function readUIntBE(this: BufferExt, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { return view.getUint8(offset); @@ -146,77 +189,151 @@ export function readUIntBE(this: BufferExt, offset, byteLength) { return view.getUint16(offset, false) * 2 ** 32 + view.getUint32(offset + 2, false); } } - throw new RangeError("byteLength must be >= 1 and <= 6"); } export function readFloatLE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, true); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateBounds(offset, "offset", 0, _byteOffset, _byteLength, 4); + return (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).getFloat32(offset, true); } + export function readFloatBE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, false); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateBounds(offset, "offset", 0, _byteOffset, _byteLength, 4); + return (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).getFloat32(offset, false); } + export function readDoubleLE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset, true); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateBounds(offset, "offset", 0, _byteOffset, _byteLength, 8); + return (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).getFloat64(offset, true); } + export function readDoubleBE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset, false); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateBounds(offset, "offset", 0, _byteOffset, _byteLength, 8); + return (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).getFloat64(offset, false); } + export function readBigInt64LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, true); } + export function readBigInt64BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, false); } + export function readBigUInt64LE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, true); } + export function readBigUInt64BE(this: BufferExt, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, false); } export function writeInt8(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", -128, 127); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt8(offset, value); return offset + 1; } + export function writeUInt8(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", 0, 255); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint8(offset, value); return offset + 1; } + export function writeInt16LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", -32768, 32767); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value, true); return offset + 2; } + export function writeInt16BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", -32768, 32767); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value, false); return offset + 2; } + export function writeUInt16LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", 0, 65535); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value, true); return offset + 2; } + export function writeUInt16BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", 0, 65535); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value, false); return offset + 2; } + export function writeInt32LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", -2147483648, 2147483647); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value, true); return offset + 4; } + export function writeInt32BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", -2147483648, 2147483647); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value, false); return offset + 4; } + export function writeUInt32LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", 0, 4294967295); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value, true); return offset + 4; } + export function writeUInt32BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(value, "value", 0, 4294967295); + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value, false); return offset + 4; } export function writeIntLE(this: BufferExt, value, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const bitLength = byteLength * 8; + require("internal/validators").validateIntRange(value, "value", -(2 ** (bitLength - 1)), 2 ** (bitLength - 1) - 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { view.setInt8(offset, value); @@ -245,14 +362,17 @@ export function writeIntLE(this: BufferExt, value, offset, byteLength) { view.setInt16(offset + 4, Math.floor(value * 2 ** -32), true); break; } - default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); - } } return offset + byteLength; } + export function writeIntBE(this: BufferExt, value, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const bitLength = byteLength * 8; + require("internal/validators").validateIntRange(value, "value", -(2 ** (bitLength - 1)), 2 ** (bitLength - 1) - 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { view.setInt8(offset, value); @@ -281,14 +401,17 @@ export function writeIntBE(this: BufferExt, value, offset, byteLength) { view.setInt16(offset, Math.floor(value * 2 ** -32), false); break; } - default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); - } } return offset + byteLength; } + export function writeUIntLE(this: BufferExt, value, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const bitLength = byteLength * 8; + require("internal/validators").validateIntRange(value, "value", 0, 2 ** bitLength - 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { view.setUint8(offset, value); @@ -317,14 +440,17 @@ export function writeUIntLE(this: BufferExt, value, offset, byteLength) { view.setUint16(offset + 4, Math.floor(value * 2 ** -32), true); break; } - default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); - } } return offset + byteLength; } + export function writeUIntBE(this: BufferExt, value, offset, byteLength) { - const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + require("internal/validators").validateIntRange(byteLength, "byteLength", 1, 6); + const _byteLength = this.byteLength; + require("internal/validators").validateIntRange(offset, "offset", 0, _byteLength - byteLength); + const bitLength = byteLength * 8; + require("internal/validators").validateIntRange(value, "value", 0, 2 ** bitLength - 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, _byteLength)); switch (byteLength) { case 1: { view.setUint8(offset, value); @@ -353,49 +479,74 @@ export function writeUIntBE(this: BufferExt, value, offset, byteLength) { view.setUint16(offset, Math.floor(value * 2 ** -32), false); break; } - default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); - } } return offset + byteLength; } export function writeFloatLE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, true); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateInteger(offset, "offset", 0, _byteLength - _byteOffset - 4); + if (_byteOffset + offset + 4 > _byteLength) throw require("internal/errors").ERR_BUFFER_OUT_OF_BOUNDS(); + (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).setFloat32(offset, value, true); return offset + 4; } export function writeFloatBE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, false); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateInteger(offset, "offset", 0, _byteLength - _byteOffset - 4); + if (_byteOffset + offset + 4 > _byteLength) throw require("internal/errors").ERR_BUFFER_OUT_OF_BOUNDS(); + (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).setFloat32(offset, value, false); return offset + 4; } export function writeDoubleLE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value, true); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateInteger(offset, "offset", 0, _byteLength - _byteOffset - 8); + if (_byteOffset + offset + 8 > _byteLength) throw require("internal/errors").ERR_BUFFER_OUT_OF_BOUNDS(); + (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).setFloat64(offset, value, true); return offset + 8; } export function writeDoubleBE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value, false); + if (offset === undefined) offset = 0; + const _byteOffset = this.byteOffset; + const _byteLength = this.byteLength; + require("internal/validators").validateInteger(offset, "offset", 0, _byteLength - _byteOffset - 8); + if (_byteOffset + offset + 8 > _byteLength) throw require("internal/errors").ERR_BUFFER_OUT_OF_BOUNDS(); + (this.$dataView ||= new DataView(this.buffer, _byteOffset, _byteLength)).setFloat64(offset, value, false); return offset + 8; } export function writeBigInt64LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value, true); return offset + 8; } export function writeBigInt64BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value, false); return offset + 8; } export function writeBigUInt64LE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, true); return offset + 8; } export function writeBigUInt64BE(this: BufferExt, value, offset) { + if (offset === undefined) offset = 0; + require("internal/validators").validateInteger(offset, "offset", 0); (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, false); return offset + 8; } diff --git a/src/js/internal/errors.ts b/src/js/internal/errors.ts index dedce8f38ad62..6f6d73297dec7 100644 --- a/src/js/internal/errors.ts +++ b/src/js/internal/errors.ts @@ -6,4 +6,5 @@ export default { ERR_IPC_CHANNEL_CLOSED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_IPC_CHANNEL_CLOSED", 0), ERR_SOCKET_BAD_TYPE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_SOCKET_BAD_TYPE", 0), ERR_INVALID_PROTOCOL: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_INVALID_PROTOCOL", 0), + ERR_BUFFER_OUT_OF_BOUNDS: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BUFFER_OUT_OF_BOUNDS", 0), }; diff --git a/src/js/internal/validators.ts b/src/js/internal/validators.ts new file mode 100644 index 0000000000000..4aca421a663f5 --- /dev/null +++ b/src/js/internal/validators.ts @@ -0,0 +1,6 @@ +export default { + validateIntRange: $newCppFunction("NodeValidator.cpp", "jsFunction_validateIntRange", 0), + validateBounds: $newCppFunction("NodeValidator.cpp", "jsFunction_validateBounds", 0), + + validateInteger: $newCppFunction("NodeValidator.cpp", "jsFunction_validateInteger", 0), +}; diff --git a/src/js/node/readline.ts b/src/js/node/readline.ts index 0c9ae8b2d0c0b..e40725bf8380a 100644 --- a/src/js/node/readline.ts +++ b/src/js/node/readline.ts @@ -27,7 +27,10 @@ // ---------------------------------------------------------------------------- const EventEmitter = require("node:events"); const { StringDecoder } = require("node:string_decoder"); +const { validateInteger } = require("internal/validators"); + const internalGetStringWidth = $newZigFunction("string.zig", "String.jsGetStringWidth", 1); + const ObjectGetPrototypeOf = Object.getPrototypeOf; const ObjectGetOwnPropertyDescriptors = Object.getOwnPropertyDescriptors; const ObjectValues = Object.values; @@ -383,20 +386,6 @@ function validateObject(value, name, options = null) { } } -/** - * @callback validateInteger - * @param {*} value - * @param {string} name - * @param {number} [min] - * @param {number} [max] - * @returns {asserts value is number} - */ -function validateInteger(value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) { - if (typeof value !== "number") throw $ERR_INVALID_ARG_TYPE(name, "number", value); - if (!NumberIsInteger(value)) throw new ERR_OUT_OF_RANGE(name, "an integer", value); - if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); -} - /** * @callback validateUint32 * @param {*} value diff --git a/src/js/node/stream.ts b/src/js/node/stream.ts index ec669f047c51e..893cf821feee5 100644 --- a/src/js/node/stream.ts +++ b/src/js/node/stream.ts @@ -29,6 +29,7 @@ const kPaused = Symbol("kPaused"); const StringDecoder = require("node:string_decoder").StringDecoder; const transferToNativeReadable = $newCppFunction("ReadableStream.cpp", "jsFunctionTransferToNativeReadableStream", 1); const { kAutoDestroyed } = require("internal/shared"); +const { validateInteger } = require("internal/validators"); const ObjectSetPrototypeOf = Object.setPrototypeOf; @@ -678,11 +679,6 @@ var require_validators = __commonJS({ validateInt32(value, name, 0, 2 ** 32 - 1); return value; } - var validateInteger = hideStackFrames((value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) => { - if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value); - if (!NumberIsInteger(value)) throw new ERR_OUT_OF_RANGE(name, "an integer", value); - if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); - }); var validateInt32 = hideStackFrames((value, name, min = -2147483648, max = 2147483647) => { if (typeof value !== "number") { throw new ERR_INVALID_ARG_TYPE(name, "number", value); diff --git a/src/js/node/tty.ts b/src/js/node/tty.ts index 62a3756493730..6bd0611a32fcf 100644 --- a/src/js/node/tty.ts +++ b/src/js/node/tty.ts @@ -4,6 +4,8 @@ const { getWindowSize: _getWindowSize, } = $cpp("ProcessBindingTTYWrap.cpp", "createBunTTYFunctions"); +const { validateInteger } = require("internal/validators"); + // primordials const NumberIsInteger = Number.isInteger; @@ -321,12 +323,6 @@ Object.defineProperty(WriteStream, "prototype", { configurable: true, }); -var validateInteger = (value, name, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => { - if (typeof value !== "number") throw ERR_INVALID_ARG_TYPE(name, "number", value); - if (!NumberIsInteger(value)) throw ERR_OUT_OF_RANGE(name, "an integer", value); - if (value < min || value > max) throw ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); -}; - export default { ReadStream, WriteStream, isatty }; function ERR_INVALID_ARG_TYPE(name, type, value) { diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 17f25ec067a9b..872391805152b 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -2003,9 +2003,9 @@ for (let withOverridenBufferWrite of [false, true]) { it("constants", () => { expect(BufferModule.constants.MAX_LENGTH).toBe(4294967296); - expect(BufferModule.constants.MAX_STRING_LENGTH).toBe(4294967295); + expect(BufferModule.constants.MAX_STRING_LENGTH).toBe(2147483647); expect(BufferModule.default.constants.MAX_LENGTH).toBe(4294967296); - expect(BufferModule.default.constants.MAX_STRING_LENGTH).toBe(4294967295); + expect(BufferModule.default.constants.MAX_STRING_LENGTH).toBe(2147483647); }); it("File", () => { diff --git a/test/js/node/test/parallel/buffer-bytelength.test.js b/test/js/node/test/parallel/buffer-bytelength.test.js new file mode 100644 index 0000000000000..5934db1dc8dad --- /dev/null +++ b/test/js/node/test/parallel/buffer-bytelength.test.js @@ -0,0 +1,131 @@ +//#FILE: test-buffer-bytelength.js +//#SHA1: bcc75ad2f868ac9414c789c29f23ee9c806c749d +//----------------- +"use strict"; + +const SlowBuffer = require("buffer").SlowBuffer; +const vm = require("vm"); + +test("Buffer.byteLength with invalid arguments", () => { + [[32, "latin1"], [NaN, "utf8"], [{}, "latin1"], []].forEach(args => { + expect(() => Buffer.byteLength(...args)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.stringContaining( + 'The "string" argument must be of type string or an instance of Buffer or ArrayBuffer.', + ), + }), + ); + }); +}); + +test("ArrayBuffer.isView for various Buffer types", () => { + expect(ArrayBuffer.isView(new Buffer(10))).toBe(true); + expect(ArrayBuffer.isView(new SlowBuffer(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.alloc(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.allocUnsafe(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.from(""))).toBe(true); +}); + +test("Buffer.byteLength for various buffer types", () => { + const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); + expect(Buffer.byteLength(incomplete)).toBe(5); + + const ascii = Buffer.from("abc"); + expect(Buffer.byteLength(ascii)).toBe(3); + + const buffer = new ArrayBuffer(8); + expect(Buffer.byteLength(buffer)).toBe(8); +}); + +test("Buffer.byteLength for TypedArrays", () => { + expect(Buffer.byteLength(new Int8Array(8))).toBe(8); + expect(Buffer.byteLength(new Uint8Array(8))).toBe(8); + expect(Buffer.byteLength(new Uint8ClampedArray(2))).toBe(2); + expect(Buffer.byteLength(new Int16Array(8))).toBe(16); + expect(Buffer.byteLength(new Uint16Array(8))).toBe(16); + expect(Buffer.byteLength(new Int32Array(8))).toBe(32); + expect(Buffer.byteLength(new Uint32Array(8))).toBe(32); + expect(Buffer.byteLength(new Float32Array(8))).toBe(32); + expect(Buffer.byteLength(new Float64Array(8))).toBe(64); +}); + +test("Buffer.byteLength for DataView", () => { + const dv = new DataView(new ArrayBuffer(2)); + expect(Buffer.byteLength(dv)).toBe(2); +}); + +test("Buffer.byteLength for zero length string", () => { + expect(Buffer.byteLength("", "ascii")).toBe(0); + expect(Buffer.byteLength("", "HeX")).toBe(0); +}); + +test("Buffer.byteLength for utf8", () => { + expect(Buffer.byteLength("∑éllö wørl∂!", "utf-8")).toBe(19); + expect(Buffer.byteLength("κλμνξο", "utf8")).toBe(12); + expect(Buffer.byteLength("挵挶挷挸挹", "utf-8")).toBe(15); + expect(Buffer.byteLength("𠝹𠱓𠱸", "UTF8")).toBe(12); + expect(Buffer.byteLength("hey there")).toBe(9); + expect(Buffer.byteLength("𠱸挶νξ#xx :)")).toBe(17); + expect(Buffer.byteLength("hello world", "")).toBe(11); + expect(Buffer.byteLength("hello world", "abc")).toBe(11); + expect(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding")).toBe(10); +}); + +test("Buffer.byteLength for base64", () => { + expect(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64")).toBe(11); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64")).toBe(11); + expect(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64")).toBe(14); + expect(Buffer.byteLength("aGkk", "base64")).toBe(3); + expect(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64")).toBe(25); +}); + +test("Buffer.byteLength for base64url", () => { + expect(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url")).toBe(11); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL")).toBe(11); + expect(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url")).toBe(14); + expect(Buffer.byteLength("aGkk", "base64url")).toBe(3); + expect(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url")).toBe(25); +}); + +test("Buffer.byteLength for special padding", () => { + expect(Buffer.byteLength("aaa=", "base64")).toBe(2); + expect(Buffer.byteLength("aaaa==", "base64")).toBe(3); + expect(Buffer.byteLength("aaa=", "base64url")).toBe(2); + expect(Buffer.byteLength("aaaa==", "base64url")).toBe(3); +}); + +test("Buffer.byteLength for various encodings", () => { + expect(Buffer.byteLength("Il était tué")).toBe(14); + expect(Buffer.byteLength("Il était tué", "utf8")).toBe(14); + + ["ascii", "latin1", "binary"] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + expect(Buffer.byteLength("Il était tué", encoding)).toBe(12); + }); + + ["ucs2", "ucs-2", "utf16le", "utf-16le"] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + expect(Buffer.byteLength("Il était tué", encoding)).toBe(24); + }); +}); + +test("Buffer.byteLength for ArrayBuffer from different context", () => { + const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); + expect(Buffer.byteLength(arrayBuf)).toBe(0); +}); + +test("Buffer.byteLength for invalid encodings", () => { + for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + expect(Buffer.isEncoding(encoding)).toBe(false); + expect(Buffer.byteLength("foo", encoding)).toBe(Buffer.byteLength("foo", "utf8")); + } +}); + +//<#END_FILE: test-buffer-bytelength.js diff --git a/test/js/node/test/parallel/buffer-compare-offset.test.js b/test/js/node/test/parallel/buffer-compare-offset.test.js new file mode 100644 index 0000000000000..037fd5de9c52c --- /dev/null +++ b/test/js/node/test/parallel/buffer-compare-offset.test.js @@ -0,0 +1,93 @@ +//#FILE: test-buffer-compare-offset.js +//#SHA1: 460e187ac1a40db0dbc00801ad68f1272d27c3cd +//----------------- +"use strict"; + +describe("Buffer.compare with offset", () => { + const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + const b = Buffer.from([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]); + + test("basic comparison", () => { + expect(a.compare(b)).toBe(-1); + }); + + test("comparison with default arguments", () => { + expect(a.compare(b, 0)).toBe(-1); + expect(() => a.compare(b, "0")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + expect(a.compare(b, undefined)).toBe(-1); + }); + + test("comparison with specified ranges", () => { + expect(a.compare(b, 0, undefined, 0)).toBe(-1); + expect(a.compare(b, 0, 0, 0)).toBe(1); + expect(() => a.compare(b, 0, "0", "0")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + expect(a.compare(b, 6, 10)).toBe(1); + expect(a.compare(b, 6, 10, 0, 0)).toBe(-1); + expect(a.compare(b, 0, 0, 0, 0)).toBe(0); + expect(a.compare(b, 1, 1, 2, 2)).toBe(0); + expect(a.compare(b, 0, 5, 4)).toBe(1); + expect(a.compare(b, 5, undefined, 1)).toBe(1); + expect(a.compare(b, 2, 4, 2)).toBe(-1); + expect(a.compare(b, 0, 7, 4)).toBe(-1); + expect(a.compare(b, 0, 7, 4, 6)).toBe(-1); + }); + + test("invalid arguments", () => { + expect(() => a.compare(b, 0, null)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + expect(() => a.compare(b, 0, { valueOf: () => 5 })).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + expect(() => a.compare(b, Infinity, -Infinity)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); + expect(a.compare(b, 0xff)).toBe(1); + expect(() => a.compare(b, "0xff")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + expect(() => a.compare(b, 0, "0xff")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + + test("out of range arguments", () => { + const oor = expect.objectContaining({ code: "ERR_OUT_OF_RANGE" }); + expect(() => a.compare(b, 0, 100, 0)).toThrow(oor); + expect(() => a.compare(b, 0, 1, 0, 100)).toThrow(oor); + expect(() => a.compare(b, -1)).toThrow(oor); + expect(() => a.compare(b, 0, Infinity)).toThrow(oor); + expect(() => a.compare(b, 0, 1, -1)).toThrow(oor); + expect(() => a.compare(b, -Infinity, Infinity)).toThrow(oor); + }); + + test("missing target argument", () => { + expect(() => a.compare()).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.stringContaining('The "target" argument must be an instance of Buffer or Uint8Array'), + }), + ); + }); +}); + +//<#END_FILE: test-buffer-compare-offset.js diff --git a/test/js/node/test/parallel/buffer-compare.test.js b/test/js/node/test/parallel/buffer-compare.test.js new file mode 100644 index 0000000000000..9f6d0c70be720 --- /dev/null +++ b/test/js/node/test/parallel/buffer-compare.test.js @@ -0,0 +1,55 @@ +//#FILE: test-buffer-compare.js +//#SHA1: eab68d7262240af3d53eabedb0e7a515b2d84adf +//----------------- +"use strict"; + +test("Buffer compare", () => { + const b = Buffer.alloc(1, "a"); + const c = Buffer.alloc(1, "c"); + const d = Buffer.alloc(2, "aa"); + const e = new Uint8Array([0x61, 0x61]); // ASCII 'aa', same as d + + expect(b.compare(c)).toBe(-1); + expect(c.compare(d)).toBe(1); + expect(d.compare(b)).toBe(1); + expect(d.compare(e)).toBe(0); + expect(b.compare(d)).toBe(-1); + expect(b.compare(b)).toBe(0); + + expect(Buffer.compare(b, c)).toBe(-1); + expect(Buffer.compare(c, d)).toBe(1); + expect(Buffer.compare(d, b)).toBe(1); + expect(Buffer.compare(b, d)).toBe(-1); + expect(Buffer.compare(c, c)).toBe(0); + expect(Buffer.compare(e, e)).toBe(0); + expect(Buffer.compare(d, e)).toBe(0); + expect(Buffer.compare(d, b)).toBe(1); + + expect(Buffer.compare(Buffer.alloc(0), Buffer.alloc(0))).toBe(0); + expect(Buffer.compare(Buffer.alloc(0), Buffer.alloc(1))).toBe(-1); + expect(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0))).toBe(1); + + expect(() => Buffer.compare(Buffer.alloc(1), "abc")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.stringContaining('The "buf2" argument must be an instance of Buffer or Uint8Array.'), + }), + ); + + expect(() => Buffer.compare("abc", Buffer.alloc(1))).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.stringContaining('The "buf1" argument must be an instance of Buffer or Uint8Array.'), + }), + ); + + expect(() => Buffer.alloc(1).compare("abc")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.stringContaining('The "target" argument must be an instance of Buffer or Uint8Array.'), + }), + ); +}); + +//<#END_FILE: test-buffer-compare.js diff --git a/test/js/node/test/parallel/buffer-constants.test.js b/test/js/node/test/parallel/buffer-constants.test.js new file mode 100644 index 0000000000000..5c7c8a18d1688 --- /dev/null +++ b/test/js/node/test/parallel/buffer-constants.test.js @@ -0,0 +1,28 @@ +//#FILE: test-buffer-constants.js +//#SHA1: a5818d34d1588306e48d574ec76b69b2ee4dc51c +//----------------- +"use strict"; + +const { kMaxLength, kStringMaxLength } = require("buffer"); +const { MAX_LENGTH, MAX_STRING_LENGTH } = require("buffer").constants; + +test("Buffer constants", () => { + expect(typeof MAX_LENGTH).toBe("number"); + expect(typeof MAX_STRING_LENGTH).toBe("number"); + expect(MAX_STRING_LENGTH).toBeLessThanOrEqual(MAX_LENGTH); + + expect(() => " ".repeat(MAX_STRING_LENGTH + 1)).toThrow( + expect.objectContaining({ + name: "RangeError", + message: expect.any(String), + }), + ); + + expect(() => " ".repeat(MAX_STRING_LENGTH)).not.toThrow(); + + // Legacy values match: + expect(kMaxLength).toBe(MAX_LENGTH); + expect(kStringMaxLength).toBe(MAX_STRING_LENGTH); +}); + +//<#END_FILE: test-buffer-constants.js diff --git a/test/js/node/test/parallel/buffer-fill.test.js b/test/js/node/test/parallel/buffer-fill.test.js new file mode 100644 index 0000000000000..1fa22e97899f2 --- /dev/null +++ b/test/js/node/test/parallel/buffer-fill.test.js @@ -0,0 +1,444 @@ +//#FILE: test-buffer-fill.js +//#SHA1: 983940aa8a47c4d0985c2c4b4d1bc323a4e7d0f5 +//----------------- +"use strict"; + +const SIZE = 28; + +let buf1, buf2; + +beforeEach(() => { + buf1 = Buffer.allocUnsafe(SIZE); + buf2 = Buffer.allocUnsafe(SIZE); +}); + +// Helper functions +function genBuffer(size, args) { + const b = Buffer.allocUnsafe(size); + return b.fill(0).fill.apply(b, args); +} + +function bufReset() { + buf1.fill(0); + buf2.fill(0); +} + +function writeToFill(string, offset, end, encoding) { + if (typeof offset === "string") { + encoding = offset; + offset = 0; + end = buf2.length; + } else if (typeof end === "string") { + encoding = end; + end = buf2.length; + } else if (end === undefined) { + end = buf2.length; + } + + if (offset < 0 || end > buf2.length) throw new RangeError("ERR_OUT_OF_RANGE"); + + if (end <= offset) return buf2; + + offset >>>= 0; + end >>>= 0; + expect(offset).toBeLessThanOrEqual(buf2.length); + + const length = end - offset < 0 ? 0 : end - offset; + + let wasZero = false; + do { + const written = buf2.write(string, offset, length, encoding); + offset += written; + if (written === 0) { + if (wasZero) throw new Error("Could not write all data to Buffer"); + else wasZero = true; + } + } while (offset < buf2.length); + + return buf2; +} + +function testBufs(string, offset, length, encoding) { + bufReset(); + buf1.fill.apply(buf1, arguments); + expect(buf1.fill.apply(buf1, arguments)).toEqual(writeToFill.apply(null, arguments)); +} + +// Tests +test("Default encoding", () => { + testBufs("abc"); + testBufs("\u0222aa"); + testBufs("a\u0234b\u0235c\u0236"); + testBufs("abc", 4); + testBufs("abc", 5); + testBufs("abc", SIZE); + testBufs("\u0222aa", 2); + testBufs("\u0222aa", 8); + testBufs("a\u0234b\u0235c\u0236", 4); + testBufs("a\u0234b\u0235c\u0236", 12); + testBufs("abc", 4, 1); + testBufs("abc", 5, 1); + testBufs("\u0222aa", 8, 1); + testBufs("a\u0234b\u0235c\u0236", 4, 1); + testBufs("a\u0234b\u0235c\u0236", 12, 1); +}); + +test("UTF8 encoding", () => { + testBufs("abc", "utf8"); + testBufs("\u0222aa", "utf8"); + testBufs("a\u0234b\u0235c\u0236", "utf8"); + testBufs("abc", 4, "utf8"); + testBufs("abc", 5, "utf8"); + testBufs("abc", SIZE, "utf8"); + testBufs("\u0222aa", 2, "utf8"); + testBufs("\u0222aa", 8, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 4, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 12, "utf8"); + testBufs("abc", 4, 1, "utf8"); + testBufs("abc", 5, 1, "utf8"); + testBufs("\u0222aa", 8, 1, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "utf8"); + expect(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0]).toBe(0xc8); +}); + +test("BINARY encoding", () => { + testBufs("abc", "binary"); + testBufs("\u0222aa", "binary"); + testBufs("a\u0234b\u0235c\u0236", "binary"); + testBufs("abc", 4, "binary"); + testBufs("abc", 5, "binary"); + testBufs("abc", SIZE, "binary"); + testBufs("\u0222aa", 2, "binary"); + testBufs("\u0222aa", 8, "binary"); + testBufs("a\u0234b\u0235c\u0236", 4, "binary"); + testBufs("a\u0234b\u0235c\u0236", 12, "binary"); + testBufs("abc", 4, 1, "binary"); + testBufs("abc", 5, 1, "binary"); + testBufs("\u0222aa", 8, 1, "binary"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "binary"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "binary"); +}); + +test("LATIN1 encoding", () => { + testBufs("abc", "latin1"); + testBufs("\u0222aa", "latin1"); + testBufs("a\u0234b\u0235c\u0236", "latin1"); + testBufs("abc", 4, "latin1"); + testBufs("abc", 5, "latin1"); + testBufs("abc", SIZE, "latin1"); + testBufs("\u0222aa", 2, "latin1"); + testBufs("\u0222aa", 8, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 4, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 12, "latin1"); + testBufs("abc", 4, 1, "latin1"); + testBufs("abc", 5, 1, "latin1"); + testBufs("\u0222aa", 8, 1, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "latin1"); +}); + +test("UCS2 encoding", () => { + testBufs("abc", "ucs2"); + testBufs("\u0222aa", "ucs2"); + testBufs("a\u0234b\u0235c\u0236", "ucs2"); + testBufs("abc", 4, "ucs2"); + testBufs("abc", SIZE, "ucs2"); + testBufs("\u0222aa", 2, "ucs2"); + testBufs("\u0222aa", 8, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 4, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 12, "ucs2"); + testBufs("abc", 4, 1, "ucs2"); + testBufs("abc", 5, 1, "ucs2"); + testBufs("\u0222aa", 8, 1, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "ucs2"); + expect(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0]).toBe(0x22); +}); + +test("HEX encoding", () => { + testBufs("616263", "hex"); + testBufs("c8a26161", "hex"); + testBufs("61c8b462c8b563c8b6", "hex"); + testBufs("616263", 4, "hex"); + testBufs("616263", 5, "hex"); + testBufs("616263", SIZE, "hex"); + testBufs("c8a26161", 2, "hex"); + testBufs("c8a26161", 8, "hex"); + testBufs("61c8b462c8b563c8b6", 4, "hex"); + testBufs("61c8b462c8b563c8b6", 12, "hex"); + testBufs("616263", 4, 1, "hex"); + testBufs("616263", 5, 1, "hex"); + testBufs("c8a26161", 8, 1, "hex"); + testBufs("61c8b462c8b563c8b6", 4, 1, "hex"); + testBufs("61c8b462c8b563c8b6", 12, 1, "hex"); +}); + +test("Invalid HEX encoding", () => { + expect(() => { + const buf = Buffer.allocUnsafe(SIZE); + buf.fill("yKJh", "hex"); + }).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }), + ); + + expect(() => { + const buf = Buffer.allocUnsafe(SIZE); + buf.fill("\u0222", "hex"); + }).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }), + ); +}); + +test("BASE64 encoding", () => { + testBufs("YWJj", "base64"); + testBufs("yKJhYQ==", "base64"); + testBufs("Yci0Ysi1Y8i2", "base64"); + testBufs("YWJj", 4, "base64"); + testBufs("YWJj", SIZE, "base64"); + testBufs("yKJhYQ==", 2, "base64"); + testBufs("yKJhYQ==", 8, "base64"); + testBufs("Yci0Ysi1Y8i2", 4, "base64"); + testBufs("Yci0Ysi1Y8i2", 12, "base64"); + testBufs("YWJj", 4, 1, "base64"); + testBufs("YWJj", 5, 1, "base64"); + testBufs("yKJhYQ==", 8, 1, "base64"); + testBufs("Yci0Ysi1Y8i2", 4, 1, "base64"); + testBufs("Yci0Ysi1Y8i2", 12, 1, "base64"); +}); + +test("BASE64URL encoding", () => { + testBufs("YWJj", "base64url"); + testBufs("yKJhYQ", "base64url"); + testBufs("Yci0Ysi1Y8i2", "base64url"); + testBufs("YWJj", 4, "base64url"); + testBufs("YWJj", SIZE, "base64url"); + testBufs("yKJhYQ", 2, "base64url"); + testBufs("yKJhYQ", 8, "base64url"); + testBufs("Yci0Ysi1Y8i2", 4, "base64url"); + testBufs("Yci0Ysi1Y8i2", 12, "base64url"); + testBufs("YWJj", 4, 1, "base64url"); + testBufs("YWJj", 5, 1, "base64url"); + testBufs("yKJhYQ", 8, 1, "base64url"); + testBufs("Yci0Ysi1Y8i2", 4, 1, "base64url"); + testBufs("Yci0Ysi1Y8i2", 12, 1, "base64url"); +}); + +test("Buffer fill", () => { + function deepStrictEqualValues(buf, arr) { + for (const [index, value] of buf.entries()) { + expect(value).toBe(arr[index]); + } + } + + const buf2Fill = Buffer.allocUnsafe(1).fill(2); + deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]); + const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex"); + deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]); +}); + +test("Check exceptions", () => { + [ + [0, -1], + [0, 0, buf1.length + 1], + ["", -1], + ["", 0, buf1.length + 1], + ["", 1, -1], + ].forEach(args => { + expect(() => buf1.fill(...args)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); + }); + + expect(() => buf1.fill("a", 0, buf1.length, "node rocks!")).toThrow( + expect.objectContaining({ + code: "ERR_UNKNOWN_ENCODING", + name: "TypeError", + message: `Unknown encoding: "node rocks!"`, + }), + ); + + [ + ["a", 0, 0, NaN], + ["a", 0, 0, false], + ].forEach(args => { + expect(() => buf1.fill(...args)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.stringContaining('The "encoding" argument must be of type string'), + }), + ); + }); + + expect(() => buf1.fill("a", 0, 0, "foo")).toThrow( + expect.objectContaining({ + code: "ERR_UNKNOWN_ENCODING", + name: "TypeError", + message: `Unknown encoding: "foo"`, + }), + ); +}); + +test("Out of range errors", () => { + expect(() => Buffer.allocUnsafe(8).fill("a", -1)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); + expect(() => Buffer.allocUnsafe(8).fill("a", 0, 9)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); +}); + +test("Empty fill", () => { + Buffer.allocUnsafe(8).fill(""); + Buffer.alloc(8, ""); +}); + +test("Buffer allocation and fill", () => { + const buf = Buffer.alloc(64, 10); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(10); + + buf.fill(11, 0, buf.length >> 1); + for (let i = 0; i < buf.length >> 1; i++) expect(buf[i]).toBe(11); + for (let i = (buf.length >> 1) + 1; i < buf.length; i++) expect(buf[i]).toBe(10); + + buf.fill("h"); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe("h".charCodeAt(0)); + + buf.fill(0); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(0); + + buf.fill(null); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(0); + + buf.fill(1, 16, 32); + for (let i = 0; i < 16; i++) expect(buf[i]).toBe(0); + for (let i = 16; i < 32; i++) expect(buf[i]).toBe(1); + for (let i = 32; i < buf.length; i++) expect(buf[i]).toBe(0); +}); + +test("Buffer fill with string", () => { + const buf = Buffer.alloc(10, "abc"); + expect(buf.toString()).toBe("abcabcabca"); + buf.fill("է"); + expect(buf.toString()).toBe("էէէէէ"); +}); + +test("Buffer fill with invalid end", () => { + expect(() => { + const end = { + [Symbol.toPrimitive]() { + return 1; + }, + }; + Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); + }).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.stringContaining('The "end" argument must be of type number'), + }), + ); +}); + +test.todo("Buffer fill with invalid length", () => { + expect(() => { + const buf = Buffer.from("w00t"); + Object.defineProperty(buf, "length", { + value: 1337, + enumerable: true, + }); + buf.fill(""); + }).toThrow( + expect.objectContaining({ + code: "ERR_BUFFER_OUT_OF_BOUNDS", + name: "RangeError", + message: "Attempt to access memory outside buffer bounds", + }), + ); +}); + +test("Buffer fill with utf16le encoding", () => { + expect(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le")).toEqual( + Buffer.from("61006200610062006100620061006200", "hex"), + ); + + expect(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le")).toEqual( + Buffer.from("610062006100620061006200610062", "hex"), + ); + + expect(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le")).toEqual( + Buffer.from("61006200610062006100620061006200", "hex"), + ); + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf16le")).toEqual( + Buffer.from("61006100610061006100610061006100", "hex"), + ); + + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le")).toBe("a".repeat(8)); + expect(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1")).toBe("a".repeat(16)); + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8")).toBe("a".repeat(16)); + + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le")).toBe("Љ".repeat(8)); + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1")).toBe("\t".repeat(16)); + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8")).toBe("Љ".repeat(8)); +}); + +test("Buffer fill with invalid hex encoding", () => { + expect(() => { + const buf = Buffer.from("a".repeat(1000)); + buf.fill("This is not correctly encoded", "hex"); + }).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }), + ); +}); + +test("alloc with no fill", () => { + const buf = Buffer.alloc(5); + expect(buf.toString().length).toBe(5); + expect(buf.toString()).toBe("\x00\x00\x00\x00\x00"); +}); + +test("alloc with fill: empty string", () => { + const buf = Buffer.alloc(5, ""); + expect(buf.toString().length).toBe(5); + expect(buf.toString()).toBe("\x00\x00\x00\x00\x00"); +}); + +test("alloc with fill: empty array", () => { + const buf = Buffer.alloc(5, []); + expect(buf.toString().length).toBe(5); + expect(buf.toString()).toBe("\x00\x00\x00\x00\x00"); +}); + +test("alloc with fill: zero", () => { + const buf = Buffer.alloc(5, 0); + expect(buf.toString().length).toBe(5); + expect(buf.toString()).toBe("\x00\x00\x00\x00\x00"); +}); + +test("alloc with fill: buffer", () => { + const bufEmptyBuffer = Buffer.alloc(5, Buffer.alloc(5)); + expect(bufEmptyBuffer.toString().length).toBe(5); + expect(bufEmptyBuffer.toString()).toBe("\x00\x00\x00\x00\x00"); +}); + +//<#END_FILE: test-buffer-fill.js diff --git a/test/js/node/test/parallel/buffer-isascii.test.js b/test/js/node/test/parallel/buffer-isascii.test.js new file mode 100644 index 0000000000000..a8fde2110a2ba --- /dev/null +++ b/test/js/node/test/parallel/buffer-isascii.test.js @@ -0,0 +1,40 @@ +//#FILE: test-buffer-isascii.js +//#SHA1: e49cbd0752feaa8042a90129dfb38610eb002ee6 +//----------------- +"use strict"; + +const { isAscii, Buffer } = require("buffer"); +const { TextEncoder } = require("util"); + +const encoder = new TextEncoder(); + +test("isAscii function", () => { + expect(isAscii(encoder.encode("hello"))).toBe(true); + expect(isAscii(encoder.encode("ğ"))).toBe(false); + expect(isAscii(Buffer.from([]))).toBe(true); +}); + +test("isAscii with invalid inputs", () => { + const invalidInputs = [undefined, "", "hello", false, true, 0, 1, 0n, 1n, Symbol(), () => {}, {}, [], null]; + + invalidInputs.forEach(input => { + expect(() => isAscii(input)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); +}); + +test("isAscii with detached array buffer", () => { + const arrayBuffer = new ArrayBuffer(1024); + structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + + expect(() => isAscii(arrayBuffer)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_STATE", + }), + ); +}); + +//<#END_FILE: test-buffer-isascii.js diff --git a/test/js/node/test/parallel/buffer-isencoding.test.js b/test/js/node/test/parallel/buffer-isencoding.test.js new file mode 100644 index 0000000000000..19443eeb8b5b3 --- /dev/null +++ b/test/js/node/test/parallel/buffer-isencoding.test.js @@ -0,0 +1,37 @@ +//#FILE: test-buffer-isencoding.js +//#SHA1: 438625bd1ca2a23aa8716bea5334f3ac07eb040f +//----------------- +"use strict"; + +describe("Buffer.isEncoding", () => { + const validEncodings = [ + "hex", + "utf8", + "utf-8", + "ascii", + "latin1", + "binary", + "base64", + "base64url", + "ucs2", + "ucs-2", + "utf16le", + "utf-16le", + ]; + + validEncodings.forEach(enc => { + test(`should return true for valid encodings: ${enc}`, () => { + expect(Buffer.isEncoding(enc)).toBe(true); + }); + }); + + const invalidEncodings = ["utf9", "utf-7", "Unicode-FTW", "new gnu gun", false, NaN, {}, Infinity, [], 1, 0, -1, ""]; + + invalidEncodings.forEach(enc => { + test(`should return false for invalid encodings: ${JSON.stringify(enc)}`, () => { + expect(Buffer.isEncoding(enc)).toBe(false); + }); + }); +}); + +//<#END_FILE: test-buffer-isencoding.js diff --git a/test/js/node/test/parallel/buffer-isutf8.test.js b/test/js/node/test/parallel/buffer-isutf8.test.js new file mode 100644 index 0000000000000..fa55fb4c5b0dc --- /dev/null +++ b/test/js/node/test/parallel/buffer-isutf8.test.js @@ -0,0 +1,90 @@ +//#FILE: test-buffer-isutf8.js +//#SHA1: 47438bb1ade853e71f1266144d24188ebe75e714 +//----------------- +"use strict"; + +const { isUtf8, Buffer } = require("buffer"); +const { TextEncoder } = require("util"); + +const encoder = new TextEncoder(); + +test("isUtf8 validation", () => { + expect(isUtf8(encoder.encode("hello"))).toBe(true); + expect(isUtf8(encoder.encode("ğ"))).toBe(true); + expect(isUtf8(Buffer.from([]))).toBe(true); +}); + +// Taken from test/fixtures/wpt/encoding/textdecoder-fatal.any.js +test("invalid UTF-8 sequences", () => { + const invalidSequences = [ + [0xff], // 'invalid code' + [0xc0], // 'ends early' + [0xe0], // 'ends early 2' + [0xc0, 0x00], // 'invalid trail' + [0xc0, 0xc0], // 'invalid trail 2' + [0xe0, 0x00], // 'invalid trail 3' + [0xe0, 0xc0], // 'invalid trail 4' + [0xe0, 0x80, 0x00], // 'invalid trail 5' + [0xe0, 0x80, 0xc0], // 'invalid trail 6' + [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], // '> 0x10FFFF' + [0xfe, 0x80, 0x80, 0x80, 0x80, 0x80], // 'obsolete lead byte' + + // Overlong encodings + [0xc0, 0x80], // 'overlong U+0000 - 2 bytes' + [0xe0, 0x80, 0x80], // 'overlong U+0000 - 3 bytes' + [0xf0, 0x80, 0x80, 0x80], // 'overlong U+0000 - 4 bytes' + [0xf8, 0x80, 0x80, 0x80, 0x80], // 'overlong U+0000 - 5 bytes' + [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], // 'overlong U+0000 - 6 bytes' + + [0xc1, 0xbf], // 'overlong U+007F - 2 bytes' + [0xe0, 0x81, 0xbf], // 'overlong U+007F - 3 bytes' + [0xf0, 0x80, 0x81, 0xbf], // 'overlong U+007F - 4 bytes' + [0xf8, 0x80, 0x80, 0x81, 0xbf], // 'overlong U+007F - 5 bytes' + [0xfc, 0x80, 0x80, 0x80, 0x81, 0xbf], // 'overlong U+007F - 6 bytes' + + [0xe0, 0x9f, 0xbf], // 'overlong U+07FF - 3 bytes' + [0xf0, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 4 bytes' + [0xf8, 0x80, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 5 bytes' + [0xfc, 0x80, 0x80, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 6 bytes' + + [0xf0, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 4 bytes' + [0xf8, 0x80, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 5 bytes' + [0xfc, 0x80, 0x80, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 6 bytes' + + [0xf8, 0x84, 0x8f, 0xbf, 0xbf], // 'overlong U+10FFFF - 5 bytes' + [0xfc, 0x80, 0x84, 0x8f, 0xbf, 0xbf], // 'overlong U+10FFFF - 6 bytes' + + // UTF-16 surrogates encoded as code points in UTF-8 + [0xed, 0xa0, 0x80], // 'lead surrogate' + [0xed, 0xb0, 0x80], // 'trail surrogate' + [0xed, 0xa0, 0x80, 0xed, 0xb0, 0x80], // 'surrogate pair' + ]; + + invalidSequences.forEach(input => { + expect(isUtf8(Buffer.from(input))).toBe(false); + }); +}); + +test("invalid input types", () => { + const invalidInputs = [null, undefined, "hello", true, false]; + + invalidInputs.forEach(input => { + expect(() => isUtf8(input)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); +}); + +test("detached array buffer", () => { + const arrayBuffer = new ArrayBuffer(1024); + structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + expect(() => isUtf8(arrayBuffer)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_STATE", + }), + ); +}); + +//<#END_FILE: test-buffer-isutf8.js diff --git a/test/js/node/test/parallel/buffer-no-negative-allocation.test.js b/test/js/node/test/parallel/buffer-no-negative-allocation.test.js new file mode 100644 index 0000000000000..7597c799bdb7f --- /dev/null +++ b/test/js/node/test/parallel/buffer-no-negative-allocation.test.js @@ -0,0 +1,24 @@ +//#FILE: test-buffer-no-negative-allocation.js +//#SHA1: c7f13ec857490bc5d1ffbf8da3fff19049c421f8 +//----------------- +"use strict"; + +const { SlowBuffer } = require("buffer"); + +// Test that negative Buffer length inputs throw errors. + +const msg = expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), +}); + +for (const f of [Buffer, Buffer.alloc, Buffer.allocUnsafe, Buffer.allocUnsafeSlow, SlowBuffer]) { + for (const n of [-Buffer.poolSize, -100, -1, NaN]) { + test(`${f.name} throws on ${n} length`, () => { + expect(() => f(n)).toThrow(msg); + }); + } +} + +//<#END_FILE: test-buffer-no-negative-allocation.js diff --git a/test/js/node/test/parallel/buffer-parent-property.test.js b/test/js/node/test/parallel/buffer-parent-property.test.js new file mode 100644 index 0000000000000..ebf02d3652b17 --- /dev/null +++ b/test/js/node/test/parallel/buffer-parent-property.test.js @@ -0,0 +1,26 @@ +//#FILE: test-buffer-parent-property.js +//#SHA1: 1496dde41464d188eecd053b64a320c71f62bd7d +//----------------- +"use strict"; + +// Fix for https://github.com/nodejs/node/issues/8266 +// +// Zero length Buffer objects should expose the `buffer` property of the +// TypedArrays, via the `parent` property. + +test("Buffer parent property", () => { + // If the length of the buffer object is zero + expect(Buffer.alloc(0).parent).toBeInstanceOf(ArrayBuffer); + + // If the length of the buffer object is equal to the underlying ArrayBuffer + expect(Buffer.alloc(Buffer.poolSize).parent).toBeInstanceOf(ArrayBuffer); + + // Same as the previous test, but with user created buffer + const arrayBuffer = new ArrayBuffer(0); + expect(Buffer.from(arrayBuffer).parent).toBe(arrayBuffer); + expect(Buffer.from(arrayBuffer).buffer).toBe(arrayBuffer); + expect(Buffer.from(arrayBuffer).parent).toBe(arrayBuffer); + expect(Buffer.from(arrayBuffer).buffer).toBe(arrayBuffer); +}); + +//<#END_FILE: test-buffer-parent-property.js diff --git a/test/js/node/test/parallel/buffer-read.test.js b/test/js/node/test/parallel/buffer-read.test.js new file mode 100644 index 0000000000000..272bdbb7b42c1 --- /dev/null +++ b/test/js/node/test/parallel/buffer-read.test.js @@ -0,0 +1,125 @@ +//#FILE: test-buffer-read.js +//#SHA1: e3c1ca217ea00561b3b3fae86fd36959b7f32f1a +//----------------- +"use strict"; + +// Testing basic buffer read functions +const buf = Buffer.from([0xa4, 0xfd, 0x48, 0xea, 0xcf, 0xff, 0xd9, 0x01, 0xde]); + +function read(buff, funx, args, expected) { + expect(buff[funx](...args)).toBe(expected); + expect(() => buff[funx](-1, args[1])).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); +} + +// Testing basic functionality of readDoubleBE() and readDoubleLE() +test("readDoubleBE and readDoubleLE", () => { + read(buf, "readDoubleBE", [1], -3.1827727774563287e295); + read(buf, "readDoubleLE", [1], -6.966010051009108e144); +}); + +// Testing basic functionality of readFloatBE() and readFloatLE() +test("readFloatBE and readFloatLE", () => { + read(buf, "readFloatBE", [1], -1.6691549692541768e37); + read(buf, "readFloatLE", [1], -7861303808); +}); + +// Testing basic functionality of readInt8() +test("readInt8", () => { + read(buf, "readInt8", [1], -3); +}); + +// Testing basic functionality of readInt16BE() and readInt16LE() +test("readInt16BE and readInt16LE", () => { + read(buf, "readInt16BE", [1], -696); + read(buf, "readInt16LE", [1], 0x48fd); +}); + +// Testing basic functionality of readInt32BE() and readInt32LE() +test("readInt32BE and readInt32LE", () => { + read(buf, "readInt32BE", [1], -45552945); + read(buf, "readInt32LE", [1], -806729475); +}); + +// Testing basic functionality of readIntBE() and readIntLE() +test("readIntBE and readIntLE", () => { + read(buf, "readIntBE", [1, 1], -3); + read(buf, "readIntLE", [2, 1], 0x48); +}); + +// Testing basic functionality of readUInt8() +test("readUInt8", () => { + read(buf, "readUInt8", [1], 0xfd); +}); + +// Testing basic functionality of readUInt16BE() and readUInt16LE() +test("readUInt16BE and readUInt16LE", () => { + read(buf, "readUInt16BE", [2], 0x48ea); + read(buf, "readUInt16LE", [2], 0xea48); +}); + +// Testing basic functionality of readUInt32BE() and readUInt32LE() +test("readUInt32BE and readUInt32LE", () => { + read(buf, "readUInt32BE", [1], 0xfd48eacf); + read(buf, "readUInt32LE", [1], 0xcfea48fd); +}); + +// Testing basic functionality of readUIntBE() and readUIntLE() +test("readUIntBE and readUIntLE", () => { + read(buf, "readUIntBE", [2, 2], 0x48ea); + read(buf, "readUIntLE", [2, 2], 0xea48); +}); + +// Error name and message +const OOR_ERROR = expect.objectContaining({ + name: "RangeError", +}); + +const OOB_ERROR = expect.objectContaining({ + name: "RangeError", + message: expect.any(String), +}); + +// Attempt to overflow buffers, similar to previous bug in array buffers +test("Buffer overflow attempts", () => { + expect(() => Buffer.allocUnsafe(8).readFloatBE(0xffffffff)).toThrow(OOR_ERROR); + expect(() => Buffer.allocUnsafe(8).readFloatLE(0xffffffff)).toThrow(OOR_ERROR); +}); + +// Ensure negative values can't get past offset +test("Negative offset attempts", () => { + expect(() => Buffer.allocUnsafe(8).readFloatBE(-1)).toThrow(OOR_ERROR); + expect(() => Buffer.allocUnsafe(8).readFloatLE(-1)).toThrow(OOR_ERROR); +}); + +// Offset checks +test("Offset checks for empty buffer", () => { + const buf = Buffer.allocUnsafe(0); + + expect(() => buf.readUInt8(0)).toThrow(OOB_ERROR); + expect(() => buf.readInt8(0)).toThrow(OOB_ERROR); +}); + +test("Offset checks for undersized buffers", () => { + [16, 32].forEach(bit => { + const buf = Buffer.allocUnsafe(bit / 8 - 1); + [`Int${bit}B`, `Int${bit}L`, `UInt${bit}B`, `UInt${bit}L`].forEach(fn => { + expect(() => buf[`read${fn}E`](0)).toThrow(OOB_ERROR); + }); + }); +}); + +test("Reading max values for different bit sizes", () => { + [16, 32].forEach(bits => { + const buf = Buffer.from([0xff, 0xff, 0xff, 0xff]); + ["LE", "BE"].forEach(endian => { + expect(buf[`readUInt${bits}${endian}`](0)).toBe(0xffffffff >>> (32 - bits)); + expect(buf[`readInt${bits}${endian}`](0)).toBe(0xffffffff >> (32 - bits)); + }); + }); +}); + +//<#END_FILE: test-buffer-read.js diff --git a/test/js/node/test/parallel/buffer-readdouble.test.js b/test/js/node/test/parallel/buffer-readdouble.test.js new file mode 100644 index 0000000000000..36d7bf1a9af75 --- /dev/null +++ b/test/js/node/test/parallel/buffer-readdouble.test.js @@ -0,0 +1,158 @@ +//#FILE: test-buffer-readdouble.js +//#SHA1: f20bbcdd359fb12ae549c75baf44554d0b0edab6 +//----------------- +"use strict"; + +// Test (64 bit) double +test("Buffer readDouble", () => { + const buffer = Buffer.allocUnsafe(8); + + buffer[0] = 0x55; + buffer[1] = 0x55; + buffer[2] = 0x55; + buffer[3] = 0x55; + buffer[4] = 0x55; + buffer[5] = 0x55; + buffer[6] = 0xd5; + buffer[7] = 0x3f; + expect(buffer.readDoubleBE(0)).toBe(1.1945305291680097e103); + expect(buffer.readDoubleLE(0)).toBe(0.3333333333333333); + + buffer[0] = 1; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 0; + buffer[6] = 0xf0; + buffer[7] = 0x3f; + expect(buffer.readDoubleBE(0)).toBe(7.291122019655968e-304); + expect(buffer.readDoubleLE(0)).toBe(1.0000000000000002); + + buffer[0] = 2; + expect(buffer.readDoubleBE(0)).toBe(4.778309726801735e-299); + expect(buffer.readDoubleLE(0)).toBe(1.0000000000000004); + + buffer[0] = 1; + buffer[6] = 0; + buffer[7] = 0; + // eslint-disable-next-line no-loss-of-precision + expect(buffer.readDoubleBE(0)).toBe(7.291122019556398e-304); + expect(buffer.readDoubleLE(0)).toBe(5e-324); + + buffer[0] = 0xff; + buffer[1] = 0xff; + buffer[2] = 0xff; + buffer[3] = 0xff; + buffer[4] = 0xff; + buffer[5] = 0xff; + buffer[6] = 0x0f; + buffer[7] = 0x00; + expect(Number.isNaN(buffer.readDoubleBE(0))).toBe(true); + expect(buffer.readDoubleLE(0)).toBe(2.225073858507201e-308); + + buffer[6] = 0xef; + buffer[7] = 0x7f; + expect(Number.isNaN(buffer.readDoubleBE(0))).toBe(true); + expect(buffer.readDoubleLE(0)).toBe(1.7976931348623157e308); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 0; + buffer[6] = 0xf0; + buffer[7] = 0x3f; + expect(buffer.readDoubleBE(0)).toBe(3.03865e-319); + expect(buffer.readDoubleLE(0)).toBe(1); + + buffer[6] = 0; + buffer[7] = 0x40; + expect(buffer.readDoubleBE(0)).toBe(3.16e-322); + expect(buffer.readDoubleLE(0)).toBe(2); + + buffer[7] = 0xc0; + expect(buffer.readDoubleBE(0)).toBe(9.5e-322); + expect(buffer.readDoubleLE(0)).toBe(-2); + + buffer[6] = 0x10; + buffer[7] = 0; + expect(buffer.readDoubleBE(0)).toBe(2.0237e-320); + expect(buffer.readDoubleLE(0)).toBe(2.2250738585072014e-308); + + buffer[6] = 0; + expect(buffer.readDoubleBE(0)).toBe(0); + expect(buffer.readDoubleLE(0)).toBe(0); + expect(1 / buffer.readDoubleLE(0) >= 0).toBe(true); + + buffer[7] = 0x80; + expect(buffer.readDoubleBE(0)).toBe(6.3e-322); + expect(buffer.readDoubleLE(0)).toBe(-0); + expect(1 / buffer.readDoubleLE(0) < 0).toBe(true); + + buffer[6] = 0xf0; + buffer[7] = 0x7f; + expect(buffer.readDoubleBE(0)).toBe(3.0418e-319); + expect(buffer.readDoubleLE(0)).toBe(Infinity); + + buffer[7] = 0xff; + expect(buffer.readDoubleBE(0)).toBe(3.04814e-319); + expect(buffer.readDoubleLE(0)).toBe(-Infinity); +}); + +["readDoubleLE", "readDoubleBE"].forEach(fn => { + const buffer = Buffer.allocUnsafe(8); + + test("works", () => { + // Verify that default offset works fine. + expect(() => buffer[fn](undefined)).not.toThrow(); + expect(() => buffer[fn]()).not.toThrow(); + }); + + ["", "0", null, {}, [], () => {}, true, false].forEach(off => { + test(`${fn}(${off})`, () => { + expect(() => buffer[fn](off)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + }); + + [Infinity, -1, 1].forEach(offset => { + test(`${fn}(${offset})`, () => { + expect(() => buffer[fn](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: `The value of "offset" is out of range. It must be >= 0 and <= 0. Received ${offset}`, + }), + ); + }); + }); + + test(`Buffer.alloc(1)[${fn}](1)`, () => { + expect(() => Buffer.alloc(1)[fn](1)).toThrow( + expect.objectContaining({ + code: "ERR_BUFFER_OUT_OF_BOUNDS", + name: "RangeError", + message: "Attempt to access memory outside buffer bounds", + }), + ); + }); + + [NaN, 1.01].forEach(offset => { + test(`${fn}(${offset})`, () => { + expect(() => buffer[fn](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: `The value of "offset" is out of range. It must be an integer. Received ${offset}`, + }), + ); + }); + }); +}); + +//<#END_FILE: test-buffer-readdouble.js diff --git a/test/js/node/test/parallel/buffer-readfloat.test.js b/test/js/node/test/parallel/buffer-readfloat.test.js new file mode 100644 index 0000000000000..6fe520498e8b1 --- /dev/null +++ b/test/js/node/test/parallel/buffer-readfloat.test.js @@ -0,0 +1,120 @@ +//#FILE: test-buffer-readfloat.js +//#SHA1: 08f4183be3d17e88f259da252bbc6fea2a06294e +//----------------- +"use strict"; + +// Test 32 bit float +test("32 bit float", () => { + const buffer = Buffer.alloc(4); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0x80; + buffer[3] = 0x3f; + expect(buffer.readFloatBE(0)).toBeCloseTo(4.600602988224807e-41); + expect(buffer.readFloatLE(0)).toBe(1); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0xc0; + expect(buffer.readFloatBE(0)).toBeCloseTo(2.6904930515036488e-43); + expect(buffer.readFloatLE(0)).toBe(-2); + + buffer[0] = 0xff; + buffer[1] = 0xff; + buffer[2] = 0x7f; + buffer[3] = 0x7f; + expect(Number.isNaN(buffer.readFloatBE(0))).toBe(true); + expect(buffer.readFloatLE(0)).toBeCloseTo(3.4028234663852886e38); + + buffer[0] = 0xab; + buffer[1] = 0xaa; + buffer[2] = 0xaa; + buffer[3] = 0x3e; + expect(buffer.readFloatBE(0)).toBeCloseTo(-1.2126478207002966e-12); + expect(buffer.readFloatLE(0)).toBeCloseTo(0.3333333432674408); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + expect(buffer.readFloatBE(0)).toBe(0); + expect(buffer.readFloatLE(0)).toBe(0); + expect(1 / buffer.readFloatLE(0) >= 0).toBe(true); + + buffer[3] = 0x80; + expect(buffer.readFloatBE(0)).toBeCloseTo(1.793662034335766e-43); + expect(buffer.readFloatLE(0)).toBe(-0); + expect(1 / buffer.readFloatLE(0) < 0).toBe(true); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0x80; + buffer[3] = 0x7f; + expect(buffer.readFloatBE(0)).toBeCloseTo(4.609571298396486e-41); + expect(buffer.readFloatLE(0)).toBe(Infinity); + + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0x80; + buffer[3] = 0xff; + expect(buffer.readFloatBE(0)).toBeCloseTo(4.627507918739843e-41); + expect(buffer.readFloatLE(0)).toBe(-Infinity); +}); + +["readFloatLE", "readFloatBE"].forEach(fn => { + const buffer = Buffer.alloc(4); + + test("works", () => { + // Verify that default offset works fine. + expect(() => buffer[fn](undefined)).not.toThrow(); + expect(() => buffer[fn]()).not.toThrow(); + }); + + ["", "0", null, {}, [], () => {}, true, false].forEach(off => { + test(`${fn}(${off})`, () => { + expect(() => buffer[fn](off)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + }); + + [Infinity, -1, 1].forEach(offset => { + test(`${fn}(${offset})`, () => { + expect(() => buffer[fn](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: `The value of "offset" is out of range. It must be >= 0 and <= 0. Received ${offset}`, + }), + ); + }); + }); + + test(`Buffer.alloc(1)[${fn}](1)`, () => { + expect(() => Buffer.alloc(1)[fn](1)).toThrow( + expect.objectContaining({ + code: "ERR_BUFFER_OUT_OF_BOUNDS", + name: "RangeError", + message: "Attempt to access memory outside buffer bounds", + }), + ); + }); + + [NaN, 1.01].forEach(offset => { + test(`${fn}(${offset})`, () => { + expect(() => buffer[fn](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: `The value of "offset" is out of range. It must be an integer. Received ${offset}`, + }), + ); + }); + }); +}); + +//<#END_FILE: test-buffer-readfloat.js diff --git a/test/js/node/test/parallel/buffer-readint.test.js b/test/js/node/test/parallel/buffer-readint.test.js new file mode 100644 index 0000000000000..9a4ff0d34d312 --- /dev/null +++ b/test/js/node/test/parallel/buffer-readint.test.js @@ -0,0 +1,199 @@ +//#FILE: test-buffer-readint.js +//#SHA1: 95feae5d0540f00ae75fb16610ad161ab8f69300 +//----------------- +"use strict"; + +// Test OOB +test("Out of bounds reads", () => { + const buffer = Buffer.alloc(4); + + ["Int8", "Int16BE", "Int16LE", "Int32BE", "Int32LE"].forEach(fn => { + // Verify that default offset works fine. + expect(() => buffer[`read${fn}`](undefined)).not.toThrow(); + expect(() => buffer[`read${fn}`]()).not.toThrow(); + + ["", "0", null, {}, [], () => {}, true, false].forEach(o => { + expect(() => buffer[`read${fn}`](o)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.any(String), + }), + ); + }); + + [Infinity, -1, -4294967295].forEach(offset => { + expect(() => buffer[`read${fn}`](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + + [NaN, 1.01].forEach(offset => { + expect(() => buffer[`read${fn}`](offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + }); +}); + +// Test 8 bit signed integers +test("8 bit signed integers", () => { + const data = Buffer.from([0x23, 0xab, 0x7c, 0xef]); + + expect(data.readInt8(0)).toBe(0x23); + + data[0] = 0xff; + expect(data.readInt8(0)).toBe(-1); + + data[0] = 0x87; + expect(data.readInt8(0)).toBe(-121); + expect(data.readInt8(1)).toBe(-85); + expect(data.readInt8(2)).toBe(124); + expect(data.readInt8(3)).toBe(-17); +}); + +// Test 16 bit integers +test("16 bit integers", () => { + const buffer = Buffer.from([0x16, 0x79, 0x65, 0x6e, 0x69, 0x78]); + + expect(buffer.readInt16BE(0)).toBe(0x1679); + expect(buffer.readInt16LE(0)).toBe(0x7916); + + buffer[0] = 0xff; + buffer[1] = 0x80; + expect(buffer.readInt16BE(0)).toBe(-128); + expect(buffer.readInt16LE(0)).toBe(-32513); + + buffer[0] = 0x77; + buffer[1] = 0x65; + expect(buffer.readInt16BE(0)).toBe(0x7765); + expect(buffer.readInt16BE(1)).toBe(0x6565); + expect(buffer.readInt16BE(2)).toBe(0x656e); + expect(buffer.readInt16BE(3)).toBe(0x6e69); + expect(buffer.readInt16BE(4)).toBe(0x6978); + expect(buffer.readInt16LE(0)).toBe(0x6577); + expect(buffer.readInt16LE(1)).toBe(0x6565); + expect(buffer.readInt16LE(2)).toBe(0x6e65); + expect(buffer.readInt16LE(3)).toBe(0x696e); + expect(buffer.readInt16LE(4)).toBe(0x7869); +}); + +// Test 32 bit integers +test("32 bit integers", () => { + const buffer = Buffer.from([0x43, 0x53, 0x16, 0x79, 0x36, 0x17]); + + expect(buffer.readInt32BE(0)).toBe(0x43531679); + expect(buffer.readInt32LE(0)).toBe(0x79165343); + + buffer[0] = 0xff; + buffer[1] = 0xfe; + buffer[2] = 0xef; + buffer[3] = 0xfa; + expect(buffer.readInt32BE(0)).toBe(-69638); + expect(buffer.readInt32LE(0)).toBe(-84934913); + + buffer[0] = 0x42; + buffer[1] = 0xc3; + buffer[2] = 0x95; + buffer[3] = 0xa9; + expect(buffer.readInt32BE(0)).toBe(0x42c395a9); + expect(buffer.readInt32BE(1)).toBe(-1013601994); + expect(buffer.readInt32BE(2)).toBe(-1784072681); + expect(buffer.readInt32LE(0)).toBe(-1449802942); + expect(buffer.readInt32LE(1)).toBe(917083587); + expect(buffer.readInt32LE(2)).toBe(389458325); +}); + +// Test Int +test("Int", () => { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + expect(buffer.readIntLE(0, 1)).toBe(0x01); + expect(buffer.readIntBE(0, 1)).toBe(0x01); + expect(buffer.readIntLE(0, 3)).toBe(0x030201); + expect(buffer.readIntBE(0, 3)).toBe(0x010203); + expect(buffer.readIntLE(0, 5)).toBe(0x0504030201); + expect(buffer.readIntBE(0, 5)).toBe(0x0102030405); + expect(buffer.readIntLE(0, 6)).toBe(0x060504030201); + expect(buffer.readIntBE(0, 6)).toBe(0x010203040506); + expect(buffer.readIntLE(1, 6)).toBe(0x070605040302); + expect(buffer.readIntBE(1, 6)).toBe(0x020304050607); + expect(buffer.readIntLE(2, 6)).toBe(0x080706050403); + expect(buffer.readIntBE(2, 6)).toBe(0x030405060708); + + // Check byteLength. + ["readIntBE", "readIntLE"].forEach(fn => { + ["", "0", null, {}, [], () => {}, true, false, undefined].forEach(len => { + expect(() => buffer[fn](0, len)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.any(String), + }), + ); + }); + + [Infinity, -1].forEach(byteLength => { + expect(() => buffer[fn](0, byteLength)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.any(String), + }), + ); + }); + + [NaN, 1.01].forEach(byteLength => { + expect(() => buffer[fn](0, byteLength)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ["readIntBE", "readIntLE"].forEach(fn => { + ["", "0", null, {}, [], () => {}, true, false, undefined].forEach(o => { + expect(() => buffer[fn](o, i)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.any(String), + }), + ); + }); + + [Infinity, -1, -4294967295].forEach(offset => { + expect(() => buffer[fn](offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + + [NaN, 1.01].forEach(offset => { + expect(() => buffer[fn](offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + }); + } +}); + +//<#END_FILE: test-buffer-readint.js diff --git a/test/js/node/test/parallel/buffer-readuint.test.js b/test/js/node/test/parallel/buffer-readuint.test.js new file mode 100644 index 0000000000000..11adbbbc7344d --- /dev/null +++ b/test/js/node/test/parallel/buffer-readuint.test.js @@ -0,0 +1,188 @@ +//#FILE: test-buffer-readuint.js +//#SHA1: 5a48fd4b94090352cd0aaea48eaa18c76e1814a1 +//----------------- +"use strict"; + +// Test OOB +describe("OOB tests", () => { + const buffer = Buffer.alloc(4); + + for (const fn of ["UInt8", "UInt16BE", "UInt16LE", "UInt32BE", "UInt32LE"]) { + describe(`read${fn}`, () => { + test("not throws", () => { + expect(() => buffer[`read${fn}`](undefined)).not.toThrow(); + expect(() => buffer[`read${fn}`]()).not.toThrow(); + }); + + for (const o of ["", "0", null, {}, [], () => {}, true, false]) { + test(`(${o})`, () => { + expect(() => buffer[`read${fn}`](o)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }), + ); + }); + } + + for (const o of [Infinity, -1, -4294967295]) { + test(`(${o})`, () => { + expect(() => buffer[`read${fn}`](o)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }), + ); + }); + } + + for (const o of [NaN, 1.01]) { + test(`(${o})`, () => { + expect(() => buffer[`read${fn}`](o)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining(`It must be an integer. Received ${o}`), + }), + ); + }); + } + }); + } +}); + +// Test 8 bit unsigned integers +test("8 bit unsigned integers", () => { + const data = Buffer.from([0xff, 0x2a, 0x2a, 0x2a]); + expect(data.readUInt8(0)).toBe(255); + expect(data.readUInt8(1)).toBe(42); + expect(data.readUInt8(2)).toBe(42); + expect(data.readUInt8(3)).toBe(42); +}); + +// Test 16 bit unsigned integers +test("16 bit unsigned integers", () => { + const data = Buffer.from([0x00, 0x2a, 0x42, 0x3f]); + expect(data.readUInt16BE(0)).toBe(0x2a); + expect(data.readUInt16BE(1)).toBe(0x2a42); + expect(data.readUInt16BE(2)).toBe(0x423f); + expect(data.readUInt16LE(0)).toBe(0x2a00); + expect(data.readUInt16LE(1)).toBe(0x422a); + expect(data.readUInt16LE(2)).toBe(0x3f42); + + data[0] = 0xfe; + data[1] = 0xfe; + expect(data.readUInt16BE(0)).toBe(0xfefe); + expect(data.readUInt16LE(0)).toBe(0xfefe); +}); + +// Test 32 bit unsigned integers +test("32 bit unsigned integers", () => { + const data = Buffer.from([0x32, 0x65, 0x42, 0x56, 0x23, 0xff]); + expect(data.readUInt32BE(0)).toBe(0x32654256); + expect(data.readUInt32BE(1)).toBe(0x65425623); + expect(data.readUInt32BE(2)).toBe(0x425623ff); + expect(data.readUInt32LE(0)).toBe(0x56426532); + expect(data.readUInt32LE(1)).toBe(0x23564265); + expect(data.readUInt32LE(2)).toBe(0xff235642); +}); + +// Test UInt +describe("UInt", () => { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + test("works", () => { + expect(buffer.readUIntLE(0, 1)).toBe(0x01); + expect(buffer.readUIntBE(0, 1)).toBe(0x01); + expect(buffer.readUIntLE(0, 3)).toBe(0x030201); + expect(buffer.readUIntBE(0, 3)).toBe(0x010203); + expect(buffer.readUIntLE(0, 5)).toBe(0x0504030201); + expect(buffer.readUIntBE(0, 5)).toBe(0x0102030405); + expect(buffer.readUIntLE(0, 6)).toBe(0x060504030201); + expect(buffer.readUIntBE(0, 6)).toBe(0x010203040506); + expect(buffer.readUIntLE(1, 6)).toBe(0x070605040302); + expect(buffer.readUIntBE(1, 6)).toBe(0x020304050607); + expect(buffer.readUIntLE(2, 6)).toBe(0x080706050403); + expect(buffer.readUIntBE(2, 6)).toBe(0x030405060708); + }); + + // Check byteLength. + for (const fn of ["readUIntBE", "readUIntLE"]) { + describe(fn, () => { + for (const len of ["", "0", null, {}, [], () => {}, true, false, undefined]) { + test(`(0, ${len})`, () => { + expect(() => buffer[fn](0, len)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + } + + for (const len of [Infinity, -1]) { + test(`(0, ${len})`, () => { + expect(() => buffer[fn](0, len)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.stringContaining(`It must be >= 1 and <= 6. Received ${len}`), + }), + ); + }); + } + + for (const len of [NaN, 1.01]) { + test(`(0, ${len})`, () => { + expect(() => buffer[fn](0, len)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining(`It must be an integer. Received ${len}`), + }), + ); + }); + } + }); + } + + // Test 1 to 6 bytes. + ["readUIntBE", "readUIntLE"].forEach(fn => { + for (let i = 1; i <= 6; i++) { + ["", "0", null, {}, [], () => {}, true, false, undefined].forEach(o => { + test(`${fn}(${o}, ${i})`, () => { + expect(() => buffer[fn](o, i)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }), + ); + }); + }); + + [Infinity, -1, -4294967295].forEach(offset => { + test(`${fn}(${offset}, ${i})`, () => { + expect(() => buffer[fn](offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining(`It must be >= 0 and <= ${8 - i}. Received ${offset}`), + }), + ); + }); + }); + + [NaN, 1.01].forEach(offset => { + test(`${fn}(${offset}, ${i})`, () => { + expect(() => buffer[fn](offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining(`It must be an integer. Received ${offset}`), + }), + ); + }); + }); + } + }); +}); + +//<#END_FILE: test-buffer-readuint.js diff --git a/test/js/node/test/parallel/buffer-slow.test.js b/test/js/node/test/parallel/buffer-slow.test.js new file mode 100644 index 0000000000000..2c98757ef27c6 --- /dev/null +++ b/test/js/node/test/parallel/buffer-slow.test.js @@ -0,0 +1,64 @@ +//#FILE: test-buffer-slow.js +//#SHA1: fadf639fe26752f00488a41a29f1977f95fc1c79 +//----------------- +"use strict"; + +const buffer = require("buffer"); +const SlowBuffer = buffer.SlowBuffer; + +const ones = [1, 1, 1, 1]; + +test("SlowBuffer should create a Buffer", () => { + let sb = SlowBuffer(4); + expect(sb).toBeInstanceOf(Buffer); + expect(sb.length).toBe(4); + sb.fill(1); + for (const [key, value] of sb.entries()) { + expect(value).toBe(ones[key]); + } + + // underlying ArrayBuffer should have the same length + expect(sb.buffer.byteLength).toBe(4); +}); + +test("SlowBuffer should work without new", () => { + let sb = SlowBuffer(4); + expect(sb).toBeInstanceOf(Buffer); + expect(sb.length).toBe(4); + sb.fill(1); + for (const [key, value] of sb.entries()) { + expect(value).toBe(ones[key]); + } +}); + +test("SlowBuffer should work with edge cases", () => { + expect(SlowBuffer(0).length).toBe(0); +}); + +test("SlowBuffer should throw with invalid length type", () => { + const bufferInvalidTypeMsg = expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.stringContaining(`The "size" argument must be of type number. Received`), + }); + + expect(() => SlowBuffer()).toThrow(bufferInvalidTypeMsg); + expect(() => SlowBuffer({})).toThrow(bufferInvalidTypeMsg); + expect(() => SlowBuffer("6")).toThrow(bufferInvalidTypeMsg); + expect(() => SlowBuffer(true)).toThrow(bufferInvalidTypeMsg); +}); + +test("SlowBuffer should throw with invalid length value", () => { + const bufferMaxSizeMsg = expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }); + + expect(() => SlowBuffer(NaN)).toThrow(bufferMaxSizeMsg); + expect(() => SlowBuffer(Infinity)).toThrow(bufferMaxSizeMsg); + expect(() => SlowBuffer(-1)).toThrow(bufferMaxSizeMsg); + expect(() => SlowBuffer(buffer.kMaxLength + 1)).toThrow(bufferMaxSizeMsg); +}); + +//<#END_FILE: test-buffer-slow.js diff --git a/test/js/node/test/parallel/buffer-tojson.test.js b/test/js/node/test/parallel/buffer-tojson.test.js new file mode 100644 index 0000000000000..89a428a492c76 --- /dev/null +++ b/test/js/node/test/parallel/buffer-tojson.test.js @@ -0,0 +1,35 @@ +//#FILE: test-buffer-tojson.js +//#SHA1: 39b31549a09e67c89316a24c895db2dfab939ec4 +//----------------- +"use strict"; + +test("Buffer JSON serialization", () => { + expect(JSON.stringify(Buffer.alloc(0))).toBe('{"type":"Buffer","data":[]}'); + expect(JSON.stringify(Buffer.from([1, 2, 3, 4]))).toBe('{"type":"Buffer","data":[1,2,3,4]}'); +}); + +// issue GH-7849 +test("Buffer deserialization", () => { + const buf = Buffer.from("test"); + const json = JSON.stringify(buf); + const obj = JSON.parse(json); + const copy = Buffer.from(obj); + + expect(copy).toEqual(buf); +}); + +// GH-5110 +test("Buffer serialization and custom deserialization", () => { + const buffer = Buffer.from("test"); + const string = JSON.stringify(buffer); + + expect(string).toBe('{"type":"Buffer","data":[116,101,115,116]}'); + + function receiver(key, value) { + return value && value.type === "Buffer" ? Buffer.from(value.data) : value; + } + + expect(JSON.parse(string, receiver)).toEqual(buffer); +}); + +//<#END_FILE: test-buffer-tojson.js diff --git a/test/js/node/test/parallel/buffer-tostring-rangeerror.test.js b/test/js/node/test/parallel/buffer-tostring-rangeerror.test.js new file mode 100644 index 0000000000000..cf3a0bea1f34a --- /dev/null +++ b/test/js/node/test/parallel/buffer-tostring-rangeerror.test.js @@ -0,0 +1,30 @@ +//#FILE: test-buffer-tostring-rangeerror.js +//#SHA1: c5bd04a7b4f3b7ecfb3898262dd73da29a9ad162 +//----------------- +"use strict"; + +// This test ensures that Node.js throws an Error when trying to convert a +// large buffer into a string. +// Regression test for https://github.com/nodejs/node/issues/649. + +const { + SlowBuffer, + constants: { MAX_STRING_LENGTH }, +} = require("buffer"); + +const len = MAX_STRING_LENGTH + 1; +const errorMatcher = expect.objectContaining({ + code: "ERR_STRING_TOO_LONG", + name: "Error", + message: expect.any(String), +}); + +test("Buffer toString with large buffer throws RangeError", () => { + expect(() => Buffer(len).toString("utf8")).toThrow(errorMatcher); + expect(() => SlowBuffer(len).toString("utf8")).toThrow(errorMatcher); + expect(() => Buffer.alloc(len).toString("utf8")).toThrow(errorMatcher); + expect(() => Buffer.allocUnsafe(len).toString("utf8")).toThrow(errorMatcher); + expect(() => Buffer.allocUnsafeSlow(len).toString("utf8")).toThrow(errorMatcher); +}); + +//<#END_FILE: test-buffer-tostring-rangeerror.js diff --git a/test/js/node/test/parallel/buffer-tostring.test.js b/test/js/node/test/parallel/buffer-tostring.test.js new file mode 100644 index 0000000000000..eb48074506b38 --- /dev/null +++ b/test/js/node/test/parallel/buffer-tostring.test.js @@ -0,0 +1,43 @@ +//#FILE: test-buffer-tostring.js +//#SHA1: 0a6490b6dd4c343c01828d1c4ff81b745b6b1552 +//----------------- +"use strict"; + +// utf8, ucs2, ascii, latin1, utf16le +const encodings = ["utf8", "utf-8", "ucs2", "ucs-2", "ascii", "latin1", "binary", "utf16le", "utf-16le"]; + +test("Buffer.from().toString() with various encodings", () => { + encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + expect(Buffer.from("foo", encoding).toString(encoding)).toBe("foo"); + }); +}); + +test("Buffer.from().toString() with base64 encoding", () => { + ["base64", "BASE64"].forEach(encoding => { + expect(Buffer.from("Zm9v", encoding).toString(encoding)).toBe("Zm9v"); + }); +}); + +test("Buffer.from().toString() with hex encoding", () => { + ["hex", "HEX"].forEach(encoding => { + expect(Buffer.from("666f6f", encoding).toString(encoding)).toBe("666f6f"); + }); +}); + +test("Buffer.from().toString() with invalid encodings", () => { + for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + expect(Buffer.isEncoding(encoding)).toBe(false); + expect(() => Buffer.from("foo").toString(encoding)).toThrow( + expect.objectContaining({ + code: "ERR_UNKNOWN_ENCODING", + name: "TypeError", + message: expect.any(String), + }), + ); + } +}); + +//<#END_FILE: test-buffer-tostring.js diff --git a/test/js/node/test/parallel/buffer-write.test.js b/test/js/node/test/parallel/buffer-write.test.js new file mode 100644 index 0000000000000..ceb7123d5f35b --- /dev/null +++ b/test/js/node/test/parallel/buffer-write.test.js @@ -0,0 +1,119 @@ +//#FILE: test-buffer-write.js +//#SHA1: 9577e31a533888b164b0abf4ebececbe04e381cb +//----------------- +"use strict"; + +[-1, 10].forEach(offset => { + test(`Buffer.alloc(9).write('foo', ${offset}) throws RangeError`, () => { + expect(() => Buffer.alloc(9).write("foo", offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); +}); + +const resultMap = new Map([ + ["utf8", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["ucs2", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ["ascii", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["latin1", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["binary", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["utf16le", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ["base64", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["base64url", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["hex", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], +]); + +// utf8, ucs2, ascii, latin1, utf16le +const encodings = ["utf8", "utf-8", "ucs2", "ucs-2", "ascii", "latin1", "binary", "utf16le", "utf-16le"]; + +encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + test(`Buffer.write with encoding ${encoding}`, () => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength("foo", encoding); + expect(buf.write("foo", 0, len, encoding)).toBe(len); + + if (encoding.includes("-")) encoding = encoding.replace("-", ""); + + expect(buf).toEqual(resultMap.get(encoding.toLowerCase())); + }); + }); + +// base64 +["base64", "BASE64", "base64url", "BASE64URL"].forEach(encoding => { + test(`Buffer.write with encoding ${encoding}`, () => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength("Zm9v", encoding); + + expect(buf.write("Zm9v", 0, len, encoding)).toBe(len); + expect(buf).toEqual(resultMap.get(encoding.toLowerCase())); + }); +}); + +// hex +["hex", "HEX"].forEach(encoding => { + test(`Buffer.write with encoding ${encoding}`, () => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength("666f6f", encoding); + + expect(buf.write("666f6f", 0, len, encoding)).toBe(len); + expect(buf).toEqual(resultMap.get(encoding.toLowerCase())); + }); +}); + +// Invalid encodings +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + test(`Invalid encoding ${encoding}`, () => { + expect(Buffer.isEncoding(encoding)).toBe(false); + expect(() => Buffer.alloc(9).write("foo", encoding)).toThrow( + expect.objectContaining({ + code: "ERR_UNKNOWN_ENCODING", + name: "TypeError", + message: expect.any(String), + }), + ); + }); +} + +// UCS-2 overflow CVE-2018-12115 +for (let i = 1; i < 4; i++) { + test(`UCS-2 overflow test ${i}`, () => { + // Allocate two Buffers sequentially off the pool. Run more than once in case + // we hit the end of the pool and don't get sequential allocations + const x = Buffer.allocUnsafe(4).fill(0); + const y = Buffer.allocUnsafe(4).fill(1); + // Should not write anything, pos 3 doesn't have enough room for a 16-bit char + expect(x.write("ыыыыыы", 3, "ucs2")).toBe(0); + // CVE-2018-12115 experienced via buffer overrun to next block in the pool + expect(Buffer.compare(y, Buffer.alloc(4, 1))).toBe(0); + }); +} + +test("Should not write any data when there is no space for 16-bit chars", () => { + const z = Buffer.alloc(4, 0); + expect(z.write("\u0001", 3, "ucs2")).toBe(0); + expect(Buffer.compare(z, Buffer.alloc(4, 0))).toBe(0); + // Make sure longer strings are written up to the buffer end. + expect(z.write("abcd", 2)).toBe(2); + expect([...z]).toEqual([0, 0, 0x61, 0x62]); +}); + +test("Large overrun should not corrupt the process", () => { + expect(Buffer.alloc(4).write("ыыыыыы".repeat(100), 3, "utf16le")).toBe(0); +}); + +test(".write() does not affect the byte after the written-to slice of the Buffer", () => { + // Refs: https://github.com/nodejs/node/issues/26422 + const buf = Buffer.alloc(8); + expect(buf.write("ыы", 1, "utf16le")).toBe(4); + expect([...buf]).toEqual([0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); +}); + +//<#END_FILE: test-buffer-write.js diff --git a/test/js/node/test/parallel/buffer-writedouble.test.js b/test/js/node/test/parallel/buffer-writedouble.test.js new file mode 100644 index 0000000000000..d528b25541dcf --- /dev/null +++ b/test/js/node/test/parallel/buffer-writedouble.test.js @@ -0,0 +1,130 @@ +//#FILE: test-buffer-writedouble.js +//#SHA1: eb8c657fe982b3e0acaf9e7d8690ec47a363b0c7 +//----------------- +"use strict"; + +// Tests to verify doubles are correctly written + +test("writeDoubleBE and writeDoubleLE", () => { + const buffer = Buffer.allocUnsafe(16); + + buffer.writeDoubleBE(2.225073858507201e-308, 0); + buffer.writeDoubleLE(2.225073858507201e-308, 8); + expect(buffer).toEqual( + Buffer.from([0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00]), + ); + + buffer.writeDoubleBE(1.0000000000000004, 0); + buffer.writeDoubleLE(1.0000000000000004, 8); + expect(buffer).toEqual( + Buffer.from([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f]), + ); + + buffer.writeDoubleBE(-2, 0); + buffer.writeDoubleLE(-2, 8); + expect(buffer).toEqual( + Buffer.from([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0]), + ); + + buffer.writeDoubleBE(1.7976931348623157e308, 0); + buffer.writeDoubleLE(1.7976931348623157e308, 8); + expect(buffer).toEqual( + Buffer.from([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f]), + ); + + buffer.writeDoubleBE(0 * -1, 0); + buffer.writeDoubleLE(0 * -1, 8); + expect(buffer).toEqual( + Buffer.from([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), + ); + + buffer.writeDoubleBE(Infinity, 0); + buffer.writeDoubleLE(Infinity, 8); + + expect(buffer).toEqual( + Buffer.from([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f]), + ); + + expect(buffer.readDoubleBE(0)).toBe(Infinity); + expect(buffer.readDoubleLE(8)).toBe(Infinity); + + buffer.writeDoubleBE(-Infinity, 0); + buffer.writeDoubleLE(-Infinity, 8); + + expect(buffer).toEqual( + Buffer.from([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff]), + ); + + expect(buffer.readDoubleBE(0)).toBe(-Infinity); + expect(buffer.readDoubleLE(8)).toBe(-Infinity); + + buffer.writeDoubleBE(NaN, 0); + buffer.writeDoubleLE(NaN, 8); + + // JS only knows a single NaN but there exist two platform specific + // implementations. Therefore, allow both quiet and signalling NaNs. + if (buffer[1] === 0xf7) { + expect(buffer).toEqual( + Buffer.from([0x7f, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x7f]), + ); + } else { + expect(buffer).toEqual( + Buffer.from([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f]), + ); + } + + expect(Number.isNaN(buffer.readDoubleBE(0))).toBe(true); + expect(Number.isNaN(buffer.readDoubleLE(8))).toBe(true); +}); + +// OOB in writeDouble{LE,BE} should throw. +test("OOB in writeDoubleLE and writeDoubleBE", () => { + const small = Buffer.allocUnsafe(1); + const buffer = Buffer.allocUnsafe(16); + + ["writeDoubleLE", "writeDoubleBE"].forEach(fn => { + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + expect(() => small[fn](11.11, 0)).toThrow( + expect.objectContaining({ + code: "ERR_BUFFER_OUT_OF_BOUNDS", + name: "RangeError", + message: expect.any(String), + }), + ); + + ["", "0", null, {}, [], () => {}, true, false].forEach(off => { + expect(() => small[fn](23, off)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.any(String), + }), + ); + }); + + [Infinity, -1, 9].forEach(offset => { + expect(() => buffer[fn](23, offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + + [NaN, 1.01].forEach(offset => { + expect(() => buffer[fn](42, offset)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.any(String), + }), + ); + }); + }); +}); + +//<#END_FILE: test-buffer-writedouble.js diff --git a/test/js/node/test/parallel/buffer-writeuint.test.js b/test/js/node/test/parallel/buffer-writeuint.test.js new file mode 100644 index 0000000000000..ca816499de7dd --- /dev/null +++ b/test/js/node/test/parallel/buffer-writeuint.test.js @@ -0,0 +1,269 @@ +//#FILE: test-buffer-writeuint.js +//#SHA1: 2a0cca5ed04fac65227a836185b8ec24f9410e6d +//----------------- +"use strict"; + +// We need to check the following things: +// - We are correctly resolving big endian (doesn't mean anything for 8 bit) +// - Correctly resolving little endian (doesn't mean anything for 8 bit) +// - Correctly using the offsets +// - Correctly interpreting values that are beyond the signed range as unsigned + +describe("Buffer writeUint tests", () => { + test("OOB", () => { + const data = Buffer.alloc(8); + ["UInt8", "UInt16BE", "UInt16LE", "UInt32BE", "UInt32LE"].forEach(fn => { + // Verify that default offset works fine. + data[`write${fn}`](23, undefined); + data[`write${fn}`](23); + + ["", "0", null, {}, [], () => {}, true, false].forEach(o => { + expect(() => data[`write${fn}`](23, o)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + + [NaN, Infinity, -1, 1.01].forEach(o => { + expect(() => data[`write${fn}`](23, o)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + }), + ); + }); + }); + }); + + test("8 bit", () => { + const data = Buffer.alloc(4); + + data.writeUInt8(23, 0); + data.writeUInt8(23, 1); + data.writeUInt8(23, 2); + data.writeUInt8(23, 3); + expect(data).toEqual(Buffer.from([23, 23, 23, 23])); + + data.writeUInt8(23, 0); + data.writeUInt8(23, 1); + data.writeUInt8(23, 2); + data.writeUInt8(23, 3); + expect(data).toEqual(Buffer.from([23, 23, 23, 23])); + + data.writeUInt8(255, 0); + expect(data[0]).toBe(255); + + data.writeUInt8(255, 0); + expect(data[0]).toBe(255); + + let value = 0xfffff; + ["writeUInt8"].forEach(fn => { + expect(() => data[fn](value, 0)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.stringContaining('The value of "value" is out of range.'), + }), + ); + }); + }); + + test("16 bit", () => { + let value = 0x2343; + const data = Buffer.alloc(4); + + data.writeUInt16BE(value, 0); + expect(data).toEqual(Buffer.from([0x23, 0x43, 0, 0])); + + data.writeUInt16BE(value, 1); + expect(data).toEqual(Buffer.from([0x23, 0x23, 0x43, 0])); + + data.writeUInt16BE(value, 2); + expect(data).toEqual(Buffer.from([0x23, 0x23, 0x23, 0x43])); + + data.writeUInt16LE(value, 0); + expect(data).toEqual(Buffer.from([0x43, 0x23, 0x23, 0x43])); + + data.writeUInt16LE(value, 1); + expect(data).toEqual(Buffer.from([0x43, 0x43, 0x23, 0x43])); + + data.writeUInt16LE(value, 2); + expect(data).toEqual(Buffer.from([0x43, 0x43, 0x43, 0x23])); + + value = 0xff80; + data.writeUInt16LE(value, 0); + expect(data).toEqual(Buffer.from([0x80, 0xff, 0x43, 0x23])); + + data.writeUInt16BE(value, 0); + expect(data).toEqual(Buffer.from([0xff, 0x80, 0x43, 0x23])); + + value = 0xfffff; + ["writeUInt16BE", "writeUInt16LE"].forEach(fn => { + expect(() => data[fn](value, 0)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.stringContaining('The value of "value" is out of range.'), + }), + ); + }); + }); + + test("32 bit", () => { + const data = Buffer.alloc(6); + let value = 0xe7f90a6d; + + data.writeUInt32BE(value, 0); + expect(data).toEqual(Buffer.from([0xe7, 0xf9, 0x0a, 0x6d, 0, 0])); + + data.writeUInt32BE(value, 1); + expect(data).toEqual(Buffer.from([0xe7, 0xe7, 0xf9, 0x0a, 0x6d, 0])); + + data.writeUInt32BE(value, 2); + expect(data).toEqual(Buffer.from([0xe7, 0xe7, 0xe7, 0xf9, 0x0a, 0x6d])); + + data.writeUInt32LE(value, 0); + expect(data).toEqual(Buffer.from([0x6d, 0x0a, 0xf9, 0xe7, 0x0a, 0x6d])); + + data.writeUInt32LE(value, 1); + expect(data).toEqual(Buffer.from([0x6d, 0x6d, 0x0a, 0xf9, 0xe7, 0x6d])); + + data.writeUInt32LE(value, 2); + expect(data).toEqual(Buffer.from([0x6d, 0x6d, 0x6d, 0x0a, 0xf9, 0xe7])); + + value = 0xfffffffff; + ["writeUInt32BE", "writeUInt32LE"].forEach(fn => { + expect(() => data[fn](value, 0)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.stringContaining('The value of "value" is out of range.'), + }), + ); + }); + }); + + test("48 bit", () => { + const value = 0x1234567890ab; + const data = Buffer.allocUnsafe(6); + data.writeUIntBE(value, 0, 6); + expect(data).toEqual(Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab])); + + data.writeUIntLE(value, 0, 6); + expect(data).toEqual(Buffer.from([0xab, 0x90, 0x78, 0x56, 0x34, 0x12])); + }); + + describe("UInt", () => { + const data = Buffer.alloc(8); + let val = 0x100; + + // Check byteLength. + ["writeUIntBE", "writeUIntLE"].forEach(fn => { + ["", "0", null, {}, [], () => {}, true, false, undefined].forEach(bl => { + test(`${fn}(23, 0, ${bl})`, () => { + expect(() => data[fn](23, 0, bl)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + }), + ); + }); + }); + + [Infinity, -1].forEach(byteLength => { + test(`${fn}(23, 0, ${byteLength}`, () => { + expect(() => data[fn](23, 0, byteLength)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + message: expect.stringContaining('The value of "byteLength" is out of range.'), + }), + ); + }); + }); + + [NaN, 1.01].forEach(byteLength => { + test(`${fn}(42, 0, ${byteLength}`, () => { + expect(() => data[fn](42, 0, byteLength)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining('The value of "byteLength" is out of range.'), + }), + ); + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ["writeUIntBE", "writeUIntLE"].forEach(fn => { + test(`${fn}(${val}, 0, ${i}`, () => { + expect(() => { + data[fn](val, 0, i); + }).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining('The value of "value" is out of range.'), + }), + ); + }); + + ["", "0", null, {}, [], () => {}, true, false].forEach(o => { + test(`${fn}(23, ${o}, ${i})`, () => { + expect(() => data[fn](23, o, i)).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }), + ); + }); + }); + + [Infinity, -1, -4294967295].forEach(offset => { + test(`${fn}(${val - 1}, ${offset}, ${i})`, () => { + expect(() => data[fn](val - 1, offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining('The value of "offset" is out of range.'), + }), + ); + }); + }); + + [NaN, 1.01].forEach(offset => { + test(`${fn}(${val - 1}, ${offset}, ${i})`, () => { + expect(() => data[fn](val - 1, offset, i)).toThrow( + expect.objectContaining({ + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: expect.stringContaining('The value of "offset" is out of range.'), + }), + ); + }); + }); + }); + + val *= 0x100; + } + }); + + const functionPairs = [ + "UInt8", + "UInt16LE", + "UInt16BE", + "UInt32LE", + "UInt32BE", + "UIntLE", + "UIntBE", + "BigUInt64LE", + "BigUInt64BE", + ]; + for (const fn of functionPairs) { + test(`UInt function aliases: ${fn}`, () => { + const p = Buffer.prototype; + const lowerFn = fn.replace(/UInt/, "Uint"); + expect(p[`write${fn}`]).toBe(p[`write${lowerFn}`]); + expect(p[`read${fn}`]).toBe(p[`read${lowerFn}`]); + }); + } +}); + +//<#END_FILE: test-buffer-writeuint.js