Skip to content

Commit

Permalink
js: fix Buffer constructor and Buffer.from (#16731)
Browse files Browse the repository at this point in the history
  • Loading branch information
nektro authored Feb 7, 2025
1 parent c970922 commit 1805001
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 174 deletions.
16 changes: 8 additions & 8 deletions bench/snippets/buffer-create.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,24 @@ bench("Buffer.from(ArrayBuffer(100))", () => {
return Buffer.from(hundred);
});

bench("new Buffer(ArrayBuffer(100))", () => {
return new Buffer(hundred);
});

var hundredArray = new Uint8Array(100);
bench("Buffer.from(Uint8Array(100))", () => {
return Buffer.from(hundredArray);
});

bench("new Buffer(Uint8Array(100))", () => {
return new Buffer(hundredArray);
});

var empty = new Uint8Array(0);
bench("Buffer.from(Uint8Array(0))", () => {
return Buffer.from(empty);
});

bench("new Buffer(ArrayBuffer(100))", () => {
return new Buffer(hundred);
});

bench("new Buffer(Uint8Array(100))", () => {
return new Buffer(hundredArray);
});

bench("new Buffer(Uint8Array(0))", () => {
return new Buffer(empty);
});
Expand Down
6 changes: 5 additions & 1 deletion src/bun.js/bindings/ErrorCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,8 +618,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, ASCIILiteral 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 {};
}
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/bindings/ErrorCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal
JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView 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, ASCIILiteral 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);
JSC::EncodedJSValue UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject);
Expand Down
159 changes: 67 additions & 92 deletions src/bun.js/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write);
extern "C" EncodedJSValue WebCore_BufferEncodingType_toJS(JSC::JSGlobalObject* lexicalGlobalObject, WebCore::BufferEncodingType encoding)
{
// clang-format off
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
switch (encoding) {
case WebCore::BufferEncodingType::utf8: return JSC::JSValue::encode(globalObject->commonStrings().utf8String(globalObject));
case WebCore::BufferEncodingType::ucs2: return JSC::JSValue::encode(globalObject->commonStrings().ucs2String(globalObject));
Expand Down Expand Up @@ -218,7 +218,7 @@ static JSUint8Array* allocBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_
auto throwScope = DECLARE_THROW_SCOPE(vm);
#endif

auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();

auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, byteLength);
Expand Down Expand Up @@ -320,7 +320,7 @@ JSC::EncodedJSValue JSBuffer__bufferFromPointerAndLengthAndDeinit(JSC::JSGlobalO

JSC::JSUint8Array* uint8Array = nullptr;

auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();

if (LIKELY(length > 0)) {
Expand Down Expand Up @@ -417,7 +417,7 @@ JSC::JSUint8Array* createEmptyBuffer(JSC::JSGlobalObject* lexicalGlobalObject)

JSC::JSUint8Array* createUninitializedBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_t length)
{
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();

return JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, subclassStructure, length);
Expand All @@ -436,7 +436,7 @@ static inline JSC::JSUint8Array* JSBuffer__bufferFromLengthAsArray(JSC::JSGlobal
return nullptr;
}

auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, static_cast<size_t>(length));

Expand Down Expand Up @@ -2412,6 +2412,60 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO

} // namespace WebCore

EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue)
{
auto* globalObject = defaultGlobalObject(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));
}

EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, size_t argsCount, JSValue arrayBufferValue, JSValue offsetValue, JSValue lengthValue)
{
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);

auto* jsBuffer = jsCast<JSC::JSArrayBuffer*>(arrayBufferValue.asCell());
RefPtr<ArrayBuffer> buffer = jsBuffer->impl();
if (buffer->isDetached()) {
return throwVMTypeError(globalObject, throwScope, "Buffer is detached"_s);
}
size_t byteLength = buffer->byteLength();
size_t offset = 0;
size_t length = byteLength;

if (!offsetValue.isUndefined()) {
double offsetD = offsetValue.toNumber(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
if (std::isnan(offsetD)) offsetD = 0;
offset = offsetD;
if (offset > byteLength) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "offset"_s);
length -= offset;
}

if (!lengthValue.isUndefined()) {
double lengthD = lengthValue.toNumber(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
if (std::isnan(lengthD)) lengthD = 0;
length = lengthD;
if (length > byteLength - offset) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "length"_s);
}

auto* subclassStructure = globalObject->JSBufferSubclassStructure();
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(buffer), offset, length);
if (UNLIKELY(!uint8Array)) {
throwOutOfMemoryError(globalObject, throwScope);
return {};
}

RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
}

static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSValue newTarget, ArgList args)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
Expand All @@ -2423,36 +2477,34 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi
}
JSValue distinguishingArg = args.at(0);
JSValue encodingArg = argsCount > 1 ? args.at(1) : JSValue();
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);

auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
if (distinguishingArg.isAnyInt()) {
throwScope.release();
if (args.at(1).isString()) {
return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "string"_s, "string"_s, distinguishingArg);
}
return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt());
auto anyint = distinguishingArg.asAnyInt();
if (anyint < 0 or anyint > Bun::Buffer::kMaxLength) return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "size"_s, 0, Bun::Buffer::kMaxLength, distinguishingArg);
return JSValue::encode(allocBuffer(lexicalGlobalObject, anyint));
} else if (distinguishingArg.isNumber()) {
JSValue lengthValue = distinguishingArg;
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);
return JSValue::encode(allocBuffer(lexicalGlobalObject, length));
} else if (distinguishingArg.isUndefinedOrNull() || distinguishingArg.isBoolean()) {
auto arg_string = distinguishingArg.toWTFString(globalObject);
auto message = makeString("The first argument must be of type string or an instance of Buffer, ArrayBuffer, Array or an Array-like object. Received "_s, arg_string);
throwTypeError(lexicalGlobalObject, throwScope, message);
return {};
return throwVMTypeError(globalObject, throwScope, message);
} else if (distinguishingArg.isCell()) {
auto type = distinguishingArg.asCell()->type();

switch (type) {
case StringType:
case StringObjectType:
case DerivedStringObjectType: {
throwScope.release();
return constructBufferFromStringAndEncoding(lexicalGlobalObject, distinguishingArg, encodingArg);
}

case Uint16ArrayType:
case Uint32ArrayType:
case Int8ArrayType:
Expand All @@ -2465,127 +2517,50 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi
case BigUint64ArrayType: {
// byteOffset and byteLength are ignored in this case, which is consitent with Node.js and new Uint8Array()
JSC::JSArrayBufferView* view = jsCast<JSC::JSArrayBufferView*>(distinguishingArg.asCell());

void* data = view->vector();
size_t byteLength = view->length();

if (UNLIKELY(!data)) {
throwException(globalObject, throwScope, createRangeError(globalObject, "Buffer is detached"_s));
return {};
}

auto* uint8Array = createUninitializedBuffer(lexicalGlobalObject, byteLength);
if (UNLIKELY(!uint8Array)) {
ASSERT(throwScope.exception());
return {};
}

if (byteLength) {
uint8Array->setFromTypedArray(lexicalGlobalObject, 0, view, 0, byteLength, CopyType::LeftToRight);
}

RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
break;
}

case DataViewType:
case Uint8ArrayType:
case Uint8ClampedArrayType: {
// byteOffset and byteLength are ignored in this case, which is consitent with Node.js and new Uint8Array()
JSC::JSArrayBufferView* view = jsCast<JSC::JSArrayBufferView*>(distinguishingArg.asCell());

void* data = view->vector();
size_t byteLength = view->byteLength();

if (UNLIKELY(!data)) {
throwException(globalObject, throwScope, createRangeError(globalObject, "Buffer is detached"_s));
return {};
}

auto* uint8Array = createBuffer(lexicalGlobalObject, static_cast<uint8_t*>(data), byteLength);

RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
}
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<size_t> 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<JSC::JSArrayBuffer*>(distinguishingArg.asCell());
RefPtr<ArrayBuffer> 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;
}
}
}

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))
{
return createJSBufferFromJS(lexicalGlobalObject, callFrame->thisValue(), ArgList(callFrame));
Expand Down
2 changes: 2 additions & 0 deletions src/bun.js/bindings/JSEnvironmentVariableMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ JSC_DEFINE_CUSTOM_SETTER(jsBunConfigVerboseFetchSetter, (JSGlobalObject * global
return true;
}

#if OS(WINDOWS)
extern "C" void Bun__Process__editWindowsEnvVar(BunString, BunString);

JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::CallFrame* callFrame))
Expand All @@ -274,6 +275,7 @@ JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::Cal
}
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
}
#endif

JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
{
Expand Down
3 changes: 3 additions & 0 deletions src/bun.js/bindings/js_classes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export default [
// class list for $inherits*() builtins, eg. $inheritsBlob()
// tests if a value is an instanceof a native class in a robust cross-realm manner
// source-of-truth impl in src/codegen/generate-classes.ts
// result in build/debug/codegen/ZigGeneratedClasses.cpp
["Blob"],
["ReadableStream", "JSReadableStream.h"],
["WritableStream", "JSWritableStream.h"],
["TransformStream", "JSTransformStream.h"],
["ArrayBuffer"],
];
Loading

0 comments on commit 1805001

Please sign in to comment.