Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js: fix Buffer constructor and Buffer.from #16731

Merged
merged 10 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
148 changes: 62 additions & 86 deletions src/bun.js/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2377,6 +2377,60 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO

} // namespace WebCore

EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue)
{
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
nektro marked this conversation as resolved.
Show resolved Hide resolved

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 = reinterpret_cast<Zig::GlobalObject*>(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();
nektro marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -2388,35 +2442,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);

if (distinguishingArg.isAnyInt()) {
nektro marked this conversation as resolved.
Show resolved Hide resolved
throwScope.release();
if (args.at(1).isString()) {
Jarred-Sumner marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -2429,127 +2482,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: {
nektro marked this conversation as resolved.
Show resolved Hide resolved
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"],
];
2 changes: 1 addition & 1 deletion src/codegen/generate-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2281,7 +2281,7 @@ JSC_DEFINE_HOST_FUNCTION(Zig::jsFunctionInherits, (JSC::JSGlobalObject * globalO
switch (id) {
${jsclasses
.map(v => v[0])
.map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(cell->inherits<WebCore::JS${v}>()));`)
.map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(jsDynamicCast<WebCore::JS${v}*>(cell) != nullptr));`)
nektro marked this conversation as resolved.
Show resolved Hide resolved
.join("\n")}
}
return JSValue::encode(jsBoolean(false));
Expand Down
Loading