diff --git a/scripts/check-node-all.sh b/scripts/check-node-all.sh new file mode 100755 index 00000000000000..d878b88abe837d --- /dev/null +++ b/scripts/check-node-all.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +i=0 +j=0 + +for x in $(find test/js/node/test/parallel -type f -name "test-$1*.js") +do + i=$((i+1)) + echo ./$x + if timeout 2 $PWD/build/debug/bun-debug ./$x + then + j=$((j+1)) + fi + echo +done + +echo $i tests tested +echo $j tests passed diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index 12a2506a4132e0..1f517564d2483f 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -724,7 +724,6 @@ pub const FormatOptions = struct { if (try arg1.getBooleanLoose(globalThis, "sorted")) |opt| { formatOptions.ordered_properties = opt; } - if (try arg1.getBooleanLoose(globalThis, "compact")) |opt| { formatOptions.single_line = opt; } @@ -2148,7 +2147,7 @@ pub const Formatter = struct { this.addForNewLine(description.len + "()".len); writer.print(comptime Output.prettyFmt("Symbol({any})", enable_ansi_colors), .{description}); } else { - writer.print(comptime Output.prettyFmt("Symbol", enable_ansi_colors), .{}); + writer.print(comptime Output.prettyFmt("Symbol()", enable_ansi_colors), .{}); } }, .Error => { diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 13d6f66d791805..cc357501373502 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -535,6 +535,19 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J return ret; } +export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) ZigString { + // very stable memory address + var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable; + var buffered_writer = MutableString.BufferedWriter{ .context = &array }; + const writer = buffered_writer.writer(); + + var formatter = ConsoleObject.Formatter{ .globalThis = globalThis }; + writer.print("{}", .{value.toFmt(&formatter)}) catch return ZigString.Empty; + buffered_writer.flush() catch return ZigString.Empty; + + return ZigString.init(array.slice()).withEncoding(); +} + pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, inspect); var str = ZigString.init("nodejs.util.inspect.custom"); diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index cd2ad0ee1fe16e..a00fb2fc4cb780 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -175,6 +175,9 @@ JSObject* createError(Zig::JSGlobalObject* globalObject, ErrorCode code, JSC::JS return createError(vm, globalObject, code, message); } +// export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) ZigString +extern "C" ZigString Bun__inspect(JSC::JSGlobalObject* globalObject, JSValue value); + WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) { ASSERT(!arg.isEmpty()); @@ -201,14 +204,14 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) auto name = JSC::getCalculatedDisplayName(vm, cell->getObject()); if (catchScope.exception()) { catchScope.clearException(); - name = "Function"_s; + name = ""_s; } if (!name.isNull() && name.length() > 0) { return makeString("[Function: "_s, name, ']'); } - return "Function"_s; + return "[Function: (anonymous)]"_s; break; } @@ -217,34 +220,133 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) } } - return arg.toWTFStringForConsole(globalObject); + // return arg.toWTFString(globalObject); // this threw an exception on `{ __proto__: null }` + ZigString zstring = Bun__inspect(globalObject, arg); + BunString bstring(BunStringTag::ZigString, BunStringImpl(zstring)); + return bstring.toWTFString(); +} + +WTF::String determineSpecificType(JSC::JSGlobalObject* globalObject, JSValue value) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + if (value.isNull()) { + return String("null"_s); + } + if (value.isUndefined()) { + return String("undefined"_s); + } + if (value.isBigInt()) { + auto str = value.toStringOrNull(globalObject); + if (!str) return {}; + return makeString("type bigint ("_s, str->getString(globalObject), "n)"_s); + } + if (value.isNumber()) { + double d = value.asNumber(); + double infinity = std::numeric_limits::infinity(); + if (value == 0) return (1 / d == -infinity) ? String("type number (-0)"_s) : String("type number (0)"_s); + if (d != d) return String("type number (NaN)"_s); + if (d == infinity) return String("type number (Infinity)"_s); + if (d == -infinity) return String("type number (-Infinity)"_s); + auto str = value.toStringOrNull(globalObject); + if (!str) return {}; + return makeString("type number ("_s, str->getString(globalObject), ")"_s); + } + if (value.isBoolean()) { + return value.asBoolean() ? String("type boolean (true)"_s) : String("type boolean (false)"_s); + } + if (value.isSymbol()) { + auto cell = value.asCell(); + auto symbol = jsCast(cell); + auto result = symbol->tryGetDescriptiveString(); + auto description = result.has_value() ? result.value() : String("Symbol()"_s); + return makeString("type symbol ("_s, description, ")"_s); + } + if (value.isCallable()) { + auto& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + auto cell = value.asCell(); + auto name = JSC::getCalculatedDisplayName(vm, cell->getObject()); + if (scope.exception()) { + scope.clearException(); + name = String(""_s); + } + if (!name.isNull() && name.length() > 0) { + return makeString("function "_s, name); + } + return String("function"_s); + } + if (value.isObject()) { + auto constructor = value.get(globalObject, vm.propertyNames->constructor); + RETURN_IF_EXCEPTION(scope, {}); + if (constructor.toBoolean(globalObject)) { + auto name = constructor.get(globalObject, vm.propertyNames->name); + RETURN_IF_EXCEPTION(scope, {}); + auto str = name.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + return makeString("an instance of "_s, str->getString(globalObject)); + } + // return `${lazyInternalUtilInspect().inspect(value, { depth: -1 })}`; + auto str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return str; + } + if (value.isString()) { + auto str = value.toString(globalObject)->getString(globalObject); + if (str.length() > 28) { + str = str.substring(0, 25); + str = makeString(str, "..."_s); + if (!str.contains('\'')) { + return makeString("type string ('"_s, str, "')"_s); + } + } + // return `type string (${JSONStringify(value)})`; + str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return makeString("type string ("_s, str, ")"_s); + } + + // value = lazyInternalUtilInspect().inspect(value, { colors: false }); + auto str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return str; } namespace Message { WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, const StringView& arg_name, const StringView& expected_type, JSValue actual_value) { - auto actual_value_string = JSValueToStringSafe(globalObject, actual_value); + auto actual_value_string = determineSpecificType(globalObject, actual_value); RETURN_IF_EXCEPTION(scope, {}); - return makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received: "_s, actual_value_string); + return makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value_string); } WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, const StringView& arg_name, ArgList expected_types, JSValue actual_value) { WTF::StringBuilder result; - auto actual_value_string = JSValueToStringSafe(globalObject, actual_value); + auto actual_value_string = determineSpecificType(globalObject, actual_value); RETURN_IF_EXCEPTION(scope, {}); - result.append("The \""_s, arg_name, "\" argument must be of type "_s); + result.append("The "_s); + + if (arg_name.contains(' ')) { + result.append(arg_name); + } else { + result.append("\""_s); + result.append(arg_name); + result.append("\" argument"_s); + } + result.append(" must be of type "_s); unsigned length = expected_types.size(); if (length == 1) { result.append(expected_types.at(0).toWTFString(globalObject)); } else if (length == 2) { result.append(expected_types.at(0).toWTFString(globalObject)); - result.append(" or "_s); + result.append(", or "_s); result.append(expected_types.at(1).toWTFString(globalObject)); } else { for (unsigned i = 0; i < length - 1; i++) { @@ -253,11 +355,11 @@ WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* gl JSValue expected_type = expected_types.at(i); result.append(expected_type.toWTFString(globalObject)); } - result.append(" or "_s); + result.append(", or "_s); result.append(expected_types.at(length - 1).toWTFString(globalObject)); } - result.append(". Received: "_s, actual_value_string); + result.append(". Received "_s, actual_value_string); return result.toString(); } @@ -295,20 +397,26 @@ 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 throwCode(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ErrorCode code, const WTF::String& message) +{ + throwScope.throwException(globalObject, createError(globalObject, code, message)); + return {}; +} + JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value) { auto arg_kind = arg_name.startsWith("options."_s) ? "property"_s : "argument"_s; auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; - auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + auto actual_value = determineSpecificType(globalObject, val_actual_value); RETURN_IF_EXCEPTION(throwScope, {}); auto message = makeString("The \""_s, arg_name, "\" "_s, arg_kind, " must be "_s, ty_kind, " "_s, expected_type, ". Received "_s, actual_value); @@ -324,7 +432,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; - auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + auto actual_value = determineSpecificType(globalObject, val_actual_value); RETURN_IF_EXCEPTION(throwScope, {}); auto message = makeString("The \""_s, arg_name, "\" "_s, arg_kind, " must be "_s, ty_kind, " "_s, expected_type, ". Received "_s, actual_value); @@ -397,7 +505,7 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec return {}; } -JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason) +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name, JSC::JSValue value, const WTF::String& reason) { ASCIILiteral type = String(name).find('.') != notFound ? "property"_s : "argument"_s; @@ -442,8 +550,12 @@ JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalOb return {}; } -JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name) { + if (!name.isEmpty()) { + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, makeString("\""_s, name, "\" is outside of buffer bounds"_s))); + return {}; + } throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); return {}; } diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index a3f4f8a67eba91..accf7b56ae8b29 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -75,19 +75,21 @@ enum Bound { namespace ERR { +JSC::EncodedJSValue throwCode(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ErrorCode code, const WTF::String& message); + JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value); -JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value); -JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, double lower, double upper, JSC::JSValue actual); -JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, double lower, double upper, JSC::JSValue actual); -JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double bound_num, Bound bound, JSC::JSValue actual); +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, const WTF::String& val_expected_type, JSC::JSValue val_actual_value); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& 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, size_t bound_num, Bound bound, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, const WTF::String& msg, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name_val, const WTF::String& msg, JSC::JSValue actual); -JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& encoding); JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& statemsg); JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); -JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name); JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase = false); JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue port, bool allowZero); diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index fb0c0b5fe9dfb9..13ba5b51954242 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -77,6 +77,8 @@ // #include #include +extern "C" bool Bun__Node__ZeroFillBuffers; + using namespace JSC; using namespace WebCore; @@ -210,6 +212,7 @@ static JSUint8Array* allocBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_ return uint8Array; } + static JSUint8Array* allocBufferUnsafe(JSC::JSGlobalObject* lexicalGlobalObject, size_t byteLength) { @@ -248,7 +251,7 @@ static int normalizeCompareVal(int val, size_t a_length, size_t b_length) return val; } -static inline WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) +static WebCore::BufferEncodingType parseEncoding(JSC::ThrowScope& scope, JSC::JSGlobalObject* lexicalGlobalObject, JSValue arg, bool validateUnknown) { auto arg_ = arg.toStringOrNull(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); @@ -256,6 +259,10 @@ static inline WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lex std::optional encoded = parseEnumeration2(*lexicalGlobalObject, arg_s); if (UNLIKELY(!encoded)) { + if (validateUnknown) { + Bun::V::validateString(scope, lexicalGlobalObject, arg, "encoding"_s); + RETURN_IF_EXCEPTION(scope, WebCore::BufferEncodingType::utf8); + } Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, arg_s); return WebCore::BufferEncodingType::utf8; } @@ -263,6 +270,19 @@ static inline WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lex return encoded.value(); } +JSC::EncodedJSValue validateOffset(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max) +{ + if (min.isUndefined()) min = jsDoubleNumber(0); + if (max.isUndefined()) max = jsDoubleNumber(Bun::Buffer::kMaxLength); + return Bun::V::validateInteger(scope, globalObject, value, name, min, max); +} +JSC::EncodedJSValue validateOffset(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, const WTF::String& name, JSC::JSValue min, JSC::JSValue max) +{ + if (min.isUndefined()) min = jsDoubleNumber(0); + if (max.isUndefined()) max = jsDoubleNumber(Bun::Buffer::kMaxLength); + return Bun::V::validateInteger(scope, globalObject, value, name, min, max); +} + namespace WebCore { using namespace JSC; @@ -426,11 +446,12 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocUnsafeBody(JS VM& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); JSValue lengthValue = callFrame->argument(0); - Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); auto result = allocBufferUnsafe(lexicalGlobalObject, length); RETURN_IF_EXCEPTION(throwScope, {}); + if (Bun__Node__ZeroFillBuffers) memset(result->typedVector(), 0, length); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } @@ -442,7 +463,7 @@ static inline JSC::EncodedJSValue constructBufferEmpty(JSGlobalObject* lexicalGl static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalObject, JSString* str, WebCore::BufferEncodingType encoding) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); const auto& view = str->tryGetValue(lexicalGlobalObject); @@ -513,7 +534,7 @@ static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalOb static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSValue arg0, JSValue arg1) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; auto scope = DECLARE_THROW_SCOPE(vm); @@ -546,10 +567,16 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG auto scope = DECLARE_THROW_SCOPE(vm); JSValue lengthValue = callFrame->argument(0); - Bun::V::validateNumber(scope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(scope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(scope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); + if (length == 0) { + auto* uint8Array = createEmptyBuffer(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(uint8Array)); + } + // fill argument if (UNLIKELY(callFrame->argumentCount() > 1)) { auto* uint8Array = createUninitializedBuffer(lexicalGlobalObject, length); @@ -565,7 +592,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG if (callFrame->argumentCount() > 2) { EnsureStillAliveScope arg2 = callFrame->uncheckedArgument(2); if (!arg2.value().isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, arg2.value()); + encoding = parseEncoding(scope, lexicalGlobalObject, arg2.value(), true); RETURN_IF_EXCEPTION(scope, {}); } } @@ -579,8 +606,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG ZigString str = Zig::toZigString(str_); if (UNLIKELY(!Bun__Buffer_fill(&str, startPtr, end - start, encoding))) { - throwTypeError(lexicalGlobalObject, scope, "Failed to decode value"_s); - return {}; + return Bun::ERR::throwCode(scope, lexicalGlobalObject, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, "Failed to decode value"_s); } } else if (auto* view = JSC::jsDynamicCast(value)) { if (UNLIKELY(view->isDetached())) { @@ -590,8 +616,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG size_t length = view->byteLength(); if (UNLIKELY(length == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Buffer cannot be empty"_s); - return {}; + return Bun::ERR::throwCode(scope, lexicalGlobalObject, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, "Buffer cannot be empty"_s); } auto* start = uint8Array->typedVector(); @@ -660,9 +685,10 @@ static inline JSC::EncodedJSValue jsBufferByteLengthFromStringAndEncoding(JSC::J return {}; } + static inline JSC::EncodedJSValue jsBufferConstructorFunction_byteLengthBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; @@ -700,7 +726,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_byteLengthBody(JSC static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); auto castedThisValue = callFrame->argument(0); @@ -748,20 +774,15 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - if (callFrame->argumentCount() < 1) { - return constructBufferEmpty(lexicalGlobalObject); - } - auto arrayValue = callFrame->uncheckedArgument(0); - auto array = JSC::jsDynamicCast(arrayValue); - if (!array) { - throwTypeError(lexicalGlobalObject, throwScope, "Argument must be an array"_s); - return {}; - } + auto listValue = callFrame->argument(0); + Bun::V::validateArray(throwScope, lexicalGlobalObject, listValue, "list"_s, jsUndefined()); + RETURN_IF_EXCEPTION(throwScope, {}); + auto array = JSC::jsDynamicCast(listValue); size_t arrayLength = array->length(); if (arrayLength < 1) { RELEASE_AND_RETURN(throwScope, constructBufferEmpty(lexicalGlobalObject)); @@ -786,8 +807,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JS auto* typedArray = JSC::jsDynamicCast(element); if (!typedArray) { - throwTypeError(lexicalGlobalObject, throwScope, "Buffer.concat expects Uint8Array"_s); - return {}; + return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, makeString("list["_s, i, "]"_s), "Buffer or Uint8Array"_s, element); } if (UNLIKELY(typedArray->isDetached())) { @@ -835,7 +855,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JS size_t remain = byteLength; auto* head = outBuffer->typedVector(); - const int arrayLengthI = arrayLength; + const int arrayLengthI = args.size(); for (int i = 0; i < arrayLengthI && remain > 0; i++) { auto* typedArray = JSC::jsCast(args.at(i)); size_t length = std::min(remain, typedArray->length()); @@ -856,7 +876,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JS // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L337 static inline JSC::EncodedJSValue jsBufferConstructorFunction_copyBytesFromBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); auto viewValue = callFrame->argument(0); @@ -880,7 +900,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_copyBytesFromBody( if (!offsetValue.isUndefined() || !lengthValue.isUndefined()) { if (!offsetValue.isUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, offsetValue, jsString(vm, String("offset"_s)), jsNumber(0), jsUndefined()); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, offsetValue, "offset"_s, jsNumber(0), jsUndefined()); RETURN_IF_EXCEPTION(throwScope, {}); offset = offsetValue.asNumber(); if (offset >= viewLength) return JSValue::encode(createEmptyBuffer(lexicalGlobalObject)); @@ -890,7 +910,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_copyBytesFromBody( double end = 0; if (!lengthValue.isUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("length"_s)), jsNumber(0), jsUndefined()); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, lengthValue, "length"_s, jsNumber(0), jsUndefined()); RETURN_IF_EXCEPTION(throwScope, {}); length = lengthValue.asNumber(); end = offset + length; @@ -960,7 +980,7 @@ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBufferPrototype, JSBufferPrototype::Base); static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); auto arg0 = callFrame->argument(0); @@ -992,7 +1012,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG default: sourceEndValue = callFrame->uncheckedArgument(4); if (sourceEndValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceEndValue, jsString(vm, String("sourceEnd"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceEndValue, "sourceEnd"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); sourceEnd = sourceEndValue.asNumber(); } @@ -1001,7 +1021,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 4: sourceStartValue = callFrame->uncheckedArgument(3); if (sourceStartValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceStartValue, jsString(vm, String("sourceStart"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceStartValue, "sourceStart"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); sourceStart = sourceStartValue.asNumber(); } @@ -1010,7 +1030,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 3: targetEndValue = callFrame->uncheckedArgument(2); if (targetEndValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetEndValue, jsString(vm, String("targetEnd"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetEndValue, "targetEnd"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); targetEnd = targetEndValue.asNumber(); } @@ -1019,7 +1039,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 2: targetStartValue = callFrame->uncheckedArgument(1); if (targetStartValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetStartValue, jsString(vm, String("targetStart"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetStartValue, "targetStart"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); targetStart = targetStartValue.asNumber(); } @@ -1072,7 +1092,7 @@ static double toInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObjec // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L205 static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); auto targetValue = callFrame->argument(0); @@ -1138,7 +1158,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob static inline JSC::EncodedJSValue jsBufferPrototypeFunction_equalsBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); if (callFrame->argumentCount() < 1) { throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); @@ -1170,9 +1190,10 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_equalsBody(JSC::JSGl RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsBoolean(normalizeCompareVal(result, a_length, b_length) == 0))); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (callFrame->argumentCount() < 1) { @@ -1211,20 +1232,19 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob } if (!encodingValue.isUndefined() && value.isString()) { - if (!encodingValue.isString()) return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "encoding"_s, "string"_s, encodingValue); - encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); + encoding = parseEncoding(scope, lexicalGlobalObject, encodingValue, true); RETURN_IF_EXCEPTION(scope, {}); } // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L1066-L1079 // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L122 if (!offsetValue.isUndefined()) { - Bun::V::validateNumber(scope, lexicalGlobalObject, offsetValue, jsString(vm, String("offset"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(scope, lexicalGlobalObject, offsetValue, "offset"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(scope, {}); offset = offsetValue.toLength(lexicalGlobalObject); } if (!endValue.isUndefined()) { - Bun::V::validateNumber(scope, lexicalGlobalObject, endValue, jsString(vm, String("end"_s)), jsNumber(0), jsNumber(limit)); + Bun::V::validateNumber(scope, lexicalGlobalObject, endValue, "end"_s, jsNumber(0), jsNumber(limit)); RETURN_IF_EXCEPTION(scope, {}); end = endValue.toLength(lexicalGlobalObject); } @@ -1255,8 +1275,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob size_t length = view->byteLength(); if (UNLIKELY(length == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Buffer cannot be empty"_s); - return {}; + return Bun::ERR::throwCode(scope, lexicalGlobalObject, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, "Buffer cannot be empty"_s); } length = std::min(length, remain); @@ -1300,6 +1319,28 @@ static int64_t indexOf(const uint8_t* thisPtr, int64_t thisLength, const uint8_t return -1; } +static int64_t indexOf16(const uint8_t* thisPtr, int64_t thisLength, const uint8_t* valuePtr, int64_t valueLength, int64_t byteOffset) +{ + size_t finalresult = 0; + if (thisLength == 1) return -1; + thisLength = thisLength / 2 * 2; + if (valueLength == 1) return -1; + valueLength = valueLength / 2 * 2; + byteOffset = byteOffset / 2 * 2; + while (true) { + auto res = indexOf(thisPtr, thisLength, valuePtr, valueLength, byteOffset); + if (res == -1) return -1; + if (res % 2 == 1) { + thisPtr += res + 1; + thisLength -= res + 1; + finalresult += res + 1; + continue; + } + finalresult += res; + return finalresult; + } +} + static int64_t lastIndexOf(const uint8_t* thisPtr, int64_t thisLength, const uint8_t* valuePtr, int64_t valueLength, int64_t byteOffset) { auto start = thisPtr; @@ -1311,108 +1352,128 @@ static int64_t lastIndexOf(const uint8_t* thisPtr, int64_t thisLength, const uin return -1; } -static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis, bool last) +static ssize_t indexOfOffset(size_t length, ssize_t offset_i64, ssize_t needle_length, bool is_forward) { - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - if (callFrame->argumentCount() < 1) { - throwVMError(lexicalGlobalObject, scope, createNotEnoughArgumentsError(lexicalGlobalObject)); - return -1; + ssize_t length_i64 = static_cast(length); + if (offset_i64 < 0) { + if (offset_i64 + length_i64 >= 0) { + // Negative offsets count backwards from the end of the buffer. + return length_i64 + offset_i64; + } else if (is_forward || needle_length == 0) { + // indexOf from before the start of the buffer: search the whole buffer. + return 0; + } else { + // lastIndexOf from before the start of the buffer: no match. + return -1; + } + } else { + if (offset_i64 + needle_length <= length_i64) { + // Valid positive offset. + return offset_i64; + } else if (needle_length == 0) { + // Out of buffer bounds, but empty needle: point to end of buffer. + return length_i64; + } else if (is_forward) { + // indexOf from past the end of the buffer: no match. + return -1; + } else { + // lastIndexOf from past the end of the buffer: search the whole buffer. + return length_i64 - 1; + } } +} - auto value = callFrame->uncheckedArgument(0); - WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; +static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter buffer, bool last) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + bool dir = !last; + const uint8_t* typedVector = buffer->typedVector(); + size_t byteLength = buffer->byteLength(); + std::optional encoding = std::nullopt; - int64_t length = static_cast(castedThis->byteLength()); - const uint8_t* typedVector = castedThis->typedVector(); + if (byteLength == 0) return -1; - int64_t byteOffset = last ? length - 1 : 0; + auto valueValue = callFrame->argument(0); + auto byteOffsetValue = callFrame->argument(1); + auto encodingValue = callFrame->argument(2); - if (callFrame->argumentCount() > 1) { - EnsureStillAliveScope arg1 = callFrame->uncheckedArgument(1); - if (arg1.value().isString()) { - encoding = parseEncoding(lexicalGlobalObject, scope, arg1.value()); - RETURN_IF_EXCEPTION(scope, -1); - } else { - auto byteOffset_ = arg1.value().toNumber(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, -1); - - if (std::isnan(byteOffset_) || std::isinf(byteOffset_)) { - byteOffset = last ? length - 1 : 0; - } else if (byteOffset_ < 0) { - byteOffset = length + static_cast(byteOffset_); - } else { - byteOffset = static_cast(byteOffset_); - } + if (byteOffsetValue.isString()) { + encodingValue = byteOffsetValue; + byteOffsetValue = jsUndefined(); + } else { + double byteOffset = byteOffsetValue.toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, -1); + if (byteOffset > 0x7fffffffp0f) byteOffsetValue = jsDoubleNumber(0x7fffffffp0f); + if (byteOffset < -0x80000000p0f) byteOffsetValue = jsDoubleNumber(-0x80000000p0f); + } - if (last) { - if (byteOffset < 0) { - return -1; - } else if (byteOffset > length - 1) { - byteOffset = length - 1; - } - } else { - if (byteOffset <= 0) { - byteOffset = 0; - } else if (byteOffset > length - 1) { - return -1; - } - } + byteOffsetValue = jsDoubleNumber(byteOffsetValue.toNumber(lexicalGlobalObject)); + RETURN_IF_EXCEPTION(scope, -1); + if (std::isnan(byteOffsetValue.asNumber())) byteOffsetValue = jsNumber(dir ? 0 : byteLength); - if (callFrame->argumentCount() > 2) { - EnsureStillAliveScope encodingValue = callFrame->uncheckedArgument(2); - if (!encodingValue.value().isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue.value()); - RETURN_IF_EXCEPTION(scope, -1); - } + if (valueValue.isNumber()) { + ssize_t byteOffset = indexOfOffset(byteLength, byteOffsetValue.asNumber(), 1, dir); + if (byteOffset == -1) return -1; + uint8_t byteValue = (valueValue.toInt32(lexicalGlobalObject)) % 256; + RETURN_IF_EXCEPTION(scope, -1); + if (last) { + for (int64_t i = byteOffset; i >= 0; --i) { + if (byteValue == typedVector[i]) return i; } + } else { + const void* offset = memchr(reinterpret_cast(typedVector + byteOffset), byteValue, byteLength - byteOffset); + if (offset != NULL) return static_cast(offset) - typedVector; } + return -1; } - if (value.isString()) { - auto* str = value.toStringOrNull(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, -1); + WTF::String encodingString; + if (!encodingValue.isUndefined()) { + encodingString = encodingValue.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + encoding = parseEnumeration2(*lexicalGlobalObject, encodingString); + } else { + encoding = BufferEncodingType::utf8; + } - JSC::EncodedJSValue encodedBuffer = constructFromEncoding(lexicalGlobalObject, str, encoding); + if (valueValue.isString()) { + if (!encoding.has_value()) { + return Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, encodingString); + } + auto* str = valueValue.toStringOrNull(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, -1); + ssize_t byteOffset = indexOfOffset(byteLength, byteOffsetValue.asNumber(), str->length(), dir); + if (byteOffset == -1) return -1; + if (str->length() == 0) return byteOffset; + JSC::EncodedJSValue encodedBuffer = constructFromEncoding(lexicalGlobalObject, str, encoding.value()); auto* arrayValue = JSC::jsDynamicCast(JSC::JSValue::decode(encodedBuffer)); int64_t lengthValue = static_cast(arrayValue->byteLength()); const uint8_t* typedVectorValue = arrayValue->typedVector(); if (last) { - return lastIndexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset); + return lastIndexOf(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); } else { - return indexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset); - } - } else if (value.isNumber()) { - uint8_t byteValue = static_cast((value.toInt32(lexicalGlobalObject)) % 256); - RETURN_IF_EXCEPTION(scope, -1); - - if (last) { - for (int64_t i = byteOffset; i >= 0; --i) { - if (byteValue == typedVector[i]) { - return i; - } - } - } else { - const void* offset = memchr(reinterpret_cast(typedVector + byteOffset), byteValue, length - byteOffset); - if (offset != NULL) { - return static_cast(static_cast(offset) - typedVector); - } + if (encoding.value() == BufferEncodingType::ucs2) return indexOf16(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); + return indexOf(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); } + } - return -1; - } else if (auto* arrayValue = JSC::jsDynamicCast(value)) { - size_t lengthValue = arrayValue->byteLength(); - const uint8_t* typedVectorValue = arrayValue->typedVector(); + if (auto* array = JSC::jsDynamicCast(valueValue)) { + if (!encoding.has_value()) encoding = BufferEncodingType::utf8; + size_t lengthValue = array->byteLength(); + ssize_t byteOffset = indexOfOffset(byteLength, byteOffsetValue.asNumber(), lengthValue, dir); + if (byteOffset == -1) return -1; + if (lengthValue == 0) return byteOffset; + const uint8_t* typedVectorValue = array->typedVector(); if (last) { - return lastIndexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset); + return lastIndexOf(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); } else { - return indexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset); + if (encoding.value() == BufferEncodingType::ucs2) return indexOf16(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); + return indexOf(typedVector, byteLength, typedVectorValue, lengthValue, byteOffset); } - } else { - throwTypeError(lexicalGlobalObject, scope, "Invalid value type"_s); - return -1; } + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "value"_s, "number, string, Buffer, or Uint8Array"_s, valueValue); return -1; } @@ -1421,19 +1482,22 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_includesBody(JSC::JS auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, false); return JSC::JSValue::encode(jsBoolean(index != -1)); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_indexOfBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, false); return JSC::JSValue::encode(jsNumber(index)); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_lastIndexOfBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, true); return JSC::JSValue::encode(jsNumber(index)); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap16Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); constexpr int elemSize = 2; @@ -1460,9 +1524,10 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap16Body(JSC::JSGl return JSC::JSValue::encode(castedThis); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap32Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); constexpr int elemSize = 4; @@ -1494,9 +1559,10 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap32Body(JSC::JSGl return JSC::JSValue::encode(castedThis); } + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); constexpr size_t elemSize = 8; @@ -1623,7 +1689,7 @@ bool inline parseArrayIndex(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalO // using byteLength and byte offsets here is intentional static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); uint32_t start = 0; uint32_t end = castedThis->byteLength(); @@ -1643,7 +1709,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS return jsBufferToString(vm, lexicalGlobalObject, castedThis, start, end, encoding); if (!arg1.isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, arg1); + encoding = parseEncoding(scope, lexicalGlobalObject, arg1, false); RETURN_IF_EXCEPTION(scope, {}); } @@ -1677,7 +1743,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS template static inline JSC::EncodedJSValue jsBufferPrototypeFunction_SliceWithEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* castedThis = JSC::jsDynamicCast(callFrame->thisValue()); const JSValue startValue = callFrame->argument(0); @@ -1769,7 +1835,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeEncodingBody(JS template static inline JSC::EncodedJSValue jsBufferPrototypeFunctionWriteWithEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - auto& vm = JSC::getVM(lexicalGlobalObject); + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* castedThis = JSC::jsDynamicCast(callFrame->thisValue()); @@ -1794,93 +1860,64 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunctionWriteWithEncoding(JSC static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { - auto& vm = JSC::getVM(lexicalGlobalObject); - uint32_t offset = 0; - uint32_t length = castedThis->byteLength(); - uint32_t max = length; - WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; + auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (UNLIKELY(callFrame->argumentCount() == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Not enough arguments"_s); - return {}; - } - - EnsureStillAliveScope arg0 = callFrame->argument(0); - auto* str = arg0.value().toStringOrNull(lexicalGlobalObject); - if (!str) { - throwTypeError(lexicalGlobalObject, scope, "write() expects a string"_s); - return {}; - } - - JSValue offsetValue = jsUndefined(); - JSValue lengthValue = jsUndefined(); - JSValue encodingValue = jsUndefined(); - - switch (callFrame->argumentCount()) { - case 4: - encodingValue = callFrame->uncheckedArgument(3); - FALLTHROUGH; - case 3: - lengthValue = callFrame->uncheckedArgument(2); - FALLTHROUGH; - case 2: - offsetValue = callFrame->uncheckedArgument(1); - break; - default: - break; - } - - auto setEncoding = [&]() { - if (!encodingValue.isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); - } - }; + auto stringValue = callFrame->argument(0); + auto offsetValue = callFrame->argument(1); + auto lengthValue = callFrame->argument(2); + auto encodingValue = callFrame->argument(3); if (offsetValue.isUndefined()) { - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1053 - RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); + Bun::V::validateString(scope, lexicalGlobalObject, stringValue, "string"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* str = stringValue.toString(lexicalGlobalObject); + uint32_t offset = 0; + uint32_t length = castedThis->byteLength(); + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, WebCore::BufferEncodingType::utf8)); } - if (lengthValue.isUndefined() && offsetValue.isString()) { - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1056 encodingValue = offsetValue; - setEncoding(); + lengthValue = jsNumber(castedThis->byteLength()); + offsetValue = jsNumber(0); + } else { + uint32_t length = castedThis->byteLength(); + validateOffset(scope, lexicalGlobalObject, offsetValue, "offset"_s, jsNumber(0), jsNumber(length)); RETURN_IF_EXCEPTION(scope, {}); - RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); - } + uint32_t offset = offsetValue.toUInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + uint32_t remaining = castedThis->byteLength() - offset; - if (UNLIKELY(!offsetValue.isNumber())) { - throwTypeError(lexicalGlobalObject, scope, "Invalid offset"_s); - return {}; + if (lengthValue.isUndefined()) { + lengthValue = jsNumber(remaining); + } else if (lengthValue.isString()) { + encodingValue = lengthValue; + lengthValue = jsNumber(remaining); + } else { + validateOffset(scope, lexicalGlobalObject, lengthValue, "length"_s, jsNumber(0), jsNumber(length)); + RETURN_IF_EXCEPTION(scope, {}); + length = lengthValue.toUInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + if (length > remaining) lengthValue = jsNumber(remaining); + } } - int32_t userOffset = offsetValue.toInt32(lexicalGlobalObject); + Bun::V::validateString(scope, lexicalGlobalObject, stringValue, "string"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* str = stringValue.toString(lexicalGlobalObject); + uint32_t offset = offsetValue.toUInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + uint32_t length = lengthValue.toUInt32(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); - if (userOffset < 0 || userOffset > max) { - throwNodeRangeError(lexicalGlobalObject, scope, "Offset is out of bounds"_s); - return {}; - } - offset = static_cast(userOffset); - uint32_t remaining = max - static_cast(userOffset); - - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1062-L1077 - if (lengthValue.isUndefined()) { - length = remaining; - } else if (lengthValue.isString()) { - encodingValue = lengthValue; - setEncoding(); - RETURN_IF_EXCEPTION(scope, {}); - length = remaining; - } else { - setEncoding(); - int32_t userLength = lengthValue.toInt32(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - length = std::min(static_cast(userLength), remaining); + if (!encodingValue.toBoolean(lexicalGlobalObject)) { + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, WebCore::BufferEncodingType::utf8)); } + auto encoding = parseEncoding(scope, lexicalGlobalObject, encodingValue, false); + RETURN_IF_EXCEPTION(scope, {}); + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); } @@ -2210,7 +2247,6 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "swap32"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap32, 0 } }, { "swap64"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap64, 0 } }, { "toJSON"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeToJSONCodeGenerator, 1 } }, - { "toLocaleString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_toString, 4 } }, { "toString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_toString, 4 } }, { "ucs2Slice"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_utf16leSlice, 2 } }, { "ucs2Write"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_utf16leWrite, 3 } }, @@ -2262,6 +2298,8 @@ void JSBufferPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalThis) JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); reifyStaticProperties(vm, JSBuffer::info(), JSBufferPrototypeTableValues, *this); + ALIAS("toLocaleString", "toString"); + ALIAS("readUintBE", "readUIntBE"); ALIAS("readUintLE", "readUIntLE"); ALIAS("readUint8", "readUInt8"); @@ -2339,6 +2377,128 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO } // namespace WebCore +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_Array, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + auto arrayValue = callFrame->argument(0); + + return constructBufferFromArray(throwScope, lexicalGlobalObject, arrayValue); +} +EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue) +{ + auto* globalObject = reinterpret_cast(lexicalGlobalObject); + + auto* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject); + MarkedArgumentBuffer argsBuffer; + argsBuffer.append(arrayValue); + JSValue target = globalObject->JSBufferConstructor(); + // TODO: I wish we could avoid this - it adds ~30ns of overhead just using JSC::construct. + auto* object = JSC::construct(lexicalGlobalObject, constructor, target, argsBuffer, "Buffer failed to construct"_s); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object)); +} +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_ArrayBuffer, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto argsCount = callFrame->argumentCount(); + + auto arrayBufferValue = callFrame->argument(0); + auto offsetValue = callFrame->argument(1); + auto lengthValue = callFrame->argument(2); + + return constructBufferFromArrayBuffer(throwScope, lexicalGlobalObject, argsCount, arrayBufferValue, offsetValue, lengthValue); +} +EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, size_t argsCount, JSValue arrayBufferValue, JSValue offsetValue, JSValue lengthValue) +{ + auto* globalObject = reinterpret_cast(lexicalGlobalObject); + + auto* jsBuffer = jsCast(arrayBufferValue.asCell()); + RefPtr buffer = jsBuffer->impl(); + if (buffer->isDetached()) { + // TOOD: return Node.js error + throwTypeError(globalObject, throwScope, "Buffer is detached"_s); + return {}; + } + size_t byteLength = buffer->byteLength(); + double byteLengthD = byteLength; + + // This closely matches `new Uint8Array(buffer, byteOffset, length)` in JavaScriptCore's implementation. + // See Source/JavaScriptCore/runtime/JSGenericTypedArrayViewConstructorInlines.h + size_t offset = 0; + std::optional length; + size_t maxLength = byteLength; + if (argsCount > 1) { + + if (!offsetValue.isUndefined()) { + offsetValue = jsDoubleNumber(offsetValue.toNumber(lexicalGlobalObject)); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(offsetValue.asNumber())) offsetValue = jsNumber(0); + if ((byteLengthD - offsetValue.asNumber()) < 0) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "offset"_s); + maxLength = byteLengthD - offsetValue.asNumber(); + offset = offsetValue.asNumber(); + } + + if (!lengthValue.isUndefined()) { + lengthValue = jsDoubleNumber(lengthValue.toNumber(lexicalGlobalObject)); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(lengthValue.asNumber())) lengthValue = jsNumber(0); + length = lengthValue.asNumber(); + if (length > 0) { + if (length > maxLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "length"_s); + } + } else { + length = 0; + } + } + } + + if (offset > byteLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "offset"_s); + } + + if (!length) { + if (buffer->isResizableOrGrowableShared()) { + if (UNLIKELY(offset > byteLength)) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "byteOffset"_s); + } + } else { + length = (byteLength - offset); + } + } + if (offset + length.value() > byteLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "length"_s); + } + + auto* subclassStructure = globalObject->JSBufferSubclassStructure(); + auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(buffer), offset, length); + if (UNLIKELY(!uint8Array)) { + throwOutOfMemoryError(globalObject, throwScope); + return JSC::JSValue::encode({}); + } + + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); +} +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_Size, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto lengthValue = callFrame->argument(0); + Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + RETURN_IF_EXCEPTION(throwScope, {}); + size_t length = lengthValue.toLength(lexicalGlobalObject); + return JSBuffer__bufferFromLength(lexicalGlobalObject, length); +} +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_String, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto stringValue = callFrame->argument(0); + auto encodingValue = callFrame->argument(1); + return constructBufferFromStringAndEncoding(lexicalGlobalObject, stringValue, encodingValue); +} + static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSValue newTarget, ArgList args) { VM& vm = lexicalGlobalObject->vm(); @@ -2359,7 +2519,7 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()); } else if (distinguishingArg.isNumber()) { JSValue lengthValue = distinguishingArg; - Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); return JSBuffer__bufferFromLength(lexicalGlobalObject, length); @@ -2435,56 +2595,7 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi case ArrayBufferType: { // This closely matches `new Uint8Array(buffer, byteOffset, length)` in JavaScriptCore's implementation. // See Source/JavaScriptCore/runtime/JSGenericTypedArrayViewConstructorInlines.h - size_t offset = 0; - std::optional length; - if (argsCount > 1) { - - offset = args.at(1).toTypedArrayIndex(globalObject, "byteOffset"_s); - - // TOOD: return Node.js error - RETURN_IF_EXCEPTION(throwScope, {}); - - if (argsCount > 2) { - // If the length value is present but undefined, treat it as missing. - JSValue lengthValue = args.at(2); - if (!lengthValue.isUndefined()) { - length = lengthValue.toTypedArrayIndex(globalObject, "length"_s); - - // TOOD: return Node.js error - RETURN_IF_EXCEPTION(throwScope, {}); - } - } - } - - auto* jsBuffer = jsCast(distinguishingArg.asCell()); - RefPtr buffer = jsBuffer->impl(); - if (buffer->isDetached()) { - // TOOD: return Node.js error - throwTypeError(globalObject, throwScope, "Buffer is detached"_s); - return {}; - } - - if (!length) { - size_t byteLength = buffer->byteLength(); - if (buffer->isResizableOrGrowableShared()) { - if (UNLIKELY(offset > byteLength)) { - // TOOD: return Node.js error - throwNodeRangeError(globalObject, throwScope, "byteOffset exceeds source ArrayBuffer byteLength"_s); - return {}; - } - } else { - length = (byteLength - offset); - } - } - - auto* subclassStructure = globalObject->JSBufferSubclassStructure(); - auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(buffer), offset, length); - if (UNLIKELY(!uint8Array)) { - throwOutOfMemoryError(globalObject, throwScope); - return JSC::JSValue::encode({}); - } - - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); + return constructBufferFromArrayBuffer(throwScope, lexicalGlobalObject, args.size(), distinguishingArg, args.at(1), args.at(2)); } default: { break; @@ -2492,24 +2603,7 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi } } - JSC::JSObject* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject); - - MarkedArgumentBuffer argsBuffer; - argsBuffer.append(distinguishingArg); - for (size_t i = 1; i < argsCount; ++i) - argsBuffer.append(args.at(i)); - - JSValue target = newTarget; - if (!target || !target.isCell()) { - target = globalObject->JSBufferConstructor(); - } - - JSC::JSObject* object = JSC::construct(lexicalGlobalObject, constructor, target, args, "Buffer failed to construct"_s); - if (!object) { - return JSC::JSValue::encode({}); - } - - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object)); + return constructBufferFromArray(throwScope, lexicalGlobalObject, distinguishingArg); } JSC_DEFINE_HOST_FUNCTION(callJSBuffer, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/JSBuffer.h b/src/bun.js/bindings/JSBuffer.h index 04795e6143a4b5..d23358c2868d5f 100644 --- a/src/bun.js/bindings/JSBuffer.h +++ b/src/bun.js/bindings/JSBuffer.h @@ -65,3 +65,11 @@ JSC::Structure* createBufferStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSVal JSC::JSObject* createBufferConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSObject* bufferPrototype); } + +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_Array, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_ArrayBuffer, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_Size, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_BufferFrom_String, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)); + +JSC::EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* lexicalGlobalObject, size_t argsCount, JSC::JSValue arrayBufferValue, JSC::JSValue offsetValue, JSC::JSValue lengthValue); +JSC::EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue arrayValue); diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp index c259609ef63aca..6ac04baba060b8 100644 --- a/src/bun.js/bindings/NodeValidator.cpp +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -51,6 +51,24 @@ JSC::EncodedJSValue V::validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObje return JSValue::encode(jsUndefined()); } +JSC::EncodedJSValue V::validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, const WTF::String& name, JSC::JSValue min, JSC::JSValue max) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (min.isUndefined()) min = jsDoubleNumber(JSC::minSafeInteger()); + if (max.isUndefined()) max = jsDoubleNumber(JSC::maxSafeInteger()); + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + 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_validateNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -82,7 +100,27 @@ JSC::EncodedJSValue V::validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObjec if (max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, max_num, Bun::UPPER, value); return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, ""_s, value); } + return JSValue::encode(jsUndefined()); +} +JSC::EncodedJSValue V::validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, const WTF::String& name, JSValue min, JSValue max) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto min_isnonnull = !min.isUndefinedOrNull(); + auto max_isnonnull = !max.isUndefinedOrNull(); + if ((min_isnonnull && value_num < min_num) || (max_isnonnull && value_num > max_num) || ((min_isnonnull || max_isnonnull) && std::isnan(value_num))) { + if (min_isnonnull && max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + if (min_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, Bun::LOWER, value); + if (max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, max_num, Bun::UPPER, value); + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, ""_s, value); + } return JSValue::encode(jsUndefined()); } @@ -297,6 +335,30 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateArray, (JSC::JSGlobalObject * global auto value = callFrame->argument(0); auto name = callFrame->argument(1); auto minLength = callFrame->argument(2); + return V::validateArray(scope, globalObject, value, name, minLength); +} +JSC::EncodedJSValue V::validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue minLength) +{ + JSC::VM& vm = globalObject->vm(); + + if (minLength.isUndefined()) minLength = jsNumber(0); + + if (!JSC::isArray(globalObject, value)) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "Array"_s, value); + + auto length = value.get(globalObject, Identifier::fromString(vm, "length"_s)); + RETURN_IF_EXCEPTION(scope, {}); + auto length_num = length.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto minLength_num = minLength.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + if (length_num < minLength_num) { + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, value, makeString("must be longer than "_s, minLength_num)); + } + return JSValue::encode(jsUndefined()); +} +JSC::EncodedJSValue V::validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, const WTF::String& name, JSValue minLength) +{ + JSC::VM& vm = globalObject->vm(); if (minLength.isUndefined()) minLength = jsNumber(0); @@ -454,6 +516,12 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBuffer, (JSC::JSGlobalObject * globa auto buffer = callFrame->argument(0); auto name = callFrame->argument(1); + return V::validateBuffer(scope, globalObject, buffer, name); +} +JSC::EncodedJSValue V::validateBuffer(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue buffer, JSValue nameValue) +{ + auto name = nameValue.isUndefined() ? "buffer"_s : nameValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); if (!buffer.isCell()) return JSValue::encode(jsUndefined()); auto ty = buffer.asCell()->type(); diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h index b691c7f5e0dc6a..1a32502a7c4e7e 100644 --- a/src/bun.js/bindings/NodeValidator.h +++ b/src/bun.js/bindings/NodeValidator.h @@ -26,11 +26,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBuffer, (JSC::JSGlobalObject * globa namespace V { +JSC::EncodedJSValue validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, const WTF::String& name, JSC::JSValue min, JSC::JSValue max); JSC::EncodedJSValue validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max); JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max); +JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, const WTF::String& name, JSValue min, JSValue max); JSC::EncodedJSValue validateFiniteNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue number, JSC::JSValue name); JSC::EncodedJSValue validateString(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name); JSC::EncodedJSValue validateString(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); +JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue minLength); +JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, const WTF::String& name, JSValue minLength); +JSC::EncodedJSValue validateBuffer(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue buffer, JSValue name); } diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index e79bf9799f4265..00ea18daa04c23 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -654,7 +654,7 @@ pub const ZigString = extern struct { return (@intFromPtr(this._unsafe_ptr_do_not_use) & (1 << 63)) != 0; } - pub inline fn utf16Slice(this: *const ZigString) []align(1) const u16 { + pub fn utf16Slice(this: *const ZigString) []align(1) const u16 { if (comptime bun.Environment.allow_assert) { if (this.len > 0 and !this.is16Bit()) { @panic("ZigString.utf16Slice() called on a latin1 string.\nPlease use .toSlice() instead or carefully check that .is16Bit() is false first."); @@ -664,7 +664,7 @@ pub const ZigString = extern struct { return @as([*]align(1) const u16, @ptrCast(untagged(this._unsafe_ptr_do_not_use)))[0..this.len]; } - pub inline fn utf16SliceAligned(this: *const ZigString) []const u16 { + pub fn utf16SliceAligned(this: *const ZigString) []const u16 { if (comptime bun.Environment.allow_assert) { if (this.len > 0 and !this.is16Bit()) { @panic("ZigString.utf16SliceAligned() called on a latin1 string.\nPlease use .toSlice() instead or carefully check that .is16Bit() is false first."); diff --git a/src/bun.js/node/buffer.zig b/src/bun.js/node/buffer.zig index 86d4fc73c1e907..af51604049fa63 100644 --- a/src/bun.js/node/buffer.zig +++ b/src/bun.js/node/buffer.zig @@ -49,6 +49,8 @@ pub const BufferVectorized = struct { Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .hex), } catch return false; + if (written == 0 and str.length() > 0) return false; + switch (written) { 0 => return true, 1 => { diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index f50424ea35bc9a..57aa62e1229062 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -1335,7 +1335,7 @@ pub const Encoder = struct { }, .hex => { - return strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len]); + return strings.decodeHexToBytesTruncate(to_ptr[0..to_len], u8, input[0..len]); }, .base64, .base64url => { @@ -1414,7 +1414,7 @@ pub const Encoder = struct { }, .hex => { - return strings.decodeHexToBytes(to[0..to_len], u16, input[0..len]); + return strings.decodeHexToBytesTruncate(to[0..to_len], u16, input[0..len]); }, .base64, .base64url => { diff --git a/src/cli.zig b/src/cli.zig index 95c5a10a047b93..3e9c3cf926c56d 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -47,6 +47,8 @@ pub var start_time: i128 = undefined; const Bunfig = @import("./bunfig.zig").Bunfig; const OOM = bun.OOM; +export var Bun__Node__ZeroFillBuffers = false; + pub const Cli = struct { pub const CompileTarget = @import("./compile_target.zig"); var wait_group: sync.WaitGroup = undefined; @@ -232,6 +234,7 @@ pub const Arguments = struct { clap.parseParam("--fetch-preconnect ... Preconnect to a URL while code is loading") catch unreachable, clap.parseParam("--max-http-header-size Set the maximum size of HTTP headers in bytes. Default is 16KiB") catch unreachable, clap.parseParam("--expose-internals Expose internals used for testing Bun itself. Usage of these APIs are completely unsupported.") catch unreachable, + clap.parseParam("--zero-fill-buffers Boolean to force Buffer.allocUnsafe(size) to be zero-filled.") catch unreachable, }; const auto_or_run_params = [_]ParamType{ @@ -794,6 +797,9 @@ pub const Arguments = struct { if (args.flag("--expose-internals")) { bun.JSC.ModuleLoader.is_allowed_to_use_internal_testing_apis = true; } + if (args.flag("--zero-fill-buffers")) { + Bun__Node__ZeroFillBuffers = true; + } } if (opts.port != null and opts.origin == null) { diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index 2c07c8aa0e350d..ce852739c0dd62 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -537,7 +537,11 @@ declare interface Function { } declare var $Buffer: { - new (a: any, b?: any, c?: any): Buffer; + new (array: Array): Buffer; + new (arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number): Buffer; + new (buffer: Buffer): Buffer; + new (size: number): Buffer; + new (string: string, encoding?: BufferEncoding): Buffer; }; declare interface Error { diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 2c0c09e982ba7f..ace7afd5332441 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -1,78 +1,37 @@ // This is marked as a constructor because Node.js allows `new Buffer.from`, // Some legacy dependencies depend on this, see #3638 $constructor; -export function from(items) { - if ($isUndefinedOrNull(items)) { - throw new TypeError( - "The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.", - ); - } +export function from(value, encodingOrOffset, length) { + const { fromString, fromArrayBuffer, fromObject } = require("internal/buffer"); + const { isAnyArrayBuffer } = require("node:util/types"); - // TODO: figure out why private symbol not found - if ( - typeof items === "string" || - (typeof items === "object" && - ($isTypedArrayView(items) || - items instanceof ArrayBuffer || - items instanceof SharedArrayBuffer || - items instanceof String)) - ) { - switch ($argumentCount()) { - case 1: { - return new $Buffer(items); - } - case 2: { - return new $Buffer(items, $argument(1)); - } - default: { - return new $Buffer(items, $argument(1), $argument(2)); - } - } - } - if (typeof items === "object") { - const data = items.data; - if (items.type === "Buffer" && Array.isArray(data)) { - return new $Buffer(data); - } - } + if (typeof value === "string") return fromString(value, encodingOrOffset); - var arrayLike = $toObject( - items, - "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.", - ) as ArrayLike; + if (typeof value === "object" && value !== null) { + if (isAnyArrayBuffer(value)) return fromArrayBuffer(value, encodingOrOffset, length); - if (!$isJSArray(arrayLike)) { - const toPrimitive = $tryGetByIdWithWellKnownSymbol(items, "toPrimitive"); + const valueOf = value.valueOf && value.valueOf(); + if (valueOf != null && valueOf !== value && (typeof valueOf === "string" || typeof valueOf === "object")) { + return Buffer.from(valueOf, encodingOrOffset, length); + } - if (toPrimitive) { - const primitive = toPrimitive.$call(items, "string"); + const b = fromObject(value); + if (b) return b; + const toPrimitive = $tryGetByIdWithWellKnownSymbol(value, "toPrimitive"); + if (typeof toPrimitive === "function") { + const primitive = toPrimitive.$call(value, "string"); if (typeof primitive === "string") { - switch ($argumentCount()) { - case 1: { - return new $Buffer(primitive); - } - case 2: { - return new $Buffer(primitive, $argument(1)); - } - default: { - return new $Buffer(primitive, $argument(1), $argument(2)); - } - } + return fromString(primitive, encodingOrOffset); } } - - if (!("length" in arrayLike) || $isCallable(arrayLike)) { - throw new TypeError( - "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.", - ); - } } - // Don't pass the second argument because Node's Buffer.from doesn't accept - // a function and Uint8Array.from requires it if it exists - // That means we cannot use $tailCallFowrardArguments here, sadly - return new $Buffer(Uint8Array.from(arrayLike).buffer); + throw $ERR_INVALID_ARG_TYPE( + "first argument", + ["string", "Buffer", "ArrayBuffer", "Array", "Array-like Object"], + value, + ); } export function isBuffer(bufferlike) { diff --git a/src/js/builtins/JSBufferPrototype.ts b/src/js/builtins/JSBufferPrototype.ts index 99ca59405aa5ac..fcb02ea859ac00 100644 --- a/src/js/builtins/JSBufferPrototype.ts +++ b/src/js/builtins/JSBufferPrototype.ts @@ -8,46 +8,93 @@ interface BufferExt extends Buffer { toString(offset: number, length: number, encoding?: BufferEncoding): string; } -export function setBigUint64(this: BufferExt, offset, value, le) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64( - offset, - value, - le, - ); -} export function readInt8(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt8(offset); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getInt8(offset); } + export function readUInt8(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint8(offset); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 1); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getUint8(offset); } + export function readInt16LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 2); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getInt16(offset, true); } + export function readInt16BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 2); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getInt16(offset, false); } + export function readUInt16LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 2); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getUint16(offset, true); } + export function readUInt16BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 2); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getUint16(offset, false); } + export function readInt32LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getInt32(offset, true); } + export function readInt32BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getInt32(offset, false); } + export function readUInt32LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getUint32(offset, true); } + export function readUInt32BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getUint32(offset, false); } export function readIntLE(this: BufferExt, offset, byteLength) { + const { ERR_INVALID_ARG_TYPE, validateInteger, boundsError } = require("internal/buffer"); + if (offset === undefined) throw ERR_INVALID_ARG_TYPE("offset", "number", offset); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + + switch (byteLength) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + validateInteger(offset, "offset"); + if (!(offset >= 0 && offset <= this.length - byteLength)) boundsError(offset, this.length - byteLength); + break; + } switch (byteLength) { case 1: { return view.getInt8(offset); @@ -71,10 +118,26 @@ 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"); + boundsError(byteLength, 6, "byteLength"); } + export function readIntBE(this: BufferExt, offset, byteLength) { + const { ERR_INVALID_ARG_TYPE, validateInteger, boundsError } = require("internal/buffer"); + if (offset === undefined) throw ERR_INVALID_ARG_TYPE("offset", "number", offset); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + + switch (byteLength) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + validateInteger(offset, "offset"); + if (!(offset >= 0 && offset <= this.length - byteLength)) boundsError(offset, this.length - byteLength); + break; + } switch (byteLength) { case 1: { return view.getInt8(offset); @@ -98,10 +161,26 @@ 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"); + boundsError(byteLength, 6, "byteLength"); } + export function readUIntLE(this: BufferExt, offset, byteLength) { + const { ERR_INVALID_ARG_TYPE, validateInteger, boundsError } = require("internal/buffer"); + if (offset === undefined) throw ERR_INVALID_ARG_TYPE("offset", "number", offset); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + + switch (byteLength) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + validateInteger(offset, "offset"); + if (!(offset >= 0 && offset <= this.length - byteLength)) boundsError(offset, this.length - byteLength); + break; + } switch (byteLength) { case 1: { return view.getUint8(offset); @@ -122,10 +201,26 @@ 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"); + boundsError(byteLength, 6, "byteLength"); } + export function readUIntBE(this: BufferExt, offset, byteLength) { + const { ERR_INVALID_ARG_TYPE, validateInteger, boundsError } = require("internal/buffer"); + if (offset === undefined) throw ERR_INVALID_ARG_TYPE("offset", "number", offset); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + + switch (byteLength) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + validateInteger(offset, "offset"); + if (!(offset >= 0 && offset <= this.length - byteLength)) boundsError(offset, this.length - byteLength); + break; + } switch (byteLength) { case 1: { return view.getUint8(offset); @@ -146,331 +241,404 @@ 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"); + boundsError(byteLength, 6, "byteLength"); } 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; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.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; + require("internal/buffer").check_read(this, offset, 4); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.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; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.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; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getFloat64(offset, false); } + export function readBigInt64LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getBigInt64(offset, true); } + export function readBigInt64BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getBigInt64(offset, false); } + export function readBigUInt64LE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, true); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getBigUint64(offset, true); } + export function readBigUInt64BE(this: BufferExt, offset) { - return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, false); + if (offset === undefined) offset = 0; + require("internal/buffer").check_read(this, offset, 8); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + return view.getBigUint64(offset, false); } export function writeInt8(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt8( - offset === undefined ? (offset = 0) : offset, - value, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int8(this, value, offset, -0x80, 0x7f); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setInt8(offset, value); return offset + 1; } + export function writeUInt8(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint8( - offset === undefined ? (offset = 0) : offset, - value, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int8(this, value, offset, 0, 0xff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setUint8(offset, value); return offset + 1; } + export function writeInt16LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int16(this, value, offset, -0x8000, 0x7fff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setInt16(offset, value, true); return offset + 2; } + export function writeInt16BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int16(this, value, offset, -0x8000, 0x7fff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setInt16(offset, value, false); return offset + 2; } + export function writeUInt16LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int16(this, value, offset, 0, 0xffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setUint16(offset, value, true); return offset + 2; } + export function writeUInt16BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int16(this, value, offset, 0, 0xffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setUint16(offset, value, false); return offset + 2; } + export function writeInt32LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int32(this, value, offset, -0x80000000, 0x7fffffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setInt32(offset, value, true); return offset + 4; } + export function writeInt32BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int32(this, value, offset, -0x80000000, 0x7fffffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setInt32(offset, value, false); return offset + 4; } + export function writeUInt32LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int32(this, value, offset, 0, 0xffffffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setUint32(offset, value, true); return offset + 4; } + export function writeUInt32BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").check_int32(this, value, offset, 0, 0xffffffff); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.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)); + value = +value; switch (byteLength) { case 1: { + require("internal/buffer").check_int8(this, value, offset, -0x80, 0x7f); view.setInt8(offset, value); break; } case 2: { + require("internal/buffer").check_int16(this, value, offset, -0x8000, 0x7fff); view.setInt16(offset, value, true); break; } case 3: { + require("internal/buffer").check_int24(this, value, offset, -0x800000, 0x7fffff); view.setUint16(offset, value & 0xffff, true); view.setInt8(offset + 2, Math.floor(value * 2 ** -16)); break; } case 4: { + require("internal/buffer").check_int32(this, value, offset, -0x80000000, 0x7fffffff); view.setInt32(offset, value, true); break; } case 5: { + require("internal/buffer").check_int40(this, value, offset, -0x8000000000, 0x7fffffffff); view.setUint32(offset, value | 0, true); view.setInt8(offset + 4, Math.floor(value * 2 ** -32)); break; } case 6: { + require("internal/buffer").check_int48(this, value, offset, -0x800000000000, 0x7fffffffffff); view.setUint32(offset, value | 0, true); view.setInt16(offset + 4, Math.floor(value * 2 ** -32), true); break; } default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); + require("internal/buffer").boundsError(byteLength, 6, "byteLength"); } } return offset + byteLength; } + export function writeIntBE(this: BufferExt, value, offset, byteLength) { const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + value = +value; switch (byteLength) { case 1: { + require("internal/buffer").check_int8(this, value, offset, -0x80, 0x7f); view.setInt8(offset, value); break; } case 2: { + require("internal/buffer").check_int16(this, value, offset, -0x8000, 0x7fff); view.setInt16(offset, value, false); break; } case 3: { + require("internal/buffer").check_int24(this, value, offset, -0x800000, 0x7fffff); view.setUint16(offset + 1, value & 0xffff, false); view.setInt8(offset, Math.floor(value * 2 ** -16)); break; } case 4: { + require("internal/buffer").check_int32(this, value, offset, -0x80000000, 0x7fffffff); view.setInt32(offset, value, false); break; } case 5: { + require("internal/buffer").check_int40(this, value, offset, -0x8000000000, 0x7fffffffff); view.setUint32(offset + 1, value | 0, false); view.setInt8(offset, Math.floor(value * 2 ** -32)); break; } case 6: { + require("internal/buffer").check_int48(this, value, offset, -0x800000000000, 0x7fffffffffff); view.setUint32(offset + 2, value | 0, false); view.setInt16(offset, Math.floor(value * 2 ** -32), false); break; } default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); + require("internal/buffer").boundsError(byteLength, 6, "byteLength"); } } return offset + byteLength; } + export function writeUIntLE(this: BufferExt, value, offset, byteLength) { const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + value = +value; switch (byteLength) { case 1: { + require("internal/buffer").check_int8(this, value, offset, 0, 0xff); view.setUint8(offset, value); break; } case 2: { + require("internal/buffer").check_int16(this, value, offset, 0, 0xffff); view.setUint16(offset, value, true); break; } case 3: { + require("internal/buffer").check_int24(this, value, offset, 0, 0xffffff); view.setUint16(offset, value & 0xffff, true); view.setUint8(offset + 2, Math.floor(value * 2 ** -16)); break; } case 4: { + require("internal/buffer").check_int32(this, value, offset, 0, 0xffffffff); view.setUint32(offset, value, true); break; } case 5: { + require("internal/buffer").check_int40(this, value, offset, 0, 0xffffffffff); view.setUint32(offset, value | 0, true); view.setUint8(offset + 4, Math.floor(value * 2 ** -32)); break; } case 6: { + require("internal/buffer").check_int48(this, value, offset, 0, 0xffffffffffff); view.setUint32(offset, value | 0, true); view.setUint16(offset + 4, Math.floor(value * 2 ** -32), true); break; } default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); + require("internal/buffer").boundsError(byteLength, 6, "byteLength"); } } return offset + byteLength; } + export function writeUIntBE(this: BufferExt, value, offset, byteLength) { const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + value = +value; switch (byteLength) { case 1: { + require("internal/buffer").check_int8(this, value, offset, 0, 0xff); view.setUint8(offset, value); break; } case 2: { + require("internal/buffer").check_int16(this, value, offset, 0, 0xffff); view.setUint16(offset, value, false); break; } case 3: { + require("internal/buffer").check_int24(this, value, offset, 0, 0xffffff); view.setUint16(offset + 1, value & 0xffff, false); view.setUint8(offset, Math.floor(value * 2 ** -16)); break; } case 4: { + require("internal/buffer").check_int32(this, value, offset, 0, 0xffffffff); view.setUint32(offset, value, false); break; } case 5: { + require("internal/buffer").check_int40(this, value, offset, 0, 0xffffffffff); view.setUint32(offset + 1, value | 0, false); view.setUint8(offset, Math.floor(value * 2 ** -32)); break; } case 6: { + require("internal/buffer").check_int48(this, value, offset, 0, 0xffffffffffff); view.setUint32(offset + 2, value | 0, false); view.setUint16(offset, Math.floor(value * 2 ** -32), false); break; } default: { - throw new RangeError("byteLength must be >= 1 and <= 6"); + require("internal/buffer").boundsError(byteLength, 6, "byteLength"); } } return offset + byteLength; } export function writeFloatLE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").checkBounds(this, offset, 3); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.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 === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").checkBounds(this, offset, 3); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.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 === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").checkBounds(this, offset, 7); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.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 === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + value = +value; + require("internal/buffer").checkBounds(this, offset, 7); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setFloat64(offset, value, false); return offset + 8; } export function writeBigInt64LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + require("internal/buffer").check_bigint64(this, value, offset, -0x8000000000000000n, 0x7fffffffffffffffn); + if (typeof value !== "bigint") throw require("internal/buffer").ERR_INVALID_ARG_TYPE("value", "bigint", value); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setBigInt64(offset, value, true); return offset + 8; } export function writeBigInt64BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + require("internal/buffer").check_bigint64(this, value, offset, -0x8000000000000000n, 0x7fffffffffffffffn); + if (typeof value !== "bigint") throw require("internal/buffer").ERR_INVALID_ARG_TYPE("value", "bigint", value); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setBigInt64(offset, value, false); return offset + 8; } export function writeBigUInt64LE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64( - offset === undefined ? (offset = 0) : offset, - value, - true, - ); + if (offset === undefined) offset = 0; + require("internal/buffer").check_bigint64(this, value, offset, 0n, 0xffffffffffffffffn); + if (typeof value !== "bigint") throw require("internal/buffer").ERR_INVALID_ARG_TYPE("value", "bigint", value); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setBigUint64(offset, value, true); return offset + 8; } export function writeBigUInt64BE(this: BufferExt, value, offset) { - (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64( - offset === undefined ? (offset = 0) : offset, - value, - false, - ); + if (offset === undefined) offset = 0; + require("internal/buffer").check_bigint64(this, value, offset, 0n, 0xffffffffffffffffn); + if (typeof value !== "bigint") throw require("internal/buffer").ERR_INVALID_ARG_TYPE("value", "bigint", value); + const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)); + view.setBigUint64(offset, value, false); return offset + 8; } @@ -487,7 +655,7 @@ export function slice(this: BufferExt, start, end) { // Use Math.trunc() to convert offset to an integer value that can be larger // than an Int32. Hence, don't use offset | 0 or similar techniques. offset = Math.trunc(offset); - if (offset === 0 || offset !== offset) { + if (offset === undefined || offset !== offset) { return 0; } else if (offset < 0) { offset += length; diff --git a/src/js/internal/buffer.ts b/src/js/internal/buffer.ts new file mode 100644 index 00000000000000..c03828d89c28c6 --- /dev/null +++ b/src/js/internal/buffer.ts @@ -0,0 +1,134 @@ +// XXX: should more of this file be c++ imports? +const { validateNumber, validateInteger } = require("internal/validators"); +const { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, ERR_BUFFER_OUT_OF_BOUNDS } = require("internal/errors"); +const { isAnyArrayBuffer } = require("node:util/types"); + +const BufferFrom1 = $newCppFunction("JSBuffer.cpp", "jsFunction_BufferFrom_Array", 0); +const BufferFrom2 = $newCppFunction("JSBuffer.cpp", "jsFunction_BufferFrom_ArrayBuffer", 0); +const BufferFrom4 = $newCppFunction("JSBuffer.cpp", "jsFunction_BufferFrom_Size", 0); +const BufferFrom5 = $newCppFunction("JSBuffer.cpp", "jsFunction_BufferFrom_String", 0); + +const ArrayIsArray = Array.isArray; + +function boundsError(value, length, type?) { + if (Math.floor(value) !== value) { + validateNumber(value, type); + throw ERR_OUT_OF_RANGE(type || "offset", "an integer", value); + } + if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS(); + throw ERR_OUT_OF_RANGE(type || "offset", `>= ${type ? 1 : 0} and <= ${length}`, value); +} + +function checkBounds(buf, offset, byteLength) { + validateNumber(offset, "offset"); + if (buf[offset] === undefined || buf[offset + byteLength] === undefined) + boundsError(offset, buf.length - (byteLength + 1)); +} + +function checkInt(value, min, max, buf, offset, byteLength) { + if (value > max || value < min) { + const n = typeof min === "bigint" ? "n" : ""; + let range; + if (byteLength > 3) { + if (min === 0 || min === 0n) { + range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and ` + `< 2${n} ** ${(byteLength + 1) * 8 - 1}${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw ERR_OUT_OF_RANGE("value", range, value); + } + checkBounds(buf, offset, byteLength); +} + +function check_read(buf, offset, byteLength) { + validateInteger(offset, "offset"); + const type = buf.length - byteLength; + if (!(offset >= 0 && offset <= type)) boundsError(offset, type); +} + +function check_int8(buf, value, offset, min, max) { + validateNumber(offset, "offset"); + if (value > max || value < min) throw ERR_OUT_OF_RANGE("value", `>= ${min} and <= ${max}`, value); + if (buf[offset] === undefined) boundsError(offset, buf.length - 1); +} + +function check_int16(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 1); +} + +function check_int24(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 2); +} + +function check_int32(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 3); +} + +function check_int40(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 4); +} + +function check_int48(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 5); +} + +function check_bigint64(buf, value, offset, min, max) { + checkInt(value, min, max, buf, offset, 7); +} + +function fromString(string, encoding) { + return BufferFrom5(string, encoding); +} + +function fromArrayBuffer(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number) { + return BufferFrom2(arrayBuffer, byteOffset, length); +} + +function fromObject(obj) { + if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { + if (typeof obj.length !== "number") { + return BufferFrom4(0); + } + return fromArrayLike(obj); + } + if (obj.type === "Buffer" && ArrayIsArray(obj.data)) { + return fromArrayLike(obj.data); + } +} + +function fromArrayLike(obj) { + if (obj.length <= 0) return BufferFrom4(0); + if (obj.length < Buffer.poolSize >>> 1) { + // if (obj.length > poolSize - poolOffset) createPool(); + // const b = new FastBuffer(allocPool, poolOffset, obj.length); + // TypedArrayPrototypeSet(b, obj, 0); + // poolOffset += obj.length; + // alignPool(); + // return b; + } + return BufferFrom1(obj); +} + +export default { + validateNumber, + validateInteger, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + boundsError, + checkBounds, + checkInt, + check_read, + check_int8, + check_int16, + check_int24, + check_int32, + check_int40, + check_int48, + check_bigint64, + fromString, + fromObject, + fromArrayBuffer, +}; diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 08830c4bfc8736..b627b1b56f11c4 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -1,6 +1,7 @@ import { Buffer, SlowBuffer, isAscii, isUtf8, kMaxLength } from "buffer"; import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { gc } from "harness"; +import vm from "node:vm"; const BufferModule = await import("buffer"); @@ -21,7 +22,7 @@ afterEach(() => gc()); const NumberIsInteger = Number.isInteger; class ERR_INVALID_ARG_TYPE extends TypeError { constructor() { - super("Invalid arg type" + Array.prototype.join.call(arguments, " ")); + super(`The "${arguments[0]}" argument must be of type ${arguments[1]}. Received (${Bun.inspect(arguments[2])})`); this.code = "ERR_INVALID_ARG_TYPE"; } } @@ -269,7 +270,7 @@ for (let withOverridenBufferWrite of [false, true]) { // Invalid encoding for Buffer.write expect(() => b.write("test string", 0, 5, "invalid")).toThrow(/encoding/); // Unsupported arguments for Buffer.write - expect(() => b.write("test", "utf8", 0)).toThrow(/invalid/i); + expect(() => b.write("test", "utf8", 0)).toThrow(/The "offset" argument must be of type number. Received /); }); it("create 0-length buffers", () => { @@ -1205,7 +1206,7 @@ for (let withOverridenBufferWrite of [false, true]) { it("toLocaleString()", () => { const buf = Buffer.from("test"); expect(buf.toLocaleString()).toBe(buf.toString()); - // expect(Buffer.prototype.toLocaleString).toBe(Buffer.prototype.toString); + expect(Buffer.prototype.toLocaleString).toBe(Buffer.prototype.toString); }); it("alloc() should throw on invalid data", () => { @@ -2116,7 +2117,7 @@ for (let withOverridenBufferWrite of [false, true]) { const buf = Buffer.from(ab); expect(buf instanceof Buffer).toBe(true); - // expect(buf.parent, buf.buffer); + expect(buf.parent, buf.buffer); expect(buf.buffer).toBe(ab); expect(buf.length).toBe(ab.byteLength); @@ -2136,12 +2137,12 @@ for (let withOverridenBufferWrite of [false, true]) { // Now test protecting users from doing stupid things - // expect(function () { - // function AB() {} - // Object.setPrototypeOf(AB, ArrayBuffer); - // Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); - // // Buffer.from(new AB()); - // }).toThrow(); + expect(function () { + function AB() {} + Object.setPrototypeOf(AB, ArrayBuffer); + Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); + Buffer.from(new AB()); + }).toThrow(); // console.log(origAB !== ab); // Test the byteOffset and length arguments @@ -2670,8 +2671,8 @@ for (let withOverridenBufferWrite of [false, true]) { }); // Test that ArrayBuffer from a different context is detected correctly - // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // expect(Buffer.byteLength(arrayBuf)).toBe(0); + const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); + expect(Buffer.byteLength(arrayBuf)).toBe(0); // Verify that invalid encodings are treated as utf8 for (let i = 1; i < 10; i++) { diff --git a/test/js/node/child_process/child_process-node.test.js b/test/js/node/child_process/child_process-node.test.js index 42931a8dcf0a06..1d4a7a430557a6 100644 --- a/test/js/node/child_process/child_process-node.test.js +++ b/test/js/node/child_process/child_process-node.test.js @@ -659,7 +659,7 @@ describe("fork", () => { code: "ERR_INVALID_ARG_TYPE", name: "TypeError", message: expect.stringContaining( - `The "modulePath" argument must be of type string, Buffer or URL. Received: `, + `The "modulePath" argument must be of type string, Buffer, or URL. Received `, ), }), ); @@ -718,7 +718,7 @@ describe("fork", () => { expect.objectContaining({ code: "ERR_INVALID_ARG_TYPE", name: "TypeError", - message: expect.stringContaining(`The "options" argument must be of type object. Received: `), + message: expect.stringContaining(`The "options" argument must be of type object. Received `), }), ); }); diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts index f70772d4218971..a259c6897da066 100644 --- a/test/js/node/child_process/child_process.test.ts +++ b/test/js/node/child_process/child_process.test.ts @@ -96,7 +96,7 @@ describe("spawn()", () => { it("should disallow invalid filename", () => { // @ts-ignore expect(() => spawn(123)).toThrow({ - message: 'The "file" argument must be of type string. Received 123', + message: 'The "file" argument must be of type string. Received type number (123)', code: "ERR_INVALID_ARG_TYPE", }); }); diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index 6b5d1079ffad7b..8385945cde2024 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -30,7 +30,7 @@ const net = require('net'); // Do not require 'os' until needed so that test-os-checked-function can // monkey patch it. If 'os' is required here, that test will fail. const path = require('path'); -const { inspect } = require('util'); +const inspect = Bun.inspect; // const { inspect } = require('util'); const { isMainThread } = require('worker_threads'); const { isModuleNamespaceObject } = require('util/types'); @@ -888,13 +888,14 @@ function invalidArgTypeHelper(input) { return ` Received ${input}`; } if (typeof input === 'function') { - return ` Received function ${input.name}`; + if (input.name) return ` Received function ${input.name}`; + return ` Received function`; } if (typeof input === 'object') { if (input.constructor?.name) { return ` Received an instance of ${input.constructor.name}`; } - return ` Received ${inspect(input, { depth: -1 })}`; + return ` Received ${inspect(input, { depth: 0 })}`; } let inspected = inspect(input, { colors: false }); diff --git a/test/js/node/test/parallel/test-buffer-alloc.js b/test/js/node/test/parallel/test-buffer-alloc.js new file mode 100644 index 00000000000000..faca8fe4549775 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-alloc.js @@ -0,0 +1,1199 @@ +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const vm = require('vm'); + +const { + SlowBuffer, + kMaxLength, +} = require('buffer'); + +// Verify the maximum Uint8Array size. There is no concrete limit by spec. The +// internal limits should be updated if this fails. +assert.throws( + () => new Uint8Array(kMaxLength + 1), + { message: `length too large: ${kMaxLength + 1}` }, +); + +const b = Buffer.allocUnsafe(1024); +assert.strictEqual(b.length, 1024); + +b[0] = -1; +assert.strictEqual(b[0], 255); + +for (let i = 0; i < 1024; i++) { + b[i] = i % 256; +} + +for (let i = 0; i < 1024; i++) { + assert.strictEqual(i % 256, b[i]); +} + +const c = Buffer.allocUnsafe(512); +assert.strictEqual(c.length, 512); + +const d = Buffer.from([]); +assert.strictEqual(d.length, 0); + +// Test offset properties +{ + const b = Buffer.alloc(128); + assert.strictEqual(b.length, 128); + assert.strictEqual(b.byteOffset, 0); + assert.strictEqual(b.offset, 0); +} + +// Test creating a Buffer from a Uint8Array +{ + const ui8 = new Uint8Array(4).fill(42); + const e = Buffer.from(ui8); + for (const [index, value] of e.entries()) { + assert.strictEqual(value, ui8[index]); + } +} +// Test creating a Buffer from a Uint8Array (old constructor) +{ + const ui8 = new Uint8Array(4).fill(42); + const e = Buffer(ui8); + for (const [key, value] of e.entries()) { + assert.strictEqual(value, ui8[key]); + } +} + +// Test creating a Buffer from a Uint32Array +// Note: it is implicitly interpreted as Array of integers modulo 256 +{ + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer.from(ui32); + for (const [index, value] of e.entries()) { + assert.strictEqual(value, ui32[index]); + } +} +// Test creating a Buffer from a Uint32Array (old constructor) +// Note: it is implicitly interpreted as Array of integers modulo 256 +{ + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer(ui32); + for (const [key, value] of e.entries()) { + assert.strictEqual(value, ui32[key]); + } +} + +// Test invalid encoding for Buffer.toString +assert.throws(() => b.toString('invalid'), + /Unknown encoding: invalid/); +// Invalid encoding for Buffer.write +assert.throws(() => b.write('test string', 0, 5, 'invalid'), + /Unknown encoding: invalid/); +// Unsupported arguments for Buffer.write +assert.throws(() => b.write('test', 'utf8', 0), + { code: 'ERR_INVALID_ARG_TYPE' }); + +// Try to create 0-length buffers. Should not throw. +Buffer.from(''); +Buffer.from('', 'ascii'); +Buffer.from('', 'latin1'); +Buffer.alloc(0); +Buffer.allocUnsafe(0); +new Buffer(''); +new Buffer('', 'ascii'); +new Buffer('', 'latin1'); +new Buffer('', 'binary'); +Buffer(0); + +const outOfRangeError = { + // code: 'ERR_OUT_OF_RANGE', // TODO: + name: 'RangeError' +}; + +// Try to write a 0-length string beyond the end of b +assert.throws(() => b.write('', 2048), outOfRangeError); + +// Throw when writing to negative offset +assert.throws(() => b.write('a', -1), outOfRangeError); + +// Throw when writing past bounds from the pool +assert.throws(() => b.write('a', 2048), outOfRangeError); + +// Throw when writing to negative offset +assert.throws(() => b.write('a', -1), outOfRangeError); + +// Try to copy 0 bytes worth of data into an empty buffer +b.copy(Buffer.alloc(0), 0, 0, 0); + +// Try to copy 0 bytes past the end of the target buffer +b.copy(Buffer.alloc(0), 1, 1, 1); +b.copy(Buffer.alloc(1), 1, 1, 1); + +// Try to copy 0 bytes from past the end of the source buffer +b.copy(Buffer.alloc(1), 0, 1024, 1024); + +// Testing for smart defaults and ability to pass string values as offset +{ + const writeTest = Buffer.from('abcdes'); + writeTest.write('n', 'ascii'); + assert.throws( + () => writeTest.write('o', '1', 'ascii'), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + writeTest.write('o', 1, 'ascii'); + writeTest.write('d', 2, 'ascii'); + writeTest.write('e', 3, 'ascii'); + writeTest.write('j', 4, 'ascii'); + assert.strictEqual(writeTest.toString(), 'nodejs'); +} + +// Offset points to the end of the buffer and does not throw. +// (see https://github.com/nodejs/node/issues/8127). +Buffer.alloc(1).write('', 1, 0); + +// ASCII slice test +{ + const asciiString = 'hello world'; + + for (let i = 0; i < asciiString.length; i++) { + b[i] = asciiString.charCodeAt(i); + } + const asciiSlice = b.toString('ascii', 0, asciiString.length); + assert.strictEqual(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + assert.strictEqual(asciiString.length, b.write(asciiString, offset, 'ascii')); + const asciiSlice = b.toString('ascii', offset, offset + asciiString.length); + assert.strictEqual(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + const sliceA = b.slice(offset, offset + asciiString.length); + const sliceB = b.slice(offset, offset + asciiString.length); + for (let i = 0; i < asciiString.length; i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } +} + +// UTF-8 slice test +{ + const utf8String = '¡hέlló wôrld!'; + const offset = 100; + + b.write(utf8String, 0, Buffer.byteLength(utf8String), 'utf8'); + let utf8Slice = b.toString('utf8', 0, Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + assert.strictEqual(Buffer.byteLength(utf8String), + b.write(utf8String, offset, 'utf8')); + utf8Slice = b.toString('utf8', offset, + offset + Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); + const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); + for (let i = 0; i < Buffer.byteLength(utf8String); i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } +} + +{ + const slice = b.slice(100, 150); + assert.strictEqual(slice.length, 50); + for (let i = 0; i < 50; i++) { + assert.strictEqual(b[100 + i], slice[i]); + } +} + +{ + // Make sure only top level parent propagates from allocPool + const b = Buffer.allocUnsafe(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(b.parent, c.parent); + assert.strictEqual(b.parent, d.parent); +} + +{ + // Also from a non-pooled instance + const b = Buffer.allocUnsafeSlow(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(c.parent, d.parent); +} + +{ + // Bug regression test + const testValue = '\u00F6\u65E5\u672C\u8A9E'; // ö日本語 + const buffer = Buffer.allocUnsafe(32); + const size = buffer.write(testValue, 0, 'utf8'); + const slice = buffer.toString('utf8', 0, size); + assert.strictEqual(slice, testValue); +} + +{ + // Test triple slice + const a = Buffer.allocUnsafe(8); + for (let i = 0; i < 8; i++) a[i] = i; + const b = a.slice(4, 8); + assert.strictEqual(b[0], 4); + assert.strictEqual(b[1], 5); + assert.strictEqual(b[2], 6); + assert.strictEqual(b[3], 7); + const c = b.slice(2, 4); + assert.strictEqual(c[0], 6); + assert.strictEqual(c[1], 7); +} + +{ + const d = Buffer.from([23, 42, 255]); + assert.strictEqual(d.length, 3); + assert.strictEqual(d[0], 23); + assert.strictEqual(d[1], 42); + assert.strictEqual(d[2], 255); + assert.deepStrictEqual(d, Buffer.from(d)); +} + +{ + // Test for proper UTF-8 Encoding + const e = Buffer.from('über'); + assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114])); +} + +{ + // Test for proper ascii Encoding, length should be 4 + const f = Buffer.from('über', 'ascii'); + assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114])); +} + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + { + // Test for proper UTF16LE encoding, length should be 8 + const f = Buffer.from('über', encoding); + assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); + } + + { + // Length should be 12 + const f = Buffer.from('привет', encoding); + assert.deepStrictEqual( + f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4]) + ); + assert.strictEqual(f.toString(encoding), 'привет'); + } + + { + const f = Buffer.from([0, 0, 0, 0, 0]); + assert.strictEqual(f.length, 5); + const size = f.write('あいうえお', encoding); + assert.strictEqual(size, 4); + assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); + } +}); + +{ + const f = Buffer.from('\uD83D\uDC4D', 'utf-16le'); // THUMBS UP SIGN (U+1F44D) + assert.strictEqual(f.length, 4); + assert.deepStrictEqual(f, Buffer.from('3DD84DDC', 'hex')); +} + +// Test construction from arrayish object +{ + const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; + let g = Buffer.from(arrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); + const strArrayIsh = { 0: '0', 1: '1', 2: '2', 3: '3', length: 4 }; + g = Buffer.from(strArrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); +} + +// +// Test toString('base64') +// +assert.strictEqual((Buffer.from('Man')).toString('base64'), 'TWFu'); +assert.strictEqual((Buffer.from('Woman')).toString('base64'), 'V29tYW4='); + +// +// Test toString('base64url') +// +assert.strictEqual((Buffer.from('Man')).toString('base64url'), 'TWFu'); +assert.strictEqual((Buffer.from('Woman')).toString('base64url'), 'V29tYW4'); + +{ + // Test that regular and URL-safe base64 both work both ways + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; + assert.deepStrictEqual(Buffer.from('//++/++/++//', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('__--_--_--__', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//', 'base64url'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('__--_--_--__', 'base64url'), + Buffer.from(expected)); +} + +const base64flavors = ['base64', 'base64url']; + +{ + // Test that regular and URL-safe base64 both work both ways with padding + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64url'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64url'), + Buffer.from(expected)); +} + +{ + // big example + const quote = 'Man is distinguished, not only by his reason, but by this ' + + 'singular passion from other animals, which is a lust ' + + 'of the mind, that by a perseverance of delight in the ' + + 'continued and indefatigable generation of knowledge, ' + + 'exceeds the short vehemence of any carnal pleasure.'; + const expected = 'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb' + + '24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci' + + 'BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ' + + 'gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu' + + 'dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ' + + 'GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm' + + '5hbCBwbGVhc3VyZS4='; + assert.strictEqual(Buffer.from(quote).toString('base64'), expected); + assert.strictEqual( + Buffer.from(quote).toString('base64url'), + expected.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '') + ); + + base64flavors.forEach((encoding) => { + let b = Buffer.allocUnsafe(1024); + let bytesWritten = b.write(expected, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder ignores whitespace + const expectedWhite = `${expected.slice(0, 60)} \n` + + `${expected.slice(60, 120)} \n` + + `${expected.slice(120, 180)} \n` + + `${expected.slice(180, 240)} \n` + + `${expected.slice(240, 300)}\n` + + `${expected.slice(300, 360)}\n`; + b = Buffer.allocUnsafe(1024); + bytesWritten = b.write(expectedWhite, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder on the constructor works + // even in the presence of whitespace. + b = Buffer.from(expectedWhite, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder ignores illegal chars + const expectedIllegal = expected.slice(0, 60) + ' \x80' + + expected.slice(60, 120) + ' \xff' + + expected.slice(120, 180) + ' \x00' + + expected.slice(180, 240) + ' \x98' + + expected.slice(240, 300) + '\x03' + + expected.slice(300, 360); + b = Buffer.from(expectedIllegal, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + }); +} + +base64flavors.forEach((encoding) => { + assert.strictEqual(Buffer.from('', encoding).toString(), ''); + assert.strictEqual(Buffer.from('K', encoding).toString(), ''); + + // multiple-of-4 with padding + assert.strictEqual(Buffer.from('Kg==', encoding).toString(), '*'); + assert.strictEqual(Buffer.from('Kio=', encoding).toString(), '*'.repeat(2)); + assert.strictEqual(Buffer.from('Kioq', encoding).toString(), '*'.repeat(3)); + assert.strictEqual( + Buffer.from('KioqKg==', encoding).toString(), '*'.repeat(4)); + assert.strictEqual( + Buffer.from('KioqKio=', encoding).toString(), '*'.repeat(5)); + assert.strictEqual( + Buffer.from('KioqKioq', encoding).toString(), '*'.repeat(6)); + assert.strictEqual(Buffer.from('KioqKioqKg==', encoding).toString(), + '*'.repeat(7)); + assert.strictEqual(Buffer.from('KioqKioqKio=', encoding).toString(), + '*'.repeat(8)); + assert.strictEqual(Buffer.from('KioqKioqKioq', encoding).toString(), + '*'.repeat(9)); + assert.strictEqual(Buffer.from('KioqKioqKioqKg==', encoding).toString(), + '*'.repeat(10)); + assert.strictEqual(Buffer.from('KioqKioqKioqKio=', encoding).toString(), + '*'.repeat(11)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioq', encoding).toString(), + '*'.repeat(12)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKg==', encoding).toString(), + '*'.repeat(13)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKio=', encoding).toString(), + '*'.repeat(14)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioq', encoding).toString(), + '*'.repeat(15)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKg==', encoding).toString(), + '*'.repeat(16)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKio=', encoding).toString(), + '*'.repeat(17)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioq', encoding).toString(), + '*'.repeat(18)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKioqKg==', + encoding).toString(), + '*'.repeat(19)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKioqKio=', + encoding).toString(), + '*'.repeat(20)); + + // No padding, not a multiple of 4 + assert.strictEqual(Buffer.from('Kg', encoding).toString(), '*'); + assert.strictEqual(Buffer.from('Kio', encoding).toString(), '*'.repeat(2)); + assert.strictEqual(Buffer.from('KioqKg', encoding).toString(), '*'.repeat(4)); + assert.strictEqual( + Buffer.from('KioqKio', encoding).toString(), '*'.repeat(5)); + assert.strictEqual(Buffer.from('KioqKioqKg', encoding).toString(), + '*'.repeat(7)); + assert.strictEqual(Buffer.from('KioqKioqKio', encoding).toString(), + '*'.repeat(8)); + assert.strictEqual(Buffer.from('KioqKioqKioqKg', encoding).toString(), + '*'.repeat(10)); + assert.strictEqual(Buffer.from('KioqKioqKioqKio', encoding).toString(), + '*'.repeat(11)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(13)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(14)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(16)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(17)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(19)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(20)); +}); + +// Handle padding graciously, multiple-of-4 or not +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64url') + .length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64url') + .length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64url').length, + 31 +); + +{ +// This string encodes single '.' character in UTF-16 + const dot = Buffer.from('//4uAA==', 'base64'); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString('base64'), '//4uAA=='); +} + +{ +// This string encodes single '.' character in UTF-16 + const dot = Buffer.from('//4uAA', 'base64url'); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString('base64url'), '__4uAA'); +} + +{ + // Writing base64 at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ['TWFkbmVzcz8h', 'IFRoaXM=', 'IGlz', 'IG5vZGUuanMh']; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, 'base64'); + } + assert.strictEqual(b.toString('latin1', 0, pos), + 'Madness?! This is node.js!'); +} + +{ + // Writing base64url at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ['TWFkbmVzcz8h', 'IFRoaXM', 'IGlz', 'IG5vZGUuanMh']; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, 'base64url'); + } + assert.strictEqual(b.toString('latin1', 0, pos), + 'Madness?! This is node.js!'); +} + +// Regression test for https://github.com/nodejs/node/issues/3496. +assert.strictEqual(Buffer.from('=bad'.repeat(1e4), 'base64').length, 0); + +// Regression test for https://github.com/nodejs/node/issues/11987. +assert.deepStrictEqual(Buffer.from('w0 ', 'base64'), + Buffer.from('w0', 'base64')); + +// Regression test for https://github.com/nodejs/node/issues/13657. +assert.deepStrictEqual(Buffer.from(' YWJvcnVtLg', 'base64'), + Buffer.from('YWJvcnVtLg', 'base64')); + +{ + // Creating buffers larger than pool size. + const l = Buffer.poolSize + 5; + const s = 'h'.repeat(l); + const b = Buffer.from(s); + + for (let i = 0; i < l; i++) { + assert.strictEqual(b[i], 'h'.charCodeAt(0)); + } + + const sb = b.toString(); + assert.strictEqual(sb.length, s.length); + assert.strictEqual(sb, s); +} + +{ + // test hex toString + const hexb = Buffer.allocUnsafe(256); + for (let i = 0; i < 256; i++) { + hexb[i] = i; + } + const hexStr = hexb.toString('hex'); + assert.strictEqual(hexStr, + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); + + const hexb2 = Buffer.from(hexStr, 'hex'); + for (let i = 0; i < 256; i++) { + assert.strictEqual(hexb2[i], hexb[i]); + } +} + +// Test single hex character is discarded. +assert.strictEqual(Buffer.from('A', 'hex').length, 0); + +// Test that if a trailing character is discarded, rest of string is processed. +assert.deepStrictEqual(Buffer.from('Abx', 'hex'), Buffer.from('Ab', 'hex')); + +// Test single base64 char encodes as 0. +assert.strictEqual(Buffer.from('A', 'base64').length, 0); + + +{ + // Test an invalid slice end. + const b = Buffer.from([1, 2, 3, 4, 5]); + const b2 = b.toString('hex', 1, 10000); + const b3 = b.toString('hex', 1, 5); + const b4 = b.toString('hex', 1); + assert.strictEqual(b2, b3); + assert.strictEqual(b2, b4); +} + +function buildBuffer(data) { + if (Array.isArray(data)) { + const buffer = Buffer.allocUnsafe(data.length); + data.forEach((v, k) => buffer[k] = v); + return buffer; + } + return null; +} + +const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); + +assert.strictEqual(x.inspect(), 'Buffer(9) [ 129, 163, 102, 111, 111, 163, 98, 97, 114 ]'); + +{ + const z = x.slice(4); + assert.strictEqual(z.length, 5); + assert.strictEqual(z[0], 0x6f); + assert.strictEqual(z[1], 0xa3); + assert.strictEqual(z[2], 0x62); + assert.strictEqual(z[3], 0x61); + assert.strictEqual(z[4], 0x72); +} + +{ + const z = x.slice(0); + assert.strictEqual(z.length, x.length); +} + +{ + const z = x.slice(0, 4); + assert.strictEqual(z.length, 4); + assert.strictEqual(z[0], 0x81); + assert.strictEqual(z[1], 0xa3); +} + +{ + const z = x.slice(0, 9); + assert.strictEqual(z.length, 9); +} + +{ + const z = x.slice(1, 4); + assert.strictEqual(z.length, 3); + assert.strictEqual(z[0], 0xa3); +} + +{ + const z = x.slice(2, 4); + assert.strictEqual(z.length, 2); + assert.strictEqual(z[0], 0x66); + assert.strictEqual(z[1], 0x6f); +} + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + const b = Buffer.allocUnsafe(10); + b.write('あいうえお', encoding); + assert.strictEqual(b.toString(encoding), 'あいうえお'); +}); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + const b = Buffer.allocUnsafe(11); + b.write('あいうえお', 1, encoding); + assert.strictEqual(b.toString(encoding, 1), 'あいうえお'); +}); + +{ + // latin1 encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); +} + +{ + // Binary encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); +} + +{ + // https://github.com/nodejs/node-v0.x-archive/pull/1210 + // Test UTF-8 string includes null character + let buf = Buffer.from('\0'); + assert.strictEqual(buf.length, 1); + buf = Buffer.from('\0\0'); + assert.strictEqual(buf.length, 2); +} + +{ + const buf = Buffer.allocUnsafe(2); + assert.strictEqual(buf.write(''), 0); // 0bytes + assert.strictEqual(buf.write('\0'), 1); // 1byte (v8 adds null terminator) + assert.strictEqual(buf.write('a\0'), 2); // 1byte * 2 + assert.strictEqual(buf.write('あ'), 0); // 3bytes + assert.strictEqual(buf.write('\0あ'), 1); // 1byte + 3bytes + assert.strictEqual(buf.write('\0\0あ'), 2); // 1byte * 2 + 3bytes +} + +{ + const buf = Buffer.allocUnsafe(10); + assert.strictEqual(buf.write('あいう'), 9); // 3bytes * 3 (v8 adds null term.) + assert.strictEqual(buf.write('あいう\0'), 10); // 3bytes * 3 + 1byte +} + +{ + // https://github.com/nodejs/node-v0.x-archive/issues/243 + // Test write() with maxLength + const buf = Buffer.allocUnsafe(4); + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xFF); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 4), 3); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0x63); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xFF); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcdef', 1, 2, 'hex'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0xAB); + assert.strictEqual(buf[2], 0xCD); + assert.strictEqual(buf[3], 0xFF); + + ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 0, 2, encoding), 2); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x00); + assert.strictEqual(buf[2], 0xFF); + assert.strictEqual(buf[3], 0xFF); + }); +} + +{ + // Test offset returns are correct + const b = Buffer.allocUnsafe(16); + assert.strictEqual(b.writeUInt32LE(0, 0), 4); + assert.strictEqual(b.writeUInt16LE(0, 4), 6); + assert.strictEqual(b.writeUInt8(0, 6), 7); + assert.strictEqual(b.writeInt8(0, 7), 8); + assert.strictEqual(b.writeDoubleLE(0, 8), 16); +} + +{ + // Test unmatched surrogates not producing invalid utf8 output + // ef bf bd = utf-8 representation of unicode replacement character + // see https://codereview.chromium.org/121173009/ + const buf = Buffer.from('ab\ud800cd', 'utf8'); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x62); + assert.strictEqual(buf[2], 0xef); + assert.strictEqual(buf[3], 0xbf); + assert.strictEqual(buf[4], 0xbd); + assert.strictEqual(buf[5], 0x63); + assert.strictEqual(buf[6], 0x64); +} + +{ + // Test for buffer overrun + const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 + const sub = buf.slice(0, 4); // length: 4 + assert.strictEqual(sub.write('12345', 'latin1'), 4); + assert.strictEqual(buf[4], 0); + assert.strictEqual(sub.write('12345', 'binary'), 4); + assert.strictEqual(buf[4], 0); +} + +{ + // Test alloc with fill option + const buf = Buffer.alloc(5, '800A', 'hex'); + assert.strictEqual(buf[0], 128); + assert.strictEqual(buf[1], 10); + assert.strictEqual(buf[2], 128); + assert.strictEqual(buf[3], 10); + assert.strictEqual(buf[4], 128); +} + + +// Check for fractional length args, junk length args, etc. +// https://github.com/joyent/node/issues/1758 + +// Call .fill() first, stops valgrind warning about uninitialized memory reads. +Buffer.allocUnsafe(3.3).fill().toString(); +// Throws bad argument error in commit 43cb4ec +Buffer.alloc(3.3).fill().toString(); +assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3); +assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3); +assert.strictEqual(Buffer.from({ length: 'BAM' }).length, 0); + +// Make sure that strings are not coerced to numbers. +assert.strictEqual(Buffer.from('99').length, 2); +assert.strictEqual(Buffer.from('13.37').length, 5); + +// Ensure that the length argument is respected. +['ascii', 'utf8', 'hex', 'base64', 'latin1', 'binary'].forEach((enc) => { + assert.strictEqual(Buffer.allocUnsafe(1).write('aaaaaa', 0, 1, enc), 1); +}); + +{ + // Regression test, guard against buffer overrun in the base64 decoder. + const a = Buffer.allocUnsafe(3); + const b = Buffer.from('xxx'); + a.write('aaaaaaaa', 'base64'); + assert.strictEqual(b.toString(), 'xxx'); +} + +// issue GH-3416 +Buffer.from(Buffer.allocUnsafe(0), 0, 0); + +// issue GH-5587 +assert.throws( + () => Buffer.alloc(8).writeFloatLE(0, 5), + outOfRangeError +); +assert.throws( + () => Buffer.alloc(16).writeDoubleLE(0, 9), + outOfRangeError +); + +// Attempt to overflow buffers, similar to previous bug in array buffers +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), + outOfRangeError +); +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), + outOfRangeError +); + +// Ensure negative values can't get past offset +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), + outOfRangeError +); +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), + outOfRangeError +); + +// Test for common write(U)IntLE/BE +{ + let buf = Buffer.allocUnsafe(3); + buf.writeUIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeUIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readUIntBE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readIntBE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntLE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123456); + + buf.fill(0xFF); + buf.writeIntBE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123456); + + buf.fill(0xFF); + buf.writeIntLE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123400); + + buf.fill(0xFF); + buf.writeIntBE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123400); + + buf.fill(0xFF); + buf.writeIntLE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); + assert.strictEqual(buf.readIntLE(0, 3), -0x120000); + + buf.fill(0xFF); + buf.writeIntBE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x120000); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeUIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890); + + buf.fill(0xFF); + buf.writeIntBE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); + assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); + assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000); + + buf.fill(0xFF); + buf.writeIntBE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000); +} + +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: +// should throw but not assert in C++ land. +assert.throws( + () => Buffer.from('', 'buffer'), + { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: buffer' + } +); + +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111. +// Constructing a buffer from another buffer should a) work, and b) not corrupt +// the source buffer. +{ + const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] + const b = Buffer.from(a); + const c = Buffer.from(b); + assert.strictEqual(b.length, a.length); + assert.strictEqual(c.length, a.length); + for (let i = 0, k = a.length; i < k; ++i) { + assert.strictEqual(a[i], i); + assert.strictEqual(b[i], i); + assert.strictEqual(c[i], i); + } +} + +if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check + // Test truncation after decode + const crypto = require('crypto'); + + const b1 = Buffer.from('YW55=======', 'base64'); + const b2 = Buffer.from('YW55', 'base64'); + + assert.strictEqual( + crypto.createHash('sha1').update(b1).digest('hex'), + crypto.createHash('sha1').update(b2).digest('hex') + ); +} else { + common.printSkipMessage('missing crypto'); +} + +const ps = Buffer.poolSize; +Buffer.poolSize = 0; +assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer); +Buffer.poolSize = ps; + +assert.throws( + () => Buffer.allocUnsafe(10).copy(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "target" argument must be an instance of Buffer or ' + + 'Uint8Array. Received undefined' + }); + +assert.throws(() => Buffer.from(), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The first argument must be of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received undefined' +}); +assert.throws(() => Buffer.from(null), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The first argument must be of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received null' +}); + +// Test prototype getters don't throw +assert.strictEqual(Buffer.prototype.parent, undefined); +assert.strictEqual(Buffer.prototype.offset, undefined); +assert.strictEqual(SlowBuffer.prototype.parent, undefined); +assert.strictEqual(SlowBuffer.prototype.offset, undefined); + + +{ + // Test that large negative Buffer length inputs don't affect the pool offset. + // Use the fromArrayLike() variant here because it's more lenient + // about its input and passes the length directly to allocate(). + assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), + Buffer.from('')); + assert.deepStrictEqual(Buffer.from({ length: -100 }), + Buffer.from('')); + + // Check pool offset after that by trying to write string into the pool. + Buffer.from('abc'); +} + + +// Test that ParseArrayIndex handles full uint32 +{ + const errMsg = common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); +} + +// ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. +assert.throws(() => { + const a = Buffer.alloc(1); + const b = Buffer.alloc(1); + a.copy(b, 0, 0x100000000, 0x100000001); +}, outOfRangeError); + +// Unpooled buffer (replaces SlowBuffer) +{ + const ubuf = Buffer.allocUnsafeSlow(10); + assert(ubuf); + assert(ubuf.buffer); + assert.strictEqual(ubuf.buffer.byteLength, 10); +} + +// Regression test to verify that an empty ArrayBuffer does not throw. +Buffer.from(new ArrayBuffer()); + +// Test that ArrayBuffer from a different context is detected correctly. +const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +Buffer.from(arrayBuf); +Buffer.from({ buffer: arrayBuf }); + +assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), + /"size" argument must be of type number/); +assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), + /"size" argument must be of type number/); + +assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString); +{ + const buf = Buffer.from('test'); + assert.strictEqual(buf.toLocaleString(), buf.toString()); +} + +assert.throws(() => { + Buffer.alloc(0x1000, 'This is not correctly encoded', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "Failed to decode value", +}); + +assert.throws(() => { + Buffer.alloc(0x1000, 'c', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "Failed to decode value", +}); + +assert.throws(() => { + Buffer.alloc(1, Buffer.alloc(0)); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "Buffer cannot be empty", +}); + +assert.throws(() => { + Buffer.alloc(40, 'x', 20); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "encoding" argument must be of type string. Received type number (20)`, +}); diff --git a/test/js/node/test/parallel/test-buffer-arraybuffer.js b/test/js/node/test/parallel/test-buffer-arraybuffer.js new file mode 100644 index 00000000000000..de1b0647889542 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-arraybuffer.js @@ -0,0 +1,152 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const LENGTH = 16; + +const ab = new ArrayBuffer(LENGTH); +const dv = new DataView(ab); +const ui = new Uint8Array(ab); +const buf = Buffer.from(ab); + + +assert.ok(buf instanceof Buffer); +assert.strictEqual(buf.parent, buf.buffer); +assert.strictEqual(buf.buffer, ab); +assert.strictEqual(buf.length, ab.byteLength); + + +buf.fill(0xC); +for (let i = 0; i < LENGTH; i++) { + assert.strictEqual(ui[i], 0xC); + ui[i] = 0xF; + assert.strictEqual(buf[i], 0xF); +} + +buf.writeUInt32LE(0xF00, 0); +buf.writeUInt32BE(0xB47, 4); +buf.writeDoubleLE(3.1415, 8); + +assert.strictEqual(dv.getUint32(0, true), 0xF00); +assert.strictEqual(dv.getUint32(4), 0xB47); +assert.strictEqual(dv.getFloat64(8, true), 3.1415); + + +// Now test protecting users from doing stupid things + +assert.throws(function() { + function AB() { } + Object.setPrototypeOf(AB, ArrayBuffer); + Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); + Buffer.from(new AB()); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The first argument must be of type string, ' + + 'Buffer, ArrayBuffer, Array, or Array-like Object. Received ' + + 'an instance of AB' +}); + +// Test the byteOffset and length arguments +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer.from(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer.from(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer.from(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test the deprecated Buffer() version also +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +{ + // If byteOffset is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0); + assert.deepStrictEqual(Buffer.from(ab, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, []), expected); + + // If byteOffset can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, [1]), Buffer.from(ab, 1)); + + // If byteOffset is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); +} + +{ + // If length is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0, 0); + assert.deepStrictEqual(Buffer.from(ab, 0, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, []), expected); + + // If length can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, 0, [1]), Buffer.from(ab, 0, 1)); + + // If length is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, 0, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test an array like entry with the length set to NaN. +assert.deepStrictEqual(Buffer.from({ length: NaN }), Buffer.alloc(0)); diff --git a/test/js/node/test/parallel/test-buffer-backing-arraybuffer.js b/test/js/node/test/parallel/test-buffer-backing-arraybuffer.js new file mode 100644 index 00000000000000..35eb41819fdd0b --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-backing-arraybuffer.js @@ -0,0 +1,38 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (true) return; // TODO: BUN +const assert = require('assert'); +const { internalBinding } = require('internal/test/binding'); +const { arrayBufferViewHasBuffer } = internalBinding('util'); + +const tests = [ + { length: 0, expectOnHeap: true }, + { length: 48, expectOnHeap: true }, + { length: 96, expectOnHeap: false }, + { length: 1024, expectOnHeap: false }, +]; + +for (const { length, expectOnHeap } of tests) { + const arrays = [ + new Uint8Array(length), + new Uint16Array(length / 2), + new Uint32Array(length / 4), + new Float32Array(length / 4), + new Float64Array(length / 8), + Buffer.alloc(length), + Buffer.allocUnsafeSlow(length), + // Buffer.allocUnsafe() is missing because it may use pooled allocations. + ]; + + for (const array of arrays) { + const isOnHeap = !arrayBufferViewHasBuffer(array); + assert.strictEqual(isOnHeap, expectOnHeap, + `mismatch: ${isOnHeap} vs ${expectOnHeap} ` + + `for ${array.constructor.name}, length = ${length}`); + + // Consistency check: Accessing .buffer should create it. + array.buffer; // eslint-disable-line no-unused-expressions + assert(arrayBufferViewHasBuffer(array)); + } +} diff --git a/test/js/node/test/parallel/test-buffer-badhex.js b/test/js/node/test/parallel/test-buffer-badhex.js new file mode 100644 index 00000000000000..61086659fa7b6e --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-badhex.js @@ -0,0 +1,48 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// Test hex strings and bad hex strings +{ + const buf = Buffer.alloc(4); + assert.strictEqual(buf.length, 4); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('abcdxx', 0, 'hex'), 2); + assert.deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0x00, 0x00])); + assert.strictEqual(buf.toString('hex'), 'abcd0000'); + assert.strictEqual(buf.write('abcdef01', 0, 'hex'), 4); + assert.deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0xef, 0x01])); + assert.strictEqual(buf.toString('hex'), 'abcdef01'); + + const copy = Buffer.from(buf.toString('hex'), 'hex'); + assert.strictEqual(buf.toString('hex'), copy.toString('hex')); +} + +{ + const buf = Buffer.alloc(5); + assert.strictEqual(buf.write('abcdxx', 1, 'hex'), 2); + assert.strictEqual(buf.toString('hex'), '00abcd0000'); +} + +{ + const buf = Buffer.alloc(4); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('xxabcd', 0, 'hex'), 0); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('xxab', 1, 'hex'), 0); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('cdxxab', 0, 'hex'), 1); + assert.deepStrictEqual(buf, Buffer.from([0xcd, 0, 0, 0])); +} + +{ + const buf = Buffer.alloc(256); + for (let i = 0; i < 256; i++) + buf[i] = i; + + const hex = buf.toString('hex'); + assert.deepStrictEqual(Buffer.from(hex, 'hex'), buf); + + const badHex = `${hex.slice(0, 256)}xx${hex.slice(256, 510)}`; + assert.deepStrictEqual(Buffer.from(badHex, 'hex'), buf.slice(0, 128)); +} diff --git a/test/js/node/test/parallel/test-buffer-bigint64.js b/test/js/node/test/parallel/test-buffer-bigint64.js new file mode 100644 index 00000000000000..3b7a28b99e43c8 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-bigint64.js @@ -0,0 +1,55 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const buf = Buffer.allocUnsafe(8); + +['LE', 'BE'].forEach(function(endianness) { + // Should allow simple BigInts to be written and read + let val = 123456789n; + buf[`writeBigInt64${endianness}`](val, 0); + let rtn = buf[`readBigInt64${endianness}`](0); + assert.strictEqual(val, rtn); + + // Should allow INT64_MAX to be written and read + val = 0x7fffffffffffffffn; + buf[`writeBigInt64${endianness}`](val, 0); + rtn = buf[`readBigInt64${endianness}`](0); + assert.strictEqual(val, rtn); + + // Should read and write a negative signed 64-bit integer + val = -123456789n; + buf[`writeBigInt64${endianness}`](val, 0); + assert.strictEqual(val, buf[`readBigInt64${endianness}`](0)); + + // Should read and write an unsigned 64-bit integer + val = 123456789n; + buf[`writeBigUInt64${endianness}`](val, 0); + assert.strictEqual(val, buf[`readBigUInt64${endianness}`](0)); + + // Should throw a RangeError upon INT64_MAX+1 being written + assert.throws(function() { + const val = 0x8000000000000000n; + buf[`writeBigInt64${endianness}`](val, 0); + }, RangeError); + + // Should throw a RangeError upon UINT64_MAX+1 being written + assert.throws(function() { + const val = 0x10000000000000000n; + buf[`writeBigUInt64${endianness}`](val, 0); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "value" is out of range. It must be ' + + '>= 0n and < 2n ** 64n. Received 18446744073709551616n' + }); + + // Should throw a TypeError upon invalid input + assert.throws(function() { + buf[`writeBigInt64${endianness}`]('bad', 0); + }, TypeError); + + // Should throw a TypeError upon invalid input + assert.throws(function() { + buf[`writeBigUInt64${endianness}`]('bad', 0); + }, TypeError); +}); diff --git a/test/js/node/test/parallel/test-buffer-bytelength.js b/test/js/node/test/parallel/test-buffer-bytelength.js new file mode 100644 index 00000000000000..95d54d425bc252 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-bytelength.js @@ -0,0 +1,132 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const SlowBuffer = require('buffer').SlowBuffer; +const vm = require('vm'); + +[ + [32, 'latin1'], + [NaN, 'utf8'], + [{}, 'latin1'], + [], +].forEach((args) => { + assert.throws( + () => Buffer.byteLength(...args), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string or an instance ' + + 'of Buffer or ArrayBuffer.' + + common.invalidArgTypeHelper(args[0]) + } + ); +}); + +assert(ArrayBuffer.isView(new Buffer(10))); +assert(ArrayBuffer.isView(new SlowBuffer(10))); +assert(ArrayBuffer.isView(Buffer.alloc(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); +assert(ArrayBuffer.isView(Buffer.from(''))); + +// buffer +const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); +assert.strictEqual(Buffer.byteLength(incomplete), 5); +const ascii = Buffer.from('abc'); +assert.strictEqual(Buffer.byteLength(ascii), 3); + +// ArrayBuffer +const buffer = new ArrayBuffer(8); +assert.strictEqual(Buffer.byteLength(buffer), 8); + +// TypedArray +const int8 = new Int8Array(8); +assert.strictEqual(Buffer.byteLength(int8), 8); +const uint8 = new Uint8Array(8); +assert.strictEqual(Buffer.byteLength(uint8), 8); +const uintc8 = new Uint8ClampedArray(2); +assert.strictEqual(Buffer.byteLength(uintc8), 2); +const int16 = new Int16Array(8); +assert.strictEqual(Buffer.byteLength(int16), 16); +const uint16 = new Uint16Array(8); +assert.strictEqual(Buffer.byteLength(uint16), 16); +const int32 = new Int32Array(8); +assert.strictEqual(Buffer.byteLength(int32), 32); +const uint32 = new Uint32Array(8); +assert.strictEqual(Buffer.byteLength(uint32), 32); +const float32 = new Float32Array(8); +assert.strictEqual(Buffer.byteLength(float32), 32); +const float64 = new Float64Array(8); +assert.strictEqual(Buffer.byteLength(float64), 64); + +// DataView +const dv = new DataView(new ArrayBuffer(2)); +assert.strictEqual(Buffer.byteLength(dv), 2); + +// Special case: zero length string +assert.strictEqual(Buffer.byteLength('', 'ascii'), 0); +assert.strictEqual(Buffer.byteLength('', 'HeX'), 0); + +// utf8 +assert.strictEqual(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19); +assert.strictEqual(Buffer.byteLength('κλμνξο', 'utf8'), 12); +assert.strictEqual(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15); +assert.strictEqual(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12); +// Without an encoding, utf8 should be assumed +assert.strictEqual(Buffer.byteLength('hey there'), 9); +assert.strictEqual(Buffer.byteLength('𠱸挶νξ#xx :)'), 17); +assert.strictEqual(Buffer.byteLength('hello world', ''), 11); +// It should also be assumed with unrecognized encoding +assert.strictEqual(Buffer.byteLength('hello world', 'abc'), 11); +assert.strictEqual(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10); + +// base64 +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'BASE64'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', 'base64'), 25 +); +// base64url +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'base64url'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'BASE64URL'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE', 'base64url'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64url'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw', 'base64url'), 25 +); +// special padding +assert.strictEqual(Buffer.byteLength('aaa=', 'base64'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64'), 3); +assert.strictEqual(Buffer.byteLength('aaa=', 'base64url'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64url'), 3); + +assert.strictEqual(Buffer.byteLength('Il était tué'), 14); +assert.strictEqual(Buffer.byteLength('Il était tué', 'utf8'), 14); + +['ascii', 'latin1', 'binary'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 12); + }); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 24); + }); + +// Test that ArrayBuffer from a different context is detected correctly +const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + +// Verify that invalid encodings are treated as utf8 +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.strictEqual(Buffer.byteLength('foo', encoding), + Buffer.byteLength('foo', 'utf8')); +} diff --git a/test/js/node/test/parallel/test-buffer-compare.js b/test/js/node/test/parallel/test-buffer-compare.js new file mode 100644 index 00000000000000..a0bddb5ab5d0f0 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-compare.js @@ -0,0 +1,47 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +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 + +assert.strictEqual(b.compare(c), -1); +assert.strictEqual(c.compare(d), 1); +assert.strictEqual(d.compare(b), 1); +assert.strictEqual(d.compare(e), 0); +assert.strictEqual(b.compare(d), -1); +assert.strictEqual(b.compare(b), 0); + +assert.strictEqual(Buffer.compare(b, c), -1); +assert.strictEqual(Buffer.compare(c, d), 1); +assert.strictEqual(Buffer.compare(d, b), 1); +assert.strictEqual(Buffer.compare(b, d), -1); +assert.strictEqual(Buffer.compare(c, c), 0); +assert.strictEqual(Buffer.compare(e, e), 0); +assert.strictEqual(Buffer.compare(d, e), 0); +assert.strictEqual(Buffer.compare(d, b), 1); + +assert.strictEqual(Buffer.compare(Buffer.alloc(0), Buffer.alloc(0)), 0); +assert.strictEqual(Buffer.compare(Buffer.alloc(0), Buffer.alloc(1)), -1); +assert.strictEqual(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0)), 1); + +assert.throws(() => Buffer.compare(Buffer.alloc(1), 'abc'), { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "buf2" argument must be an instance of Buffer or Uint8Array. ' + + 'Received type string ("abc")' +}); +assert.throws(() => Buffer.compare('abc', Buffer.alloc(1)), { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "buf1" argument must be an instance of Buffer or Uint8Array. ' + + 'Received type string ("abc")' +}); + +assert.throws(() => Buffer.alloc(1).compare('abc'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "target" argument must be an instance of ' + + 'Buffer or Uint8Array. Received type string ("abc")' +}); diff --git a/test/js/node/test/parallel/test-buffer-concat.js b/test/js/node/test/parallel/test-buffer-concat.js new file mode 100644 index 00000000000000..2e7541fb58b063 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-concat.js @@ -0,0 +1,100 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const zero = []; +const one = [ Buffer.from('asdf') ]; +const long = []; +for (let i = 0; i < 10; i++) long.push(Buffer.from('asdf')); + +const flatZero = Buffer.concat(zero); +const flatOne = Buffer.concat(one); +const flatLong = Buffer.concat(long); +const flatLongLen = Buffer.concat(long, 40); + +assert.strictEqual(flatZero.length, 0); +assert.strictEqual(flatOne.toString(), 'asdf'); + +const check = 'asdf'.repeat(10); + +// A special case where concat used to return the first item, +// if the length is one. This check is to make sure that we don't do that. +assert.notStrictEqual(flatOne, one[0]); +assert.strictEqual(flatLong.toString(), check); +assert.strictEqual(flatLongLen.toString(), check); + +[undefined, null, Buffer.from('hello')].forEach((value) => { + assert.throws(() => { + Buffer.concat(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list" argument must be an instance of Array.' + + `${common.invalidArgTypeHelper(value)}` + }); +}); + +[[42], ['hello', Buffer.from('world')]].forEach((value) => { + assert.throws(() => { + Buffer.concat(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list[0]" argument must be an instance of Buffer ' + + `or Uint8Array.${common.invalidArgTypeHelper(value[0])}` + }); +}); + +assert.throws(() => { + Buffer.concat([Buffer.from('hello'), 3]); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list[1]" argument must be an instance of Buffer ' + + 'or Uint8Array. Received type number (3)' +}); + +// eslint-disable-next-line node-core/crypto-check +const random10 = common.hasCrypto ? + require('crypto').randomBytes(10) : + Buffer.alloc(10, 1); +const empty = Buffer.alloc(0); + +assert.notDeepStrictEqual(random10, empty); +assert.notDeepStrictEqual(random10, Buffer.alloc(10)); + +assert.deepStrictEqual(Buffer.concat([], 100), empty); +assert.deepStrictEqual(Buffer.concat([random10], 0), empty); +assert.deepStrictEqual(Buffer.concat([random10], 10), random10); +assert.deepStrictEqual(Buffer.concat([random10, random10], 10), random10); +assert.deepStrictEqual(Buffer.concat([empty, random10]), random10); +assert.deepStrictEqual(Buffer.concat([random10, empty, empty]), random10); + +// The tail should be zero-filled +assert.deepStrictEqual(Buffer.concat([empty], 100), Buffer.alloc(100)); +assert.deepStrictEqual(Buffer.concat([empty], 4096), Buffer.alloc(4096)); +assert.deepStrictEqual( + Buffer.concat([random10], 40), + Buffer.concat([random10, Buffer.alloc(30)])); + +assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]), + new Uint8Array([0x43, 0x44])]), + Buffer.from('ABCD')); diff --git a/test/js/node/test/parallel/test-buffer-constants.js b/test/js/node/test/parallel/test-buffer-constants.js new file mode 100644 index 00000000000000..458d9a39c5364f --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-constants.js @@ -0,0 +1,17 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const { kMaxLength, kStringMaxLength } = require('buffer'); +const { MAX_LENGTH, MAX_STRING_LENGTH } = require('buffer').constants; + +assert.strictEqual(typeof MAX_LENGTH, 'number'); +assert.strictEqual(typeof MAX_STRING_LENGTH, 'number'); +assert(MAX_STRING_LENGTH <= MAX_LENGTH); +assert.throws(() => ' '.repeat(MAX_STRING_LENGTH + 1), /^RangeError: Out of memory$/); + +' '.repeat(MAX_STRING_LENGTH); // Should not throw. + +// Legacy values match: +assert.strictEqual(kMaxLength, MAX_LENGTH); +assert.strictEqual(kStringMaxLength, MAX_STRING_LENGTH); diff --git a/test/js/node/test/parallel/test-buffer-copy.js b/test/js/node/test/parallel/test-buffer-copy.js new file mode 100644 index 00000000000000..16a638a51181e6 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-copy.js @@ -0,0 +1,237 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const b = Buffer.allocUnsafe(1024); +const c = Buffer.allocUnsafe(512); + +let cntr = 0; + +{ + // copy 512 bytes, from 0 to 512. + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Current behavior is to coerce values to integers. + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, '0', '0', '512'); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Floats will be converted to integers via `Math.floor` + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 512.5); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Copy c into b, without specifying sourceEnd + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0, 0); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copy c into b, without specifying sourceStart + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copied source range greater than source length + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0, 0, c.length + 1); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copy longer buffer b to shorter c without targetStart + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Copy starting near end of b to c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, b.length - Math.floor(c.length / 2)); + assert.strictEqual(copied, Math.floor(c.length / 2)); + for (let i = 0; i < Math.floor(c.length / 2); i++) { + assert.strictEqual(c[i], b[b.length - Math.floor(c.length / 2) + i]); + } + for (let i = Math.floor(c.length / 2) + 1; i < c.length; i++) { + assert.strictEqual(c[c.length - 1], c[i]); + } +} + +{ + // Try to copy 513 bytes, and check we don't overrun c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 513); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // copy 768 bytes from b into b + b.fill(++cntr); + b.fill(++cntr, 256); + const copied = b.copy(b, 0, 256, 1024); + assert.strictEqual(copied, 768); + for (let i = 0; i < b.length; i++) { + assert.strictEqual(b[i], cntr); + } +} + +// Copy string longer than buffer length (failure will segfault) +const bb = Buffer.allocUnsafe(10); +bb.fill('hello crazy world'); + + +// Try to copy from before the beginning of b. Should not throw. +b.copy(c, 0, 100, 10); + +// Throw with invalid source type +assert.throws( + () => Buffer.prototype.copy.call(0), + { + // code: 'ERR_INVALID_ARG_TYPE', + code: 'ERR_INVALID_THIS', + name: 'TypeError', + } +); + +// Copy throws at negative targetStart +assert.throws( + () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), -1, 0), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "targetStart" is out of range. ' + + 'It must be >= 0 and <= 5. Received -1' + } +); + +// Copy throws at negative sourceStart +assert.throws( + () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), 0, -1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + } +); + +// Copy throws if sourceStart is greater than length of source +assert.throws( + () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), 0, 100), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + } +); + +{ + // Check sourceEnd resets to targetEnd if former is greater than the latter + b.fill(++cntr); + c.fill(++cntr); + b.copy(c, 0, 0, 1025); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +// Throw with negative sourceEnd +assert.throws( + () => b.copy(c, 0, 0, -1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "sourceEnd" is out of range. ' + + 'It must be >= 0 and <= 1024. Received -1' + } +); + +// When sourceStart is greater than sourceEnd, zero copied +assert.strictEqual(b.copy(c, 0, 100, 10), 0); + +// When targetStart > targetLength, zero copied +assert.strictEqual(b.copy(c, 512, 0, 10), 0); + +// Test that the `target` can be a Uint8Array. +{ + const d = new Uint8Array(c); + // copy 512 bytes, from 0 to 512. + b.fill(++cntr); + d.fill(++cntr); + const copied = b.copy(d, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < d.length; i++) { + assert.strictEqual(d[i], b[i]); + } +} + +// Test that the source can be a Uint8Array, too. +{ + const e = new Uint8Array(b); + // copy 512 bytes, from 0 to 512. + e.fill(++cntr); + c.fill(++cntr); + const copied = Buffer.prototype.copy.call(e, c, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], e[i]); + } +} + +// https://github.com/nodejs/node/issues/23668: Do not crash for invalid input. +c.fill('c'); +b.copy(c, 'not a valid offset'); +// Make sure this acted like a regular copy with `0` offset. +assert.deepStrictEqual(c, b.slice(0, c.length)); + +{ + c.fill('C'); + assert.throws(() => { + b.copy(c, { [Symbol.toPrimitive]() { throw new Error('foo'); } }); + }, /foo/); + // No copying took place: + assert.deepStrictEqual(c.toString(), 'C'.repeat(c.length)); +} diff --git a/test/js/node/test/parallel/test-buffer-equals.js b/test/js/node/test/parallel/test-buffer-equals.js new file mode 100644 index 00000000000000..62b1289b3466d1 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-equals.js @@ -0,0 +1,25 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdf'); +const c = Buffer.from('abcdf'); +const d = Buffer.from('abcde'); +const e = Buffer.from('abcdef'); + +assert.ok(b.equals(c)); +assert.ok(!c.equals(d)); +assert.ok(!d.equals(e)); +assert.ok(d.equals(d)); +assert.ok(d.equals(new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]))); + +assert.throws( + () => Buffer.alloc(1).equals('abc'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "otherBuffer" argument must be an instance of ' + + 'Buffer or Uint8Array. Received type string ("abc")' + } +); diff --git a/test/js/node/test/parallel/test-buffer-fill.js b/test/js/node/test/parallel/test-buffer-fill.js new file mode 100644 index 00000000000000..32291d3123bc48 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-fill.js @@ -0,0 +1,429 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const SIZE = 28; + +const buf1 = Buffer.allocUnsafe(SIZE); +const buf2 = Buffer.allocUnsafe(SIZE); + +// 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); + +// UTF8 +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'); +assert.strictEqual(Buffer.allocUnsafe(1).fill(0).fill('\u0222')[0], 0xc8); + +// BINARY +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'); + +// LATIN1 +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'); + +// UCS2 +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'); +assert.strictEqual(Buffer.allocUnsafe(1).fill('\u0222', 'ucs2')[0], 0x22); + +// HEX +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'); + +assert.throws(() => { + const buf = Buffer.allocUnsafe(SIZE); + + buf.fill('yKJh', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + +assert.throws(() => { + const buf = Buffer.allocUnsafe(SIZE); + + buf.fill('\u0222', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + +// BASE64 +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'); + +// BASE64URL +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'); + +// Buffer +function deepStrictEqualValues(buf, arr) { + for (const [index, value] of buf.entries()) { + assert.deepStrictEqual(value, 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]); + +// Check exceptions +[ + [0, -1], + [0, 0, buf1.length + 1], + ['', -1], + ['', 0, buf1.length + 1], + ['', 1, -1], +].forEach((args) => { + assert.throws( + () => buf1.fill(...args), + { code: 'ERR_OUT_OF_RANGE' } + ); +}); + +assert.throws( + () => buf1.fill('a', 0, buf1.length, 'node rocks!'), + { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: node rocks!' + } +); + +[ + ['a', 0, 0, NaN], + ['a', 0, 0, false], +].forEach((args) => { + assert.throws( + () => buf1.fill(...args), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "encoding" argument must be of type ' + + `string.${common.invalidArgTypeHelper(args[3])}` + } + ); +}); + +assert.throws( + () => buf1.fill('a', 0, 0, 'foo'), + { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: foo' + } +); + +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); +} + +// This is mostly accurate. Except write() won't write partial bytes to the +// string while fill() blindly copies bytes into memory. To account for that an +// error will be thrown if not all the data can be written, and the SIZE has +// been massaged to work with the input characters. +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; + } + + // Should never be reached. + if (offset < 0 || end > buf2.length) + throw new ERR_OUT_OF_RANGE(); + + if (end <= offset) + return buf2; + + offset >>>= 0; + end >>>= 0; + assert(offset <= buf2.length); + + // Convert "end" to "length" (which write understands). + const length = end - offset < 0 ? 0 : end - offset; + + let wasZero = false; + do { + const written = buf2.write(string, offset, length, encoding); + offset += written; + // Safety check in case write falls into infinite loop. + 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); + // Swap bytes on BE archs for ucs2 encoding. + assert.deepStrictEqual(buf1.fill.apply(buf1, arguments), + writeToFill.apply(null, arguments)); +} + +// Make sure these throw. +assert.throws( + () => Buffer.allocUnsafe(8).fill('a', -1), + { code: 'ERR_OUT_OF_RANGE' }); +assert.throws( + () => Buffer.allocUnsafe(8).fill('a', 0, 9), + { code: 'ERR_OUT_OF_RANGE' }); + +// Make sure this doesn't hang indefinitely. +Buffer.allocUnsafe(8).fill(''); +Buffer.alloc(8, ''); + +{ + const buf = Buffer.alloc(64, 10); + for (let i = 0; i < buf.length; i++) + assert.strictEqual(buf[i], 10); + + buf.fill(11, 0, buf.length >> 1); + for (let i = 0; i < buf.length >> 1; i++) + assert.strictEqual(buf[i], 11); + for (let i = (buf.length >> 1) + 1; i < buf.length; i++) + assert.strictEqual(buf[i], 10); + + buf.fill('h'); + for (let i = 0; i < buf.length; i++) + assert.strictEqual(buf[i], 'h'.charCodeAt(0)); + + buf.fill(0); + for (let i = 0; i < buf.length; i++) + assert.strictEqual(buf[i], 0); + + buf.fill(null); + for (let i = 0; i < buf.length; i++) + assert.strictEqual(buf[i], 0); + + buf.fill(1, 16, 32); + for (let i = 0; i < 16; i++) + assert.strictEqual(buf[i], 0); + for (let i = 16; i < 32; i++) + assert.strictEqual(buf[i], 1); + for (let i = 32; i < buf.length; i++) + assert.strictEqual(buf[i], 0); +} + +{ + const buf = Buffer.alloc(10, 'abc'); + assert.strictEqual(buf.toString(), 'abcabcabca'); + buf.fill('է'); + assert.strictEqual(buf.toString(), 'էէէէէ'); +} + +// Make sure "end" is properly checked, even if it's magically mangled using +// Symbol.toPrimitive. +{ + assert.throws(() => { + const end = { + [Symbol.toPrimitive]() { + return 1; + } + }; + Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "end" argument must be of type number. Received an ' + + 'instance of Object' + }); +} + +// Test that bypassing 'length' won't cause an abort. +// assert.throws(() => { +// const buf = Buffer.from('w00t'); +// Object.defineProperty(buf, 'length', { +// value: 1337, +// enumerable: true +// }); +// buf.fill(''); +// }, { +// code: 'ERR_BUFFER_OUT_OF_BOUNDS', +// name: 'RangeError', +// message: 'Attempt to access memory outside buffer bounds' +// }); + +assert.deepStrictEqual( + Buffer.allocUnsafeSlow(16).fill('ab', 'utf16le'), + Buffer.from('61006200610062006100620061006200', 'hex')); + +assert.deepStrictEqual( + Buffer.allocUnsafeSlow(15).fill('ab', 'utf16le'), + Buffer.from('610062006100620061006200610062', 'hex')); + +assert.deepStrictEqual( + Buffer.allocUnsafeSlow(16).fill('ab', 'utf16le'), + Buffer.from('61006200610062006100620061006200', 'hex')); +assert.deepStrictEqual( + Buffer.allocUnsafeSlow(16).fill('a', 'utf16le'), + Buffer.from('61006100610061006100610061006100', 'hex')); + +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('a', 'utf16le').toString('utf16le'), + 'a'.repeat(8)); +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('a', 'latin1').toString('latin1'), + 'a'.repeat(16)); +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('a', 'utf8').toString('utf8'), + 'a'.repeat(16)); + +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('Љ', 'utf16le').toString('utf16le'), + 'Љ'.repeat(8)); +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('Љ', 'latin1').toString('latin1'), + '\t'.repeat(16)); +assert.strictEqual( + Buffer.allocUnsafeSlow(16).fill('Љ', 'utf8').toString('utf8'), + 'Љ'.repeat(8)); + +assert.throws(() => { + const buf = Buffer.from('a'.repeat(1000)); + + buf.fill('This is not correctly encoded', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + + +{ + const bufEmptyString = Buffer.alloc(5, ''); + assert.strictEqual(bufEmptyString.toString(), '\x00\x00\x00\x00\x00'); + + const bufEmptyArray = Buffer.alloc(5, []); + assert.strictEqual(bufEmptyArray.toString(), '\x00\x00\x00\x00\x00'); + + const bufEmptyBuffer = Buffer.alloc(5, Buffer.alloc(5)); + assert.strictEqual(bufEmptyBuffer.toString(), '\x00\x00\x00\x00\x00'); + + const bufZero = Buffer.alloc(5, 0); + assert.strictEqual(bufZero.toString(), '\x00\x00\x00\x00\x00'); +} diff --git a/test/js/node/test/parallel/test-buffer-from.js b/test/js/node/test/parallel/test-buffer-from.js new file mode 100644 index 00000000000000..60236acfa0aea7 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-from.js @@ -0,0 +1,141 @@ +'use strict'; + +const common = require('../common'); +const { deepStrictEqual, strictEqual, throws } = require('assert'); +const { runInNewContext } = require('vm'); + +const checkString = 'test'; + +const check = Buffer.from(checkString); + +class MyString extends String { + constructor() { + super(checkString); + } +} + +class MyPrimitive { + [Symbol.toPrimitive]() { + return checkString; + } +} + +class MyBadPrimitive { + [Symbol.toPrimitive]() { + return 1; + } +} + +deepStrictEqual(Buffer.from(new String(checkString)), check); +deepStrictEqual(Buffer.from(new MyString()), check); +deepStrictEqual(Buffer.from(new MyPrimitive()), check); +deepStrictEqual( + Buffer.from(runInNewContext('new String(checkString)', { checkString })), + check +); + +[ + {}, + new Boolean(true), + { valueOf() { return null; } }, + { valueOf() { return undefined; } }, + { valueOf: null }, + { __proto__: null }, + new Number(true), + new MyBadPrimitive(), + Symbol(), + 5n, + (one, two, three) => {}, + undefined, + null, +].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The first argument must be of type string, ' + + 'Buffer, ArrayBuffer, Array, or Array-like Object.' + + common.invalidArgTypeHelper(input) + }; + throws(() => Buffer.from(input), errObj); + throws(() => Buffer.from(input, 'hex'), errObj); +}); + +Buffer.allocUnsafe(10); // Should not throw. +Buffer.from('deadbeaf', 'hex'); // Should not throw. + + +{ + const u16 = new Uint16Array([0xffff]); + const b16 = Buffer.copyBytesFrom(u16); + u16[0] = 0; + strictEqual(b16.length, 2); + strictEqual(b16[0], 255); + strictEqual(b16[1], 255); +} + +{ + const u16 = new Uint16Array([0, 0xffff]); + const b16 = Buffer.copyBytesFrom(u16, 1, 5); + u16[0] = 0xffff; + u16[1] = 0; + strictEqual(b16.length, 2); + strictEqual(b16[0], 255); + strictEqual(b16[1], 255); +} + +{ + const u32 = new Uint32Array([0xffffffff]); + const b32 = Buffer.copyBytesFrom(u32); + u32[0] = 0; + strictEqual(b32.length, 4); + strictEqual(b32[0], 255); + strictEqual(b32[1], 255); + strictEqual(b32[2], 255); + strictEqual(b32[3], 255); +} + +throws(() => { + Buffer.copyBytesFrom(); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +['', Symbol(), true, false, {}, [], () => {}, 1, 1n, null, undefined].forEach( + (notTypedArray) => throws(() => { + Buffer.copyBytesFrom('nope'); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), notANumber); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +[-1, NaN, 1.1, -Infinity].forEach((outOfRange) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), outOfRange); + }, { + code: 'ERR_OUT_OF_RANGE', + }) +); + +['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), 0, notANumber); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +[-1, NaN, 1.1, -Infinity].forEach((outOfRange) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), 0, outOfRange); + }, { + code: 'ERR_OUT_OF_RANGE', + }) +); diff --git a/test/js/node/test/parallel/test-buffer-includes.js b/test/js/node/test/parallel/test-buffer-includes.js new file mode 100644 index 00000000000000..5cc7648b1e0d92 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-includes.js @@ -0,0 +1,304 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdef'); +const buf_a = Buffer.from('a'); +const buf_bc = Buffer.from('bc'); +const buf_f = Buffer.from('f'); +const buf_z = Buffer.from('z'); +const buf_empty = Buffer.from(''); + +assert(b.includes('a')); +assert(!b.includes('a', 1)); +assert(!b.includes('a', -1)); +assert(!b.includes('a', -4)); +assert(b.includes('a', -b.length)); +assert(b.includes('a', NaN)); +assert(b.includes('a', -Infinity)); +assert(!b.includes('a', Infinity)); +assert(b.includes('bc')); +assert(!b.includes('bc', 2)); +assert(!b.includes('bc', -1)); +assert(!b.includes('bc', -3)); +assert(b.includes('bc', -5)); +assert(b.includes('bc', NaN)); +assert(b.includes('bc', -Infinity)); +assert(!b.includes('bc', Infinity)); +assert(b.includes('f'), b.length - 1); +assert(!b.includes('z')); +assert(b.includes('')); +assert(b.includes('', 1)); +assert(b.includes('', b.length + 1)); +assert(b.includes('', Infinity)); +assert(b.includes(buf_a)); +assert(!b.includes(buf_a, 1)); +assert(!b.includes(buf_a, -1)); +assert(!b.includes(buf_a, -4)); +assert(b.includes(buf_a, -b.length)); +assert(b.includes(buf_a, NaN)); +assert(b.includes(buf_a, -Infinity)); +assert(!b.includes(buf_a, Infinity)); +assert(b.includes(buf_bc)); +assert(!b.includes(buf_bc, 2)); +assert(!b.includes(buf_bc, -1)); +assert(!b.includes(buf_bc, -3)); +assert(b.includes(buf_bc, -5)); +assert(b.includes(buf_bc, NaN)); +assert(b.includes(buf_bc, -Infinity)); +assert(!b.includes(buf_bc, Infinity)); +assert(b.includes(buf_f), b.length - 1); +assert(!b.includes(buf_z)); +assert(b.includes(buf_empty)); +assert(b.includes(buf_empty, 1)); +assert(b.includes(buf_empty, b.length + 1)); +assert(b.includes(buf_empty, Infinity)); +assert(b.includes(0x61)); +assert(!b.includes(0x61, 1)); +assert(!b.includes(0x61, -1)); +assert(!b.includes(0x61, -4)); +assert(b.includes(0x61, -b.length)); +assert(b.includes(0x61, NaN)); +assert(b.includes(0x61, -Infinity)); +assert(!b.includes(0x61, Infinity)); +assert(!b.includes(0x0)); + +// test offsets +assert(b.includes('d', 2)); +assert(b.includes('f', 5)); +assert(b.includes('f', -1)); +assert(!b.includes('f', 6)); + +assert(b.includes(Buffer.from('d'), 2)); +assert(b.includes(Buffer.from('f'), 5)); +assert(b.includes(Buffer.from('f'), -1)); +assert(!b.includes(Buffer.from('f'), 6)); + +assert(!Buffer.from('ff').includes(Buffer.from('f'), 1, 'ucs2')); + +// test hex encoding +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .includes('64', 0, 'hex'), + true +); +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .includes(Buffer.from('64', 'hex'), 0, 'hex'), + true +); + +// Test base64 encoding +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .includes('ZA==', 0, 'base64'), + true +); +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .includes(Buffer.from('ZA==', 'base64'), 0, 'base64'), + true +); + +// test ascii encoding +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .includes('d', 0, 'ascii'), + true +); +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .includes(Buffer.from('d', 'ascii'), 0, 'ascii'), + true +); + +// Test latin1 encoding +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .includes('d', 0, 'latin1'), + true +); +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .includes(Buffer.from('d', 'latin1'), 0, 'latin1'), + true +); + +// Test binary encoding +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .includes('d', 0, 'binary'), + true +); +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .includes(Buffer.from('d', 'binary'), 0, 'binary'), + true +); + + +// test ucs2 encoding +let twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + +assert(twoByteString.includes('\u0395', 4, 'ucs2')); +assert(twoByteString.includes('\u03a3', -4, 'ucs2')); +assert(twoByteString.includes('\u03a3', -6, 'ucs2')); +assert(twoByteString.includes( + Buffer.from('\u03a3', 'ucs2'), -6, 'ucs2')); +assert(!twoByteString.includes('\u03a3', -2, 'ucs2')); + +const mixedByteStringUcs2 = + Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2'); +assert(mixedByteStringUcs2.includes('bc', 0, 'ucs2')); +assert(mixedByteStringUcs2.includes('\u03a3', 0, 'ucs2')); +assert(!mixedByteStringUcs2.includes('\u0396', 0, 'ucs2')); + +assert.ok( + mixedByteStringUcs2.includes(Buffer.from('bc', 'ucs2'), 0, 'ucs2')); +assert.ok( + mixedByteStringUcs2.includes(Buffer.from('\u03a3', 'ucs2'), 0, 'ucs2')); +assert.ok( + !mixedByteStringUcs2.includes(Buffer.from('\u0396', 'ucs2'), 0, 'ucs2')); + +twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + +// Test single char pattern +assert(twoByteString.includes('\u039a', 0, 'ucs2')); +assert(twoByteString.includes('\u0391', 0, 'ucs2'), 'Alpha'); +assert(twoByteString.includes('\u03a3', 0, 'ucs2'), 'First Sigma'); +assert(twoByteString.includes('\u03a3', 6, 'ucs2'), 'Second Sigma'); +assert(twoByteString.includes('\u0395', 0, 'ucs2'), 'Epsilon'); +assert(!twoByteString.includes('\u0392', 0, 'ucs2'), 'Not beta'); + +// Test multi-char pattern +assert(twoByteString.includes('\u039a\u0391', 0, 'ucs2'), 'Lambda Alpha'); +assert(twoByteString.includes('\u0391\u03a3', 0, 'ucs2'), 'Alpha Sigma'); +assert(twoByteString.includes('\u03a3\u03a3', 0, 'ucs2'), 'Sigma Sigma'); +assert(twoByteString.includes('\u03a3\u0395', 0, 'ucs2'), 'Sigma Epsilon'); + +const mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395'); +assert(mixedByteStringUtf8.includes('bc')); +assert(mixedByteStringUtf8.includes('bc', 5)); +assert(mixedByteStringUtf8.includes('bc', -8)); +assert(mixedByteStringUtf8.includes('\u03a3')); +assert(!mixedByteStringUtf8.includes('\u0396')); + + +// Test complex string includes algorithms. Only trigger for long strings. +// Long string that isn't a simple repeat of a shorter string. +let longString = 'A'; +for (let i = 66; i < 76; i++) { // from 'B' to 'K' + longString = longString + String.fromCharCode(i) + longString; +} + +const longBufferString = Buffer.from(longString); + +// Pattern of 15 chars, repeated every 16 chars in long +let pattern = 'ABACABADABACABA'; +for (let i = 0; i < longBufferString.length - pattern.length; i += 7) { + const includes = longBufferString.includes(pattern, i); + assert(includes, `Long ABACABA...-string at index ${i}`); +} +assert(longBufferString.includes('AJABACA'), 'Long AJABACA, First J'); +assert(longBufferString.includes('AJABACA', 511), 'Long AJABACA, Second J'); + +pattern = 'JABACABADABACABA'; +assert(longBufferString.includes(pattern), 'Long JABACABA..., First J'); +assert(longBufferString.includes(pattern, 512), 'Long JABACABA..., Second J'); + +// Search for a non-ASCII string in a pure ASCII string. +const asciiString = Buffer.from( + 'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'); +assert(!asciiString.includes('\x2061')); +assert(asciiString.includes('leb', 0)); + +// Search in string containing many non-ASCII chars. +const allCodePoints = []; +for (let i = 0; i < 65534; i++) allCodePoints[i] = i; +const allCharsString = String.fromCharCode.apply(String, allCodePoints) + + String.fromCharCode(65534, 65535); +const allCharsBufferUtf8 = Buffer.from(allCharsString); +const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2'); + +// Search for string long enough to trigger complex search with ASCII pattern +// and UC16 subject. +assert(!allCharsBufferUtf8.includes('notfound')); +assert(!allCharsBufferUcs2.includes('notfound')); + +// Find substrings in Utf8. +let lengths = [1, 3, 15]; // Single char, simple and complex. +let indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b]; +for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i]; + let length = lengths[lengthIndex]; + + if (index + length > 0x7F) { + length = 2 * length; + } + + if (index + length > 0x7FF) { + length = 3 * length; + } + + if (index + length > 0xFFFF) { + length = 4 * length; + } + + const patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length); + assert(index, allCharsBufferUtf8.includes(patternBufferUtf8)); + + const patternStringUtf8 = patternBufferUtf8.toString(); + assert(index, allCharsBufferUtf8.includes(patternStringUtf8)); + } +} + +// Find substrings in Usc2. +lengths = [2, 4, 16]; // Single char, simple and complex. +indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0]; +for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i] * 2; + const length = lengths[lengthIndex]; + + const patternBufferUcs2 = + allCharsBufferUcs2.slice(index, index + length); + assert.ok( + allCharsBufferUcs2.includes(patternBufferUcs2, 0, 'ucs2')); + + const patternStringUcs2 = patternBufferUcs2.toString('ucs2'); + assert.ok( + allCharsBufferUcs2.includes(patternStringUcs2, 0, 'ucs2')); + } +} + +[ + () => { }, + {}, + [], +].forEach((val) => { + assert.throws( + () => b.includes(val), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "value" argument must be of type number, string, Buffer, or Uint8Array.' + common.invalidArgTypeHelper(val) + } + ); +}); + +// Test truncation of Number arguments to uint8 +{ + const buf = Buffer.from('this is a test'); + assert.ok(buf.includes(0x6973)); + assert.ok(buf.includes(0x697320)); + assert.ok(buf.includes(0x69732069)); + assert.ok(buf.includes(0x697374657374)); + assert.ok(buf.includes(0x69737374)); + assert.ok(buf.includes(0x69737465)); + assert.ok(buf.includes(0x69737465)); + assert.ok(buf.includes(-140)); + assert.ok(buf.includes(-152)); + assert.ok(!buf.includes(0xff)); + assert.ok(!buf.includes(0xffff)); +} diff --git a/test/js/node/test/parallel/test-buffer-indexof.js b/test/js/node/test/parallel/test-buffer-indexof.js new file mode 100644 index 00000000000000..524230c01bc7de --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-indexof.js @@ -0,0 +1,629 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdef'); +const buf_a = Buffer.from('a'); +const buf_bc = Buffer.from('bc'); +const buf_f = Buffer.from('f'); +const buf_z = Buffer.from('z'); +const buf_empty = Buffer.from(''); + +const s = 'abcdef'; + +assert.strictEqual(b.indexOf('a'), 0); +assert.strictEqual(b.indexOf('a', 1), -1); +assert.strictEqual(b.indexOf('a', -1), -1); +assert.strictEqual(b.indexOf('a', -4), -1); +assert.strictEqual(b.indexOf('a', -b.length), 0); +assert.strictEqual(b.indexOf('a', NaN), 0); +assert.strictEqual(b.indexOf('a', -Infinity), 0); +assert.strictEqual(b.indexOf('a', Infinity), -1); +assert.strictEqual(b.indexOf('bc'), 1); +assert.strictEqual(b.indexOf('bc', 2), -1); +assert.strictEqual(b.indexOf('bc', -1), -1); +assert.strictEqual(b.indexOf('bc', -3), -1); +assert.strictEqual(b.indexOf('bc', -5), 1); +assert.strictEqual(b.indexOf('bc', NaN), 1); +assert.strictEqual(b.indexOf('bc', -Infinity), 1); +assert.strictEqual(b.indexOf('bc', Infinity), -1); +assert.strictEqual(b.indexOf('f'), b.length - 1); +assert.strictEqual(b.indexOf('z'), -1); +assert.strictEqual(b.indexOf(''), 0); +assert.strictEqual(b.indexOf('', 1), 1); +assert.strictEqual(b.indexOf('', b.length + 1), b.length); +assert.strictEqual(b.indexOf('', Infinity), b.length); +assert.strictEqual(b.indexOf(buf_a), 0); +assert.strictEqual(b.indexOf(buf_a, 1), -1); +assert.strictEqual(b.indexOf(buf_a, -1), -1); +assert.strictEqual(b.indexOf(buf_a, -4), -1); +assert.strictEqual(b.indexOf(buf_a, -b.length), 0); +assert.strictEqual(b.indexOf(buf_a, NaN), 0); +assert.strictEqual(b.indexOf(buf_a, -Infinity), 0); +assert.strictEqual(b.indexOf(buf_a, Infinity), -1); +assert.strictEqual(b.indexOf(buf_bc), 1); +assert.strictEqual(b.indexOf(buf_bc, 2), -1); +assert.strictEqual(b.indexOf(buf_bc, -1), -1); +assert.strictEqual(b.indexOf(buf_bc, -3), -1); +assert.strictEqual(b.indexOf(buf_bc, -5), 1); +assert.strictEqual(b.indexOf(buf_bc, NaN), 1); +assert.strictEqual(b.indexOf(buf_bc, -Infinity), 1); +assert.strictEqual(b.indexOf(buf_bc, Infinity), -1); +assert.strictEqual(b.indexOf(buf_f), b.length - 1); +assert.strictEqual(b.indexOf(buf_z), -1); +assert.strictEqual(b.indexOf(buf_empty), 0); +assert.strictEqual(b.indexOf(buf_empty, 1), 1); +assert.strictEqual(b.indexOf(buf_empty, b.length + 1), b.length); +assert.strictEqual(b.indexOf(buf_empty, Infinity), b.length); +assert.strictEqual(b.indexOf(0x61), 0); +assert.strictEqual(b.indexOf(0x61, 1), -1); +assert.strictEqual(b.indexOf(0x61, -1), -1); +assert.strictEqual(b.indexOf(0x61, -4), -1); +assert.strictEqual(b.indexOf(0x61, -b.length), 0); +assert.strictEqual(b.indexOf(0x61, NaN), 0); +assert.strictEqual(b.indexOf(0x61, -Infinity), 0); +assert.strictEqual(b.indexOf(0x61, Infinity), -1); +assert.strictEqual(b.indexOf(0x0), -1); + +// test offsets +assert.strictEqual(b.indexOf('d', 2), 3); +assert.strictEqual(b.indexOf('f', 5), 5); +assert.strictEqual(b.indexOf('f', -1), 5); +assert.strictEqual(b.indexOf('f', 6), -1); + +assert.strictEqual(b.indexOf(Buffer.from('d'), 2), 3); +assert.strictEqual(b.indexOf(Buffer.from('f'), 5), 5); +assert.strictEqual(b.indexOf(Buffer.from('f'), -1), 5); +assert.strictEqual(b.indexOf(Buffer.from('f'), 6), -1); + +assert.strictEqual(Buffer.from('ff').indexOf(Buffer.from('f'), 1, 'ucs2'), -1); + +// Test invalid and uppercase encoding +assert.strictEqual(b.indexOf('b', 'utf8'), 1); +assert.strictEqual(b.indexOf('b', 'UTF8'), 1); +assert.strictEqual(b.indexOf('62', 'HEX'), 1); +assert.throws(() => b.indexOf('bad', 'enc'), /Unknown encoding: enc/); + +// test hex encoding +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .indexOf('64', 0, 'hex'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .indexOf(Buffer.from('64', 'hex'), 0, 'hex'), + 3 +); + +// Test base64 encoding +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .indexOf('ZA==', 0, 'base64'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .indexOf(Buffer.from('ZA==', 'base64'), 0, 'base64'), + 3 +); + +// Test base64url encoding +assert.strictEqual( + Buffer.from(b.toString('base64url'), 'base64url') + .indexOf('ZA==', 0, 'base64url'), + 3 +); + +// test ascii encoding +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .indexOf('d', 0, 'ascii'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .indexOf(Buffer.from('d', 'ascii'), 0, 'ascii'), + 3 +); + +// Test latin1 encoding +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .indexOf('d', 0, 'latin1'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .indexOf(Buffer.from('d', 'latin1'), 0, 'latin1'), + 3 +); +assert.strictEqual( + Buffer.from('aa\u00e8aa', 'latin1') + .indexOf('\u00e8', 'latin1'), + 2 +); +assert.strictEqual( + Buffer.from('\u00e8', 'latin1') + .indexOf('\u00e8', 'latin1'), + 0 +); +assert.strictEqual( + Buffer.from('\u00e8', 'latin1') + .indexOf(Buffer.from('\u00e8', 'latin1'), 'latin1'), + 0 +); + +// Test binary encoding +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .indexOf('d', 0, 'binary'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .indexOf(Buffer.from('d', 'binary'), 0, 'binary'), + 3 +); +assert.strictEqual( + Buffer.from('aa\u00e8aa', 'binary') + .indexOf('\u00e8', 'binary'), + 2 +); +assert.strictEqual( + Buffer.from('\u00e8', 'binary') + .indexOf('\u00e8', 'binary'), + 0 +); +assert.strictEqual( + Buffer.from('\u00e8', 'binary') + .indexOf(Buffer.from('\u00e8', 'binary'), 'binary'), + 0 +); + + +// Test optional offset with passed encoding +assert.strictEqual(Buffer.from('aaaa0').indexOf('30', 'hex'), 4); +assert.strictEqual(Buffer.from('aaaa00a').indexOf('3030', 'hex'), 4); + +{ + // Test usc2 and utf16le encoding + ['ucs2', 'utf16le'].forEach((encoding) => { + const twoByteString = Buffer.from( + '\u039a\u0391\u03a3\u03a3\u0395', encoding); + + assert.strictEqual(twoByteString.indexOf('\u0395', 4, encoding), 8); + assert.strictEqual(twoByteString.indexOf('\u03a3', -4, encoding), 6); + assert.strictEqual(twoByteString.indexOf('\u03a3', -6, encoding), 4); + assert.strictEqual(twoByteString.indexOf( + Buffer.from('\u03a3', encoding), -6, encoding), 4); + assert.strictEqual(-1, twoByteString.indexOf('\u03a3', -2, encoding)); + }); +} + +const mixedByteStringUcs2 = + Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2'); +assert.strictEqual(mixedByteStringUcs2.indexOf('bc', 0, 'ucs2'), 6); +assert.strictEqual(mixedByteStringUcs2.indexOf('\u03a3', 0, 'ucs2'), 10); +assert.strictEqual(-1, mixedByteStringUcs2.indexOf('\u0396', 0, 'ucs2')); + +assert.strictEqual( + mixedByteStringUcs2.indexOf(Buffer.from('bc', 'ucs2'), 0, 'ucs2'), 6); +assert.strictEqual( + mixedByteStringUcs2.indexOf(Buffer.from('\u03a3', 'ucs2'), 0, 'ucs2'), 10); +assert.strictEqual( + -1, mixedByteStringUcs2.indexOf(Buffer.from('\u0396', 'ucs2'), 0, 'ucs2')); + +{ + const twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + + // Test single char pattern + assert.strictEqual(twoByteString.indexOf('\u039a', 0, 'ucs2'), 0); + let index = twoByteString.indexOf('\u0391', 0, 'ucs2'); + assert.strictEqual(index, 2, `Alpha - at index ${index}`); + index = twoByteString.indexOf('\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 4, `First Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3', 6, 'ucs2'); + assert.strictEqual(index, 6, `Second Sigma - at index ${index}`); + index = twoByteString.indexOf('\u0395', 0, 'ucs2'); + assert.strictEqual(index, 8, `Epsilon - at index ${index}`); + index = twoByteString.indexOf('\u0392', 0, 'ucs2'); + assert.strictEqual(-1, index, `Not beta - at index ${index}`); + + // Test multi-char pattern + index = twoByteString.indexOf('\u039a\u0391', 0, 'ucs2'); + assert.strictEqual(index, 0, `Lambda Alpha - at index ${index}`); + index = twoByteString.indexOf('\u0391\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 2, `Alpha Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 4, `Sigma Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3\u0395', 0, 'ucs2'); + assert.strictEqual(index, 6, `Sigma Epsilon - at index ${index}`); +} + +const mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395'); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc'), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc', 5), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc', -8), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('\u03a3'), 7); +assert.strictEqual(mixedByteStringUtf8.indexOf('\u0396'), -1); + + +// Test complex string indexOf algorithms. Only trigger for long strings. +// Long string that isn't a simple repeat of a shorter string. +let longString = 'A'; +for (let i = 66; i < 76; i++) { // from 'B' to 'K' + longString = longString + String.fromCharCode(i) + longString; +} + +const longBufferString = Buffer.from(longString); + +// Pattern of 15 chars, repeated every 16 chars in long +let pattern = 'ABACABADABACABA'; +for (let i = 0; i < longBufferString.length - pattern.length; i += 7) { + const index = longBufferString.indexOf(pattern, i); + assert.strictEqual((i + 15) & ~0xf, index, + `Long ABACABA...-string at index ${i}`); +} + +let index = longBufferString.indexOf('AJABACA'); +assert.strictEqual(index, 510, `Long AJABACA, First J - at index ${index}`); +index = longBufferString.indexOf('AJABACA', 511); +assert.strictEqual(index, 1534, `Long AJABACA, Second J - at index ${index}`); + +pattern = 'JABACABADABACABA'; +index = longBufferString.indexOf(pattern); +assert.strictEqual(index, 511, `Long JABACABA..., First J - at index ${index}`); +index = longBufferString.indexOf(pattern, 512); +assert.strictEqual( + index, 1535, `Long JABACABA..., Second J - at index ${index}`); + +// Search for a non-ASCII string in a pure ASCII string. +const asciiString = Buffer.from( + 'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'); +assert.strictEqual(-1, asciiString.indexOf('\x2061')); +assert.strictEqual(asciiString.indexOf('leb', 0), 3); + +// Search in string containing many non-ASCII chars. +const allCodePoints = []; +for (let i = 0; i < 65534; i++) allCodePoints[i] = i; +const allCharsString = String.fromCharCode.apply(String, allCodePoints) + String.fromCharCode(65534, 65535); +const allCharsBufferUtf8 = Buffer.from(allCharsString); +const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2'); + +// Search for string long enough to trigger complex search with ASCII pattern +// and UC16 subject. +assert.strictEqual(-1, allCharsBufferUtf8.indexOf('notfound')); +assert.strictEqual(-1, allCharsBufferUcs2.indexOf('notfound')); + +// Needle is longer than haystack, but only because it's encoded as UTF-16 +assert.strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'ucs2'), -1); + +assert.strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'utf8'), 0); +assert.strictEqual(Buffer.from('aaaa').indexOf('你好', 'ucs2'), -1); + +// Haystack has odd length, but the needle is UCS2. +assert.strictEqual(Buffer.from('aaaaa').indexOf('b', 'ucs2'), -1); + +{ + // Find substrings in Utf8. + const lengths = [1, 3, 15]; // Single char, simple and complex. + const indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b]; + for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i]; + let length = lengths[lengthIndex]; + + if (index + length > 0x7F) { + length = 2 * length; + } + + if (index + length > 0x7FF) { + length = 3 * length; + } + + if (index + length > 0xFFFF) { + length = 4 * length; + } + + const patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length); + assert.strictEqual(index, allCharsBufferUtf8.indexOf(patternBufferUtf8)); + + const patternStringUtf8 = patternBufferUtf8.toString(); + assert.strictEqual(index, allCharsBufferUtf8.indexOf(patternStringUtf8)); + } + } +} + +{ + // Find substrings in Usc2. + const lengths = [2, 4, 16]; // Single char, simple and complex. + const indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0]; + for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i] * 2; + const length = lengths[lengthIndex]; + + const patternBufferUcs2 = allCharsBufferUcs2.slice(index, index + length); + const actualB = allCharsBufferUcs2.indexOf(patternBufferUcs2, 0, 'ucs2'); + assert.strictEqual(actualB, index); + + const patternStringUcs2 = patternBufferUcs2.toString('ucs2'); + const actualS = allCharsBufferUcs2.indexOf(patternStringUcs2, 0, 'ucs2'); + assert.strictEqual(actualS, index); + } + } +} + +[ + () => {}, + {}, + [], +].forEach((val) => { + assert.throws( + () => b.indexOf(val), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "value" argument must be of type number, string, Buffer, or Uint8Array.' + common.invalidArgTypeHelper(val) + } + ); +}); + +// Test weird offset arguments. +// The following offsets coerce to NaN or 0, searching the whole Buffer +assert.strictEqual(b.indexOf('b', undefined), 1); +assert.strictEqual(b.indexOf('b', {}), 1); +assert.strictEqual(b.indexOf('b', 0), 1); +assert.strictEqual(b.indexOf('b', null), 1); +assert.strictEqual(b.indexOf('b', []), 1); + +// The following offset coerces to 2, in other words +[2] === 2 +assert.strictEqual(b.indexOf('b', [2]), -1); + +// Behavior should match String.indexOf() +assert.strictEqual( + b.indexOf('b', undefined), + s.indexOf('b', undefined)); +assert.strictEqual( + b.indexOf('b', {}), + s.indexOf('b', {})); +assert.strictEqual( + b.indexOf('b', 0), + s.indexOf('b', 0)); +assert.strictEqual( + b.indexOf('b', null), + s.indexOf('b', null)); +assert.strictEqual( + b.indexOf('b', []), + s.indexOf('b', [])); +assert.strictEqual( + b.indexOf('b', [2]), + s.indexOf('b', [2])); + +// All code for handling encodings is shared between Buffer.indexOf and +// Buffer.lastIndexOf, so only testing the separate lastIndexOf semantics. + +// Test lastIndexOf basic functionality; Buffer b contains 'abcdef'. +// lastIndexOf string: +assert.strictEqual(b.lastIndexOf('a'), 0); +assert.strictEqual(b.lastIndexOf('a', 1), 0); +assert.strictEqual(b.lastIndexOf('b', 1), 1); +assert.strictEqual(b.lastIndexOf('c', 1), -1); +assert.strictEqual(b.lastIndexOf('a', -1), 0); +assert.strictEqual(b.lastIndexOf('a', -4), 0); +assert.strictEqual(b.lastIndexOf('a', -b.length), 0); +assert.strictEqual(b.lastIndexOf('a', -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf('a', NaN), 0); +assert.strictEqual(b.lastIndexOf('a', -Infinity), -1); +assert.strictEqual(b.lastIndexOf('a', Infinity), 0); +// lastIndexOf Buffer: +assert.strictEqual(b.lastIndexOf(buf_a), 0); +assert.strictEqual(b.lastIndexOf(buf_a, 1), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -1), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -4), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -b.length), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf(buf_a, NaN), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(buf_a, Infinity), 0); +assert.strictEqual(b.lastIndexOf(buf_bc), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, 2), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -1), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -3), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -5), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -6), -1); +assert.strictEqual(b.lastIndexOf(buf_bc, NaN), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(buf_bc, Infinity), 1); +assert.strictEqual(b.lastIndexOf(buf_f), b.length - 1); +assert.strictEqual(b.lastIndexOf(buf_z), -1); +assert.strictEqual(b.lastIndexOf(buf_empty), b.length); +assert.strictEqual(b.lastIndexOf(buf_empty, 1), 1); +assert.strictEqual(b.lastIndexOf(buf_empty, b.length + 1), b.length); +assert.strictEqual(b.lastIndexOf(buf_empty, Infinity), b.length); +// lastIndexOf number: +assert.strictEqual(b.lastIndexOf(0x61), 0); +assert.strictEqual(b.lastIndexOf(0x61, 1), 0); +assert.strictEqual(b.lastIndexOf(0x61, -1), 0); +assert.strictEqual(b.lastIndexOf(0x61, -4), 0); +assert.strictEqual(b.lastIndexOf(0x61, -b.length), 0); +assert.strictEqual(b.lastIndexOf(0x61, -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf(0x61, NaN), 0); +assert.strictEqual(b.lastIndexOf(0x61, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(0x61, Infinity), 0); +assert.strictEqual(b.lastIndexOf(0x0), -1); + +// Test weird offset arguments. +// The following offsets coerce to NaN, searching the whole Buffer +assert.strictEqual(b.lastIndexOf('b', undefined), 1); +assert.strictEqual(b.lastIndexOf('b', {}), 1); + +// The following offsets coerce to 0 +assert.strictEqual(b.lastIndexOf('b', 0), -1); +assert.strictEqual(b.lastIndexOf('b', null), -1); +assert.strictEqual(b.lastIndexOf('b', []), -1); + +// The following offset coerces to 2, in other words +[2] === 2 +assert.strictEqual(b.lastIndexOf('b', [2]), 1); + +// Behavior should match String.lastIndexOf() +assert.strictEqual( + b.lastIndexOf('b', undefined), + s.lastIndexOf('b', undefined)); +assert.strictEqual( + b.lastIndexOf('b', {}), + s.lastIndexOf('b', {})); +assert.strictEqual( + b.lastIndexOf('b', 0), + s.lastIndexOf('b', 0)); +assert.strictEqual( + b.lastIndexOf('b', null), + s.lastIndexOf('b', null)); +assert.strictEqual( + b.lastIndexOf('b', []), + s.lastIndexOf('b', [])); +assert.strictEqual( + b.lastIndexOf('b', [2]), + s.lastIndexOf('b', [2])); + +// Test needles longer than the haystack. +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'ucs2'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'utf8'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'latin1'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'binary'), -1); +assert.strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa')), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 2, 'ucs2'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 3, 'utf8'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'latin1'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'binary'), -1); +assert.strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa'), 7), -1); + +// 你好 expands to a total of 6 bytes using UTF-8 and 4 bytes using UTF-16 +assert.strictEqual(buf_bc.lastIndexOf('你好', 'ucs2'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'utf8'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'latin1'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'binary'), -1); +assert.strictEqual(buf_bc.lastIndexOf(Buffer.from('你好')), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 2, 'ucs2'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 3, 'utf8'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 5, 'latin1'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 5, 'binary'), -1); +assert.strictEqual(buf_bc.lastIndexOf(Buffer.from('你好'), 7), -1); + +// Test lastIndexOf on a longer buffer: +const bufferString = Buffer.from('a man a plan a canal panama'); +assert.strictEqual(bufferString.lastIndexOf('canal'), 15); +assert.strictEqual(bufferString.lastIndexOf('panama'), 21); +assert.strictEqual(bufferString.lastIndexOf('a man a plan a canal panama'), 0); +assert.strictEqual(-1, bufferString.lastIndexOf('a man a plan a canal mexico')); +assert.strictEqual(-1, bufferString + .lastIndexOf('a man a plan a canal mexico city')); +assert.strictEqual(-1, bufferString.lastIndexOf(Buffer.from('a'.repeat(1000)))); +assert.strictEqual(bufferString.lastIndexOf('a man a plan', 4), 0); +assert.strictEqual(bufferString.lastIndexOf('a '), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', 13), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', 12), 6); +assert.strictEqual(bufferString.lastIndexOf('a ', 5), 0); +assert.strictEqual(bufferString.lastIndexOf('a ', -1), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', -27), 0); +assert.strictEqual(-1, bufferString.lastIndexOf('a ', -28)); + +// Test lastIndexOf for the case that the first character can be found, +// but in a part of the buffer that does not make search to search +// due do length constraints. +const abInUCS2 = Buffer.from('ab', 'ucs2'); +assert.strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'latin1').lastIndexOf('µ')); +assert.strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'binary').lastIndexOf('µ')); +assert.strictEqual(-1, Buffer.from('bc').lastIndexOf('ab')); +assert.strictEqual(-1, Buffer.from('abc').lastIndexOf('qa')); +assert.strictEqual(-1, Buffer.from('abcdef').lastIndexOf('qabc')); +assert.strictEqual(-1, Buffer.from('bc').lastIndexOf(Buffer.from('ab'))); +assert.strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf('ab', 'ucs2')); +assert.strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf(abInUCS2)); + +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab'), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 1), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 2), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 3), 0); + +// The above tests test the LINEAR and SINGLE-CHAR strategies. +// Now, we test the BOYER-MOORE-HORSPOOL strategy. +// Test lastIndexOf on a long buffer w multiple matches: +pattern = 'JABACABADABACABA'; +assert.strictEqual(longBufferString.lastIndexOf(pattern), 1535); +assert.strictEqual(longBufferString.lastIndexOf(pattern, 1535), 1535); +assert.strictEqual(longBufferString.lastIndexOf(pattern, 1534), 511); + +// Finally, give it a really long input to trigger fallback from BMH to +// regular BOYER-MOORE (which has better worst-case complexity). + +// Generate a really long Thue-Morse sequence of 'yolo' and 'swag', +// "yolo swag swag yolo swag yolo yolo swag" ..., goes on for about 5MB. +// This is hard to search because it all looks similar, but never repeats. + +// countBits returns the number of bits in the binary representation of n. +function countBits(n) { + let count; + for (count = 0; n > 0; count++) { + n = n & (n - 1); // remove top bit + } + return count; +} +const parts = []; +for (let i = 0; i < 1000000; i++) { + parts.push((countBits(i) % 2 === 0) ? 'yolo' : 'swag'); +} +const reallyLong = Buffer.from(parts.join(' ')); +assert.strictEqual(reallyLong.slice(0, 19).toString(), 'yolo swag swag yolo'); + +// Expensive reverse searches. Stress test lastIndexOf: +pattern = reallyLong.slice(0, 100000); // First 1/50th of the pattern. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 4751360); +assert.strictEqual(reallyLong.lastIndexOf(pattern, 4000000), 3932160); +assert.strictEqual(reallyLong.lastIndexOf(pattern, 3000000), 2949120); +pattern = reallyLong.slice(100000, 200000); // Second 1/50th. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 4728480); +pattern = reallyLong.slice(0, 1000000); // First 1/5th. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 3932160); +pattern = reallyLong.slice(0, 2000000); // first 2/5ths. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 0); + +// Test truncation of Number arguments to uint8 +{ + const buf = Buffer.from('this is a test'); + assert.strictEqual(buf.indexOf(0x6973), 3); + assert.strictEqual(buf.indexOf(0x697320), 4); + assert.strictEqual(buf.indexOf(0x69732069), 2); + assert.strictEqual(buf.indexOf(0x697374657374), 0); + assert.strictEqual(buf.indexOf(0x69737374), 0); + assert.strictEqual(buf.indexOf(0x69737465), 11); + assert.strictEqual(buf.indexOf(0x69737465), 11); + assert.strictEqual(buf.indexOf(-140), 0); + assert.strictEqual(buf.indexOf(-152), 1); + assert.strictEqual(buf.indexOf(0xff), -1); + assert.strictEqual(buf.indexOf(0xffff), -1); +} + +// Test that Uint8Array arguments are okay. +{ + const needle = new Uint8Array([ 0x66, 0x6f, 0x6f ]); + const haystack = Buffer.from('a foo b foo'); + assert.strictEqual(haystack.indexOf(needle), 2); + assert.strictEqual(haystack.lastIndexOf(needle), haystack.length - 3); +} + +// Avoid abort because of invalid usage +// see https://github.com/nodejs/node/issues/32753 +// { +// assert.throws(() => { +// const buffer = require('buffer'); +// new buffer.Buffer.prototype.lastIndexOf(1, 'str'); +// }, { +// code: 'ERR_INVALID_ARG_TYPE', +// name: 'TypeError', +// message: 'The "buffer" argument must be an instance of Buffer, ' + +// 'TypedArray, or DataView. ' + +// 'Received an instance of lastIndexOf' +// }); +// } diff --git a/test/js/node/test/parallel/test-buffer-new.js b/test/js/node/test/parallel/test-buffer-new.js new file mode 100644 index 00000000000000..7c840e3de7a6ea --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-new.js @@ -0,0 +1,10 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.throws(() => new Buffer(42, 'utf8'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string. Received type number (42)' +}); diff --git a/test/js/node/test/parallel/test-buffer-read.js b/test/js/node/test/parallel/test-buffer-read.js new file mode 100644 index 00000000000000..212a05a6afbede --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-read.js @@ -0,0 +1,106 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// Testing basic buffer read functions +const buf = Buffer.from([0xa4, 0xfd, 0x48, 0xea, 0xcf, 0xff, 0xd9, 0x01, 0xde]); + +function read(buff, funx, args, expected) { + assert.strictEqual(buff[funx](...args), expected); + assert.throws( + () => buff[funx](-1, args[1]), + { name: 'RangeError', code: 'ERR_OUT_OF_RANGE' } + ); +} + +// Testing basic functionality of readDoubleBE() and readDoubleLE() +read(buf, 'readDoubleBE', [1], -3.1827727774563287e+295); +read(buf, 'readDoubleLE', [1], -6.966010051009108e+144); + +// Testing basic functionality of readFloatBE() and readFloatLE() +read(buf, 'readFloatBE', [1], -1.6691549692541768e+37); +read(buf, 'readFloatLE', [1], -7861303808); + +// Testing basic functionality of readInt8() +read(buf, 'readInt8', [1], -3); + +// Testing basic functionality of readInt16BE() and readInt16LE() +read(buf, 'readInt16BE', [1], -696); +read(buf, 'readInt16LE', [1], 0x48fd); + +// Testing basic functionality of readInt32BE() and readInt32LE() +read(buf, 'readInt32BE', [1], -45552945); +read(buf, 'readInt32LE', [1], -806729475); + +// Testing basic functionality of readIntBE() and readIntLE() +read(buf, 'readIntBE', [1, 1], -3); +read(buf, 'readIntLE', [2, 1], 0x48); + +// Testing basic functionality of readUInt8() +read(buf, 'readUInt8', [1], 0xfd); + +// Testing basic functionality of readUInt16BE() and readUInt16LE() +read(buf, 'readUInt16BE', [2], 0x48ea); +read(buf, 'readUInt16LE', [2], 0xea48); + +// Testing basic functionality of readUInt32BE() and readUInt32LE() +read(buf, 'readUInt32BE', [1], 0xfd48eacf); +read(buf, 'readUInt32LE', [1], 0xcfea48fd); + +// Testing basic functionality of readUIntBE() and readUIntLE() +read(buf, 'readUIntBE', [2, 2], 0x48ea); +read(buf, 'readUIntLE', [2, 2], 0xea48); + +// Error name and message +const OOR_ERROR = +{ + name: 'RangeError' +}; + +const OOB_ERROR = +{ + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' +}; + +// Attempt to overflow buffers, similar to previous bug in array buffers +assert.throws( + () => Buffer.allocUnsafe(8).readFloatBE(0xffffffff), OOR_ERROR); + +assert.throws( + () => Buffer.allocUnsafe(8).readFloatLE(0xffffffff), OOR_ERROR); + +// Ensure negative values can't get past offset +assert.throws( + () => Buffer.allocUnsafe(8).readFloatBE(-1), OOR_ERROR); +assert.throws( + () => Buffer.allocUnsafe(8).readFloatLE(-1), OOR_ERROR); + +// Offset checks +{ + const buf = Buffer.allocUnsafe(0); + + assert.throws( + () => buf.readUInt8(0), OOB_ERROR); + assert.throws( + () => buf.readInt8(0), OOB_ERROR); +} + +[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) => { + assert.throws( + () => buf[`read${fn}E`](0), OOB_ERROR); + }); +}); + +[16, 32].forEach((bits) => { + const buf = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + ['LE', 'BE'].forEach((endian) => { + assert.strictEqual(buf[`readUInt${bits}${endian}`](0), + (0xFFFFFFFF >>> (32 - bits))); + + assert.strictEqual(buf[`readInt${bits}${endian}`](0), + (0xFFFFFFFF >> (32 - bits))); + }); +}); diff --git a/test/js/node/test/parallel/test-buffer-readdouble.js b/test/js/node/test/parallel/test-buffer-readdouble.js new file mode 100644 index 00000000000000..9ddaec30cb1a98 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-readdouble.js @@ -0,0 +1,148 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Test (64 bit) double +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; +assert.strictEqual(buffer.readDoubleBE(0), 1.1945305291680097e+103); +assert.strictEqual(buffer.readDoubleLE(0), 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; +assert.strictEqual(buffer.readDoubleBE(0), 7.291122019655968e-304); +assert.strictEqual(buffer.readDoubleLE(0), 1.0000000000000002); + +buffer[0] = 2; +assert.strictEqual(buffer.readDoubleBE(0), 4.778309726801735e-299); +assert.strictEqual(buffer.readDoubleLE(0), 1.0000000000000004); + +buffer[0] = 1; +buffer[6] = 0; +buffer[7] = 0; +// eslint-disable-next-line no-loss-of-precision +assert.strictEqual(buffer.readDoubleBE(0), 7.291122019556398e-304); +assert.strictEqual(buffer.readDoubleLE(0), 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; +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.strictEqual(buffer.readDoubleLE(0), 2.225073858507201e-308); + +buffer[6] = 0xef; +buffer[7] = 0x7f; +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.strictEqual(buffer.readDoubleLE(0), 1.7976931348623157e+308); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0; +buffer[4] = 0; +buffer[5] = 0; +buffer[6] = 0xf0; +buffer[7] = 0x3f; +assert.strictEqual(buffer.readDoubleBE(0), 3.03865e-319); +assert.strictEqual(buffer.readDoubleLE(0), 1); + +buffer[6] = 0; +buffer[7] = 0x40; +assert.strictEqual(buffer.readDoubleBE(0), 3.16e-322); +assert.strictEqual(buffer.readDoubleLE(0), 2); + +buffer[7] = 0xc0; +assert.strictEqual(buffer.readDoubleBE(0), 9.5e-322); +assert.strictEqual(buffer.readDoubleLE(0), -2); + +buffer[6] = 0x10; +buffer[7] = 0; +assert.strictEqual(buffer.readDoubleBE(0), 2.0237e-320); +assert.strictEqual(buffer.readDoubleLE(0), 2.2250738585072014e-308); + +buffer[6] = 0; +assert.strictEqual(buffer.readDoubleBE(0), 0); +assert.strictEqual(buffer.readDoubleLE(0), 0); +assert.ok(1 / buffer.readDoubleLE(0) >= 0); + +buffer[7] = 0x80; +assert.strictEqual(buffer.readDoubleBE(0), 6.3e-322); +assert.strictEqual(buffer.readDoubleLE(0), -0); +assert.ok(1 / buffer.readDoubleLE(0) < 0); + +buffer[6] = 0xf0; +buffer[7] = 0x7f; +assert.strictEqual(buffer.readDoubleBE(0), 3.0418e-319); +assert.strictEqual(buffer.readDoubleLE(0), Infinity); + +buffer[7] = 0xff; +assert.strictEqual(buffer.readDoubleBE(0), 3.04814e-319); +assert.strictEqual(buffer.readDoubleLE(0), -Infinity); + +['readDoubleLE', 'readDoubleBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](undefined); + buffer[fn](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](off), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(off)}`, + } + ); + }); + + [-1, 1].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 0. Received ${offset}` + }); + }); + + assert.throws( + () => Buffer.alloc(1)[fn](1), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + [Infinity, NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); +}); diff --git a/test/js/node/test/parallel/test-buffer-readfloat.js b/test/js/node/test/parallel/test-buffer-readfloat.js new file mode 100644 index 00000000000000..a78a912185eb35 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-readfloat.js @@ -0,0 +1,110 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Test 32 bit float +const buffer = Buffer.alloc(4); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0x3f; +assert.strictEqual(buffer.readFloatBE(0), 4.600602988224807e-41); +assert.strictEqual(buffer.readFloatLE(0), 1); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0xc0; +assert.strictEqual(buffer.readFloatBE(0), 2.6904930515036488e-43); +assert.strictEqual(buffer.readFloatLE(0), -2); + +buffer[0] = 0xff; +buffer[1] = 0xff; +buffer[2] = 0x7f; +buffer[3] = 0x7f; +assert.ok(Number.isNaN(buffer.readFloatBE(0))); +assert.strictEqual(buffer.readFloatLE(0), 3.4028234663852886e+38); + +buffer[0] = 0xab; +buffer[1] = 0xaa; +buffer[2] = 0xaa; +buffer[3] = 0x3e; +assert.strictEqual(buffer.readFloatBE(0), -1.2126478207002966e-12); +assert.strictEqual(buffer.readFloatLE(0), 0.3333333432674408); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0; +assert.strictEqual(buffer.readFloatBE(0), 0); +assert.strictEqual(buffer.readFloatLE(0), 0); +assert.ok(1 / buffer.readFloatLE(0) >= 0); + +buffer[3] = 0x80; +assert.strictEqual(buffer.readFloatBE(0), 1.793662034335766e-43); +assert.strictEqual(buffer.readFloatLE(0), -0); +assert.ok(1 / buffer.readFloatLE(0) < 0); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0x7f; +assert.strictEqual(buffer.readFloatBE(0), 4.609571298396486e-41); +assert.strictEqual(buffer.readFloatLE(0), Infinity); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0xff; +assert.strictEqual(buffer.readFloatBE(0), 4.627507918739843e-41); +assert.strictEqual(buffer.readFloatLE(0), -Infinity); + +['readFloatLE', 'readFloatBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](undefined); + buffer[fn](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](off), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(off)}`, + } + ); + }); + + [-1, 1].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 0. Received ${offset}` + }); + }); + + assert.throws( + () => Buffer.alloc(1)[fn](1), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + [Infinity, NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); +}); diff --git a/test/js/node/test/parallel/test-buffer-readint.js b/test/js/node/test/parallel/test-buffer-readint.js new file mode 100644 index 00000000000000..89d9a9456a9703 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-readint.js @@ -0,0 +1,198 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Test OOB +{ + const buffer = Buffer.alloc(4); + + ['Int8', 'Int16BE', 'Int16LE', 'Int32BE', 'Int32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[`read${fn}`](undefined); + buffer[`read${fn}`](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => buffer[`read${fn}`](o), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(o)}`, + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} + +// Test 8 bit signed integers +{ + const data = Buffer.from([0x23, 0xab, 0x7c, 0xef]); + + assert.strictEqual(data.readInt8(0), 0x23); + + data[0] = 0xff; + assert.strictEqual(data.readInt8(0), -1); + + data[0] = 0x87; + assert.strictEqual(data.readInt8(0), -121); + assert.strictEqual(data.readInt8(1), -85); + assert.strictEqual(data.readInt8(2), 124); + assert.strictEqual(data.readInt8(3), -17); +} + +// Test 16 bit integers +{ + const buffer = Buffer.from([0x16, 0x79, 0x65, 0x6e, 0x69, 0x78]); + + assert.strictEqual(buffer.readInt16BE(0), 0x1679); + assert.strictEqual(buffer.readInt16LE(0), 0x7916); + + buffer[0] = 0xff; + buffer[1] = 0x80; + assert.strictEqual(buffer.readInt16BE(0), -128); + assert.strictEqual(buffer.readInt16LE(0), -32513); + + buffer[0] = 0x77; + buffer[1] = 0x65; + assert.strictEqual(buffer.readInt16BE(0), 0x7765); + assert.strictEqual(buffer.readInt16BE(1), 0x6565); + assert.strictEqual(buffer.readInt16BE(2), 0x656e); + assert.strictEqual(buffer.readInt16BE(3), 0x6e69); + assert.strictEqual(buffer.readInt16BE(4), 0x6978); + assert.strictEqual(buffer.readInt16LE(0), 0x6577); + assert.strictEqual(buffer.readInt16LE(1), 0x6565); + assert.strictEqual(buffer.readInt16LE(2), 0x6e65); + assert.strictEqual(buffer.readInt16LE(3), 0x696e); + assert.strictEqual(buffer.readInt16LE(4), 0x7869); +} + +// Test 32 bit integers +{ + const buffer = Buffer.from([0x43, 0x53, 0x16, 0x79, 0x36, 0x17]); + + assert.strictEqual(buffer.readInt32BE(0), 0x43531679); + assert.strictEqual(buffer.readInt32LE(0), 0x79165343); + + buffer[0] = 0xff; + buffer[1] = 0xfe; + buffer[2] = 0xef; + buffer[3] = 0xfa; + assert.strictEqual(buffer.readInt32BE(0), -69638); + assert.strictEqual(buffer.readInt32LE(0), -84934913); + + buffer[0] = 0x42; + buffer[1] = 0xc3; + buffer[2] = 0x95; + buffer[3] = 0xa9; + assert.strictEqual(buffer.readInt32BE(0), 0x42c395a9); + assert.strictEqual(buffer.readInt32BE(1), -1013601994); + assert.strictEqual(buffer.readInt32BE(2), -1784072681); + assert.strictEqual(buffer.readInt32LE(0), -1449802942); + assert.strictEqual(buffer.readInt32LE(1), 917083587); + assert.strictEqual(buffer.readInt32LE(2), 389458325); +} + +// Test Int +{ + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + assert.strictEqual(buffer.readIntLE(0, 1), 0x01); + assert.strictEqual(buffer.readIntBE(0, 1), 0x01); + assert.strictEqual(buffer.readIntLE(0, 3), 0x030201); + assert.strictEqual(buffer.readIntBE(0, 3), 0x010203); + assert.strictEqual(buffer.readIntLE(0, 5), 0x0504030201); + assert.strictEqual(buffer.readIntBE(0, 5), 0x0102030405); + assert.strictEqual(buffer.readIntLE(0, 6), 0x060504030201); + assert.strictEqual(buffer.readIntBE(0, 6), 0x010203040506); + assert.strictEqual(buffer.readIntLE(1, 6), 0x070605040302); + assert.strictEqual(buffer.readIntBE(1, 6), 0x020304050607); + assert.strictEqual(buffer.readIntLE(2, 6), 0x080706050403); + assert.strictEqual(buffer.readIntBE(2, 6), 0x030405060708); + + // Check byteLength. + ['readIntBE', 'readIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((len) => { + assert.throws( + () => buffer[fn](0, len), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + }); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['readIntBE', 'readIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => buffer[fn](o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [-1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [Infinity, NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/test/js/node/test/parallel/test-buffer-readuint.js b/test/js/node/test/parallel/test-buffer-readuint.js new file mode 100644 index 00000000000000..3c0e1f549413c7 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-readuint.js @@ -0,0 +1,168 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const common = require('../common'); + +// Test OOB +{ + const buffer = Buffer.alloc(4); + + ['UInt8', 'UInt16BE', 'UInt16LE', 'UInt32BE', 'UInt32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[`read${fn}`](undefined); + buffer[`read${fn}`](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => buffer[`read${fn}`](o), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(o)}`, + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} + +// Test 8 bit unsigned integers +{ + const data = Buffer.from([0xff, 0x2a, 0x2a, 0x2a]); + assert.strictEqual(data.readUInt8(0), 255); + assert.strictEqual(data.readUInt8(1), 42); + assert.strictEqual(data.readUInt8(2), 42); + assert.strictEqual(data.readUInt8(3), 42); +} + +// Test 16 bit unsigned integers +{ + const data = Buffer.from([0x00, 0x2a, 0x42, 0x3f]); + assert.strictEqual(data.readUInt16BE(0), 0x2a); + assert.strictEqual(data.readUInt16BE(1), 0x2a42); + assert.strictEqual(data.readUInt16BE(2), 0x423f); + assert.strictEqual(data.readUInt16LE(0), 0x2a00); + assert.strictEqual(data.readUInt16LE(1), 0x422a); + assert.strictEqual(data.readUInt16LE(2), 0x3f42); + + data[0] = 0xfe; + data[1] = 0xfe; + assert.strictEqual(data.readUInt16BE(0), 0xfefe); + assert.strictEqual(data.readUInt16LE(0), 0xfefe); +} + +// Test 32 bit unsigned integers +{ + const data = Buffer.from([0x32, 0x65, 0x42, 0x56, 0x23, 0xff]); + assert.strictEqual(data.readUInt32BE(0), 0x32654256); + assert.strictEqual(data.readUInt32BE(1), 0x65425623); + assert.strictEqual(data.readUInt32BE(2), 0x425623ff); + assert.strictEqual(data.readUInt32LE(0), 0x56426532); + assert.strictEqual(data.readUInt32LE(1), 0x23564265); + assert.strictEqual(data.readUInt32LE(2), 0xff235642); +} + +// Test UInt +{ + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + assert.strictEqual(buffer.readUIntLE(0, 1), 0x01); + assert.strictEqual(buffer.readUIntBE(0, 1), 0x01); + assert.strictEqual(buffer.readUIntLE(0, 3), 0x030201); + assert.strictEqual(buffer.readUIntBE(0, 3), 0x010203); + assert.strictEqual(buffer.readUIntLE(0, 5), 0x0504030201); + assert.strictEqual(buffer.readUIntBE(0, 5), 0x0102030405); + assert.strictEqual(buffer.readUIntLE(0, 6), 0x060504030201); + assert.strictEqual(buffer.readUIntBE(0, 6), 0x010203040506); + assert.strictEqual(buffer.readUIntLE(1, 6), 0x070605040302); + assert.strictEqual(buffer.readUIntBE(1, 6), 0x020304050607); + assert.strictEqual(buffer.readUIntLE(2, 6), 0x080706050403); + assert.strictEqual(buffer.readUIntBE(2, 6), 0x030405060708); + + // Check byteLength. + ['readUIntBE', 'readUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((len) => { + assert.throws( + () => buffer[fn](0, len), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + }); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['readUIntBE', 'readUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => buffer[fn](o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(o)}`, + }); + }); + + [-1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [Infinity, NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js b/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js new file mode 100644 index 00000000000000..79678f268572a2 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const sab = new SharedArrayBuffer(24); +const arr1 = new Uint16Array(sab); +const arr2 = new Uint16Array(12); +arr2[0] = 5000; +arr1[0] = 5000; +arr1[1] = 4000; +arr2[1] = 4000; + +const arr_buf = Buffer.from(arr1.buffer); +const ar_buf = Buffer.from(arr2.buffer); + +assert.deepStrictEqual(arr_buf, ar_buf); + +arr1[1] = 6000; +arr2[1] = 6000; + +assert.deepStrictEqual(arr_buf, ar_buf); + +// Checks for calling Buffer.byteLength on a SharedArrayBuffer. +assert.strictEqual(Buffer.byteLength(sab), sab.byteLength); + +Buffer.from({ buffer: sab }); // Should not throw. diff --git a/test/js/node/test/parallel/test-buffer-write.js b/test/js/node/test/parallel/test-buffer-write.js new file mode 100644 index 00000000000000..c834b6ee8ff71a --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-write.js @@ -0,0 +1,108 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +[-1, 10].forEach((offset) => { + assert.throws( + () => Buffer.alloc(9).write('foo', offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 9. Received ${offset}` + } + ); +}); + +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) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('foo', encoding); + assert.strictEqual(buf.write('foo', 0, len, encoding), len); + + if (encoding.includes('-')) + encoding = encoding.replace('-', ''); + + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); + }); + +// base64 +['base64', 'BASE64', 'base64url', 'BASE64URL'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('Zm9v', encoding); + + assert.strictEqual(buf.write('Zm9v', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// hex +['hex', 'HEX'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('666f6f', encoding); + + assert.strictEqual(buf.write('666f6f', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// Invalid encodings +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + const error = common.expectsError({ + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: `Unknown encoding: ${encoding}` + }); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.throws(() => Buffer.alloc(9).write('foo', encoding), error); +} + +// UCS-2 overflow CVE-2018-12115 +for (let i = 1; i < 4; 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 + assert.strictEqual(x.write('ыыыыыы', 3, 'ucs2'), 0); + // CVE-2018-12115 experienced via buffer overrun to next block in the pool + assert.strictEqual(Buffer.compare(y, Buffer.alloc(4, 1)), 0); +} + +// Should not write any data when there is no space for 16-bit chars +const z = Buffer.alloc(4, 0); +assert.strictEqual(z.write('\u0001', 3, 'ucs2'), 0); +assert.strictEqual(Buffer.compare(z, Buffer.alloc(4, 0)), 0); +// Make sure longer strings are written up to the buffer end. +assert.strictEqual(z.write('abcd', 2), 2); +assert.deepStrictEqual([...z], [0, 0, 0x61, 0x62]); + +// Large overrun could corrupt the process +assert.strictEqual(Buffer.alloc(4) + .write('ыыыыыы'.repeat(100), 3, 'utf16le'), 0); + +{ + // .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); + assert.strictEqual(buf.write('ыы', 1, 'utf16le'), 4); + assert.deepStrictEqual([...buf], [0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); +} diff --git a/test/js/node/test/parallel/test-buffer-writedouble.js b/test/js/node/test/parallel/test-buffer-writedouble.js new file mode 100644 index 00000000000000..5026c8187430b3 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-writedouble.js @@ -0,0 +1,133 @@ +'use strict'; + +// Tests to verify doubles are correctly written + +require('../common'); +const assert = require('assert'); + +const buffer = Buffer.allocUnsafe(16); + +buffer.writeDoubleBE(2.225073858507201e-308, 0); +buffer.writeDoubleLE(2.225073858507201e-308, 8); +assert.ok(buffer.equals(new Uint8Array([ + 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); +assert.ok(buffer.equals(new Uint8Array([ + 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, +]))); + +buffer.writeDoubleBE(-2, 0); +buffer.writeDoubleLE(-2, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, +]))); + +buffer.writeDoubleBE(1.7976931348623157e+308, 0); +buffer.writeDoubleLE(1.7976931348623157e+308, 8); +assert.ok(buffer.equals(new Uint8Array([ + 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); +assert.ok(buffer.equals(new Uint8Array([ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +]))); + +buffer.writeDoubleBE(Infinity, 0); +buffer.writeDoubleLE(Infinity, 8); + +assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F, +]))); + +assert.strictEqual(buffer.readDoubleBE(0), Infinity); +assert.strictEqual(buffer.readDoubleLE(8), Infinity); + +buffer.writeDoubleBE(-Infinity, 0); +buffer.writeDoubleLE(-Infinity, 8); + +assert.ok(buffer.equals(new Uint8Array([ + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, +]))); + +assert.strictEqual(buffer.readDoubleBE(0), -Infinity); +assert.strictEqual(buffer.readDoubleLE(8), -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) { + assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7F, + ]))); +} else { + assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, + ]))); +} + +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.ok(Number.isNaN(buffer.readDoubleLE(8))); + +// OOB in writeDouble{LE,BE} should throw. +{ + const small = Buffer.allocUnsafe(1); + + ['writeDoubleLE', 'writeDoubleBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws( + () => small[fn](11.11, 0), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => small[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1, 9].forEach((offset) => { + assert.throws( + () => buffer[fn](23, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 8. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](42, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} diff --git a/test/js/node/test/parallel/test-buffer-writefloat.js b/test/js/node/test/parallel/test-buffer-writefloat.js new file mode 100644 index 00000000000000..8676a819fc776d --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-writefloat.js @@ -0,0 +1,117 @@ +'use strict'; + +// Tests to verify floats are correctly written + +require('../common'); +const assert = require('assert'); + +const buffer = Buffer.allocUnsafe(8); + +buffer.writeFloatBE(1, 0); +buffer.writeFloatLE(1, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f ]))); + +buffer.writeFloatBE(1 / 3, 0); +buffer.writeFloatLE(1 / 3, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x3e, 0xaa, 0xaa, 0xab, 0xab, 0xaa, 0xaa, 0x3e ]))); + +buffer.writeFloatBE(3.4028234663852886e+38, 0); +buffer.writeFloatLE(3.4028234663852886e+38, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x7f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f ]))); + +buffer.writeFloatLE(1.1754943508222875e-38, 0); +buffer.writeFloatBE(1.1754943508222875e-38, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00 ]))); + +buffer.writeFloatBE(0 * -1, 0); +buffer.writeFloatLE(0 * -1, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 ]))); + +buffer.writeFloatBE(Infinity, 0); +buffer.writeFloatLE(Infinity, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F ]))); + +assert.strictEqual(buffer.readFloatBE(0), Infinity); +assert.strictEqual(buffer.readFloatLE(4), Infinity); + +buffer.writeFloatBE(-Infinity, 0); +buffer.writeFloatLE(-Infinity, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF ]))); + +assert.strictEqual(buffer.readFloatBE(0), -Infinity); +assert.strictEqual(buffer.readFloatLE(4), -Infinity); + +buffer.writeFloatBE(NaN, 0); +buffer.writeFloatLE(NaN, 4); + +// JS only knows a single NaN but there exist two platform specific +// implementations. Therefore, allow both quiet and signalling NaNs. +if (buffer[1] === 0xBF) { + assert.ok( + buffer.equals(new Uint8Array( + [ 0x7F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x7F ]))); +} else { + assert.ok( + buffer.equals(new Uint8Array( + [ 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x7F ]))); +} + +assert.ok(Number.isNaN(buffer.readFloatBE(0))); +assert.ok(Number.isNaN(buffer.readFloatLE(4))); + +// OOB in writeFloat{LE,BE} should throw. +{ + const small = Buffer.allocUnsafe(1); + + ['writeFloatLE', 'writeFloatBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws( + () => small[fn](11.11, 0), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => small[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }); + + [Infinity, -1, 5].forEach((offset) => { + assert.throws( + () => buffer[fn](23, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 4. Received ${offset}` + } + ); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](42, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} diff --git a/test/js/node/test/parallel/test-buffer-writeint.js b/test/js/node/test/parallel/test-buffer-writeint.js new file mode 100644 index 00000000000000..5fb0d78268d51b --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-writeint.js @@ -0,0 +1,268 @@ +'use strict'; + +// Tests to verify signed integers are correctly written + +require('../common'); +const assert = require('assert'); +const errorOutOfBounds = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: new RegExp('^The value of "value" is out of range\\. ' + + 'It must be >= -\\d+ and <= \\d+\\. Received .+$') +}; + +// Test 8 bit +{ + const buffer = Buffer.alloc(2); + + buffer.writeInt8(0x23, 0); + buffer.writeInt8(-5, 1); + assert.ok(buffer.equals(new Uint8Array([ 0x23, 0xfb ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt8(0x7f, 0); + buffer.writeInt8(-0x80, 1); + assert.ok(buffer.equals(new Uint8Array([ 0x7f, 0x80 ]))); + + assert.throws(() => { + buffer.writeInt8(0x7f + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer.writeInt8(-0x80 - 1, 0); + }, errorOutOfBounds); + + // Verify that default offset works fine. + buffer.writeInt8(23, undefined); + buffer.writeInt8(23); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer.writeInt8(23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer.writeInt8(23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); +} + +// Test 16 bit +{ + const buffer = Buffer.alloc(4); + + buffer.writeInt16BE(0x0023, 0); + buffer.writeInt16LE(0x0023, 2); + assert.ok(buffer.equals(new Uint8Array([ 0x00, 0x23, 0x23, 0x00 ]))); + + buffer.writeInt16BE(-5, 0); + buffer.writeInt16LE(-5, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xff, 0xfb, 0xfb, 0xff ]))); + + buffer.writeInt16BE(-1679, 0); + buffer.writeInt16LE(-1679, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xf9, 0x71, 0x71, 0xf9 ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt16BE(0x7fff, 0); + buffer.writeInt16BE(-0x8000, 2); + assert.ok(buffer.equals(new Uint8Array([ 0x7f, 0xff, 0x80, 0x00 ]))); + + buffer.writeInt16LE(0x7fff, 0); + buffer.writeInt16LE(-0x8000, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xff, 0x7f, 0x00, 0x80 ]))); + + ['writeInt16BE', 'writeInt16LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws(() => { + buffer[fn](0x7fff + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer[fn](-0x8000 - 1, 0); + }, errorOutOfBounds); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); + }); +} + +// Test 32 bit +{ + const buffer = Buffer.alloc(8); + + buffer.writeInt32BE(0x23, 0); + buffer.writeInt32LE(0x23, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0x00, 0x00, 0x00, 0x23, 0x23, 0x00, 0x00, 0x00, + ]))); + + buffer.writeInt32BE(-5, 0); + buffer.writeInt32LE(-5, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xff, 0xff, 0xff, + ]))); + + buffer.writeInt32BE(-805306713, 0); + buffer.writeInt32LE(-805306713, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xcf, 0xff, 0xfe, 0xa7, 0xa7, 0xfe, 0xff, 0xcf, + ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt32BE(0x7fffffff, 0); + buffer.writeInt32BE(-0x80000000, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, + ]))); + + buffer.writeInt32LE(0x7fffffff, 0); + buffer.writeInt32LE(-0x80000000, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x80, + ]))); + + ['writeInt32BE', 'writeInt32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws(() => { + buffer[fn](0x7fffffff + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer[fn](-0x80000000 - 1, 0); + }, errorOutOfBounds); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); + }); +} + +// Test 48 bit +{ + const value = 0x1234567890ab; + const buffer = Buffer.allocUnsafe(6); + buffer.writeIntBE(value, 0, 6); + assert.ok(buffer.equals(new Uint8Array([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, + ]))); + + buffer.writeIntLE(value, 0, 6); + assert.ok(buffer.equals(new Uint8Array([ + 0xab, 0x90, 0x78, 0x56, 0x34, 0x12, + ]))); +} + +// Test Int +{ + const data = Buffer.alloc(8); + + // Check byteLength. + ['writeIntBE', 'writeIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((bl) => { + assert.throws( + () => data[fn](23, 0, bl), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => data[fn](23, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + } + ); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => data[fn](42, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['writeIntBE', 'writeIntLE'].forEach((fn) => { + const min = -(2 ** (i * 8 - 1)); + const max = 2 ** (i * 8 - 1) - 1; + let range = `>= ${min} and <= ${max}`; + if (i > 4) { + range = `>= -(2 ** ${i * 8 - 1}) and < 2 ** ${i * 8 - 1}`; + } + [min - 1, max + 1].forEach((val) => { + const received = String(val); + assert.throws(() => { + data[fn](val, 0, i); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "value" is out of range. ' + + `It must be ${range}. Received ${received}` + }); + }); + + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => data[fn](min, o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => data[fn](min, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => data[fn](max, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/test/js/node/test/parallel/test-buffer-writeuint.js b/test/js/node/test/parallel/test-buffer-writeuint.js new file mode 100644 index 00000000000000..6416973d3b554a --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-writeuint.js @@ -0,0 +1,234 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const common = require('../common'); + +// 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 + +{ // 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) => { + assert.throws( + () => data[`write${fn}`](23, o), + { + code: 'ERR_INVALID_ARG_TYPE', + message: `The "offset" argument must be of type number.${common.invalidArgTypeHelper(o)}`, + }); + }); + + [NaN, Infinity, -1, 1.01].forEach((o) => { + assert.throws( + () => data[`write${fn}`](23, o), + { 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); + assert.ok(data.equals(new Uint8Array([23, 23, 23, 23]))); + + data.writeUInt8(23, 0); + data.writeUInt8(23, 1); + data.writeUInt8(23, 2); + data.writeUInt8(23, 3); + assert.ok(data.equals(new Uint8Array([23, 23, 23, 23]))); + + data.writeUInt8(255, 0); + assert.strictEqual(data[0], 255); + + data.writeUInt8(255, 0); + assert.strictEqual(data[0], 255); +} + +// Test 16 bit +{ + let value = 0x2343; + const data = Buffer.alloc(4); + + data.writeUInt16BE(value, 0); + assert.ok(data.equals(new Uint8Array([0x23, 0x43, 0, 0]))); + + data.writeUInt16BE(value, 1); + assert.ok(data.equals(new Uint8Array([0x23, 0x23, 0x43, 0]))); + + data.writeUInt16BE(value, 2); + assert.ok(data.equals(new Uint8Array([0x23, 0x23, 0x23, 0x43]))); + + data.writeUInt16LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x43, 0x23, 0x23, 0x43]))); + + data.writeUInt16LE(value, 1); + assert.ok(data.equals(new Uint8Array([0x43, 0x43, 0x23, 0x43]))); + + data.writeUInt16LE(value, 2); + assert.ok(data.equals(new Uint8Array([0x43, 0x43, 0x43, 0x23]))); + + value = 0xff80; + data.writeUInt16LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x80, 0xff, 0x43, 0x23]))); + + data.writeUInt16BE(value, 0); + assert.ok(data.equals(new Uint8Array([0xff, 0x80, 0x43, 0x23]))); + + value = 0xfffff; + ['writeUInt16BE', 'writeUInt16LE'].forEach((fn) => { + assert.throws( + () => data[fn](value, 0), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "value" is out of range. ' + + `It must be >= 0 and <= 65535. Received ${value}` + } + ); + }); +} + +// Test 32 bit +{ + const data = Buffer.alloc(6); + const value = 0xe7f90a6d; + + data.writeUInt32BE(value, 0); + assert.ok(data.equals(new Uint8Array([0xe7, 0xf9, 0x0a, 0x6d, 0, 0]))); + + data.writeUInt32BE(value, 1); + assert.ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xf9, 0x0a, 0x6d, 0]))); + + data.writeUInt32BE(value, 2); + assert.ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xe7, 0xf9, 0x0a, 0x6d]))); + + data.writeUInt32LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x6d, 0x0a, 0xf9, 0xe7, 0x0a, 0x6d]))); + + data.writeUInt32LE(value, 1); + assert.ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x0a, 0xf9, 0xe7, 0x6d]))); + + data.writeUInt32LE(value, 2); + assert.ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x6d, 0x0a, 0xf9, 0xe7]))); +} + +// Test 48 bit +{ + const value = 0x1234567890ab; + const data = Buffer.allocUnsafe(6); + data.writeUIntBE(value, 0, 6); + assert.ok(data.equals(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]))); + + data.writeUIntLE(value, 0, 6); + assert.ok(data.equals(new Uint8Array([0xab, 0x90, 0x78, 0x56, 0x34, 0x12]))); +} + +// Test UInt +{ + const data = Buffer.alloc(8); + let val = 0x100; + + // Check byteLength. + ['writeUIntBE', 'writeUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((bl) => { + assert.throws( + () => data[fn](23, 0, bl), + { + code: 'ERR_INVALID_ARG_TYPE', + }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => data[fn](23, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + } + ); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => data[fn](42, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + const range = i < 5 ? `= ${val - 1}` : ` 2 ** ${i * 8}`; + const received = String(val); + ['writeUIntBE', 'writeUIntLE'].forEach((fn) => { + assert.throws(() => { + data[fn](val, 0, i); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "value" is out of range. ' + + `It must be >= 0 and <${range}. Received ${received}` + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => data[fn](23, o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => data[fn](val - 1, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => data[fn](val - 1, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + + val *= 0x100; + } +} + +for (const fn of [ + 'UInt8', 'UInt16LE', 'UInt16BE', 'UInt32LE', 'UInt32BE', 'UIntLE', 'UIntBE', + 'BigUInt64LE', 'BigUInt64BE', +]) { + const p = Buffer.prototype; + const lowerFn = fn.replace(/UInt/, 'Uint'); + assert.strictEqual(p[`write${fn}`], p[`write${lowerFn}`]); + assert.strictEqual(p[`read${fn}`], p[`read${lowerFn}`]); +} diff --git a/test/js/node/test/parallel/test-buffer-zero-fill-cli.js b/test/js/node/test/parallel/test-buffer-zero-fill-cli.js new file mode 100644 index 00000000000000..6c2b6608b197f1 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-zero-fill-cli.js @@ -0,0 +1,32 @@ +'use strict'; +// Flags: --zero-fill-buffers + +// when using --zero-fill-buffers, every Buffer and SlowBuffer +// instance must be zero filled upon creation + +require('../common'); +const SlowBuffer = require('buffer').SlowBuffer; +const assert = require('assert'); + +function isZeroFilled(buf) { + for (const n of buf) + if (n > 0) return false; + return true; +} + +// This can be somewhat unreliable because the +// allocated memory might just already happen to +// contain all zeroes. The test is run multiple +// times to improve the reliability. +for (let i = 0; i < 50; i++) { + const bufs = [ + Buffer.alloc(20), + Buffer(20), + Buffer.allocUnsafe(20), + SlowBuffer(20), + new SlowBuffer(20), + ]; + for (const buf of bufs) { + assert(isZeroFilled(buf)); + } +} diff --git a/test/js/node/test/parallel/test-common-must-not-call.js b/test/js/node/test/parallel/test-common-must-not-call.js index b3c94a2390ffb6..87e78b1a0ac1da 100644 --- a/test/js/node/test/parallel/test-common-must-not-call.js +++ b/test/js/node/test/parallel/test-common-must-not-call.js @@ -22,7 +22,7 @@ const createValidate = (line, args = []) => common.mustCall((e) => { const rest = msg.substring(firstColon + 1); assert.strictEqual(path.basename(fileName), 'test-common-must-not-call.js'); const argsInfo = args.length > 0 ? - `\ncalled with arguments: ${args.map(util.inspect).join(', ')}` : ''; + `\ncalled with arguments: ${args.map(v => Bun.inspect(v)).join(', ')}` : ''; assert.strictEqual(rest, line + argsInfo); });