mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
fix(buffer): return fixed-length view from slice on resizable ArrayBuffer (#26822)
## Summary Follow-up to #26819 ([review comment](https://github.com/oven-sh/bun/pull/26819#discussion_r2781484939)). Fixes `Buffer.slice()` / `Buffer.subarray()` on resizable `ArrayBuffer` / growable `SharedArrayBuffer` to return a **fixed-length view** instead of a length-tracking view. ## Problem The resizable/growable branch was passing `std::nullopt` to `JSUint8Array::create()`, which creates a length-tracking view. When the underlying buffer grows, the sliced view's length would incorrectly expand: ```js const rab = new ArrayBuffer(10, { maxByteLength: 20 }); const buf = Buffer.from(rab); const sliced = buf.slice(0, 5); sliced.length; // 5 rab.resize(20); sliced.length; // was 10 (wrong), now 5 (correct) ``` Node.js specifies that `Buffer.slice()` always returns a fixed-length view (verified on Node.js v22). ## Fix Replace `std::nullopt` with `newLength` in the `isResizableOrGrowableShared()` branch of `jsBufferPrototypeFunction_sliceBody`. ## Test Added a regression test that creates a `Buffer` from a resizable `ArrayBuffer`, slices it, resizes the buffer, and verifies the slice length doesn't change. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1957,7 +1957,7 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_sliceBody(JSC::JSGlobalObje
|
||||
|
||||
if (castedThis->isResizableOrGrowableShared()) {
|
||||
auto* subclassStructure = globalObject->JSResizableOrGrowableSharedBufferSubclassStructure();
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, std::nullopt);
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
|
||||
@@ -930,6 +930,25 @@ for (let withOverridenBufferWrite of [false, true]) {
|
||||
expect(() => buf.subarray(0, 5)).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("slice() on resizable ArrayBuffer returns fixed-length view", () => {
|
||||
const rab = new ArrayBuffer(10, { maxByteLength: 20 });
|
||||
const buf = Buffer.from(rab);
|
||||
buf[0] = 1;
|
||||
buf[1] = 2;
|
||||
buf[2] = 3;
|
||||
buf[3] = 4;
|
||||
buf[4] = 5;
|
||||
|
||||
const sliced = buf.slice(0, 5);
|
||||
expect(sliced.length).toBe(5);
|
||||
expect(sliced[0]).toBe(1);
|
||||
expect(sliced[4]).toBe(5);
|
||||
|
||||
// Growing the buffer should NOT change the slice length
|
||||
rab.resize(20);
|
||||
expect(sliced.length).toBe(5);
|
||||
});
|
||||
|
||||
function forEachUnicode(label, test) {
|
||||
["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding =>
|
||||
it(`${label} (${encoding})`, test.bind(null, encoding)),
|
||||
|
||||
Reference in New Issue
Block a user