Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
c7273a1893 fix(ffi): CString.byteLength and CString.byteOffset are undefined when not explicitly passed
When constructing a CString with only a pointer argument, byteOffset and
byteLength were left as undefined because they were only set when
explicitly passed. Now byteOffset defaults to 0 and byteLength is
computed from the string's UTF-8 byte length when not provided.

Closes #22920

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 04:40:50 +00:00
2 changed files with 73 additions and 11 deletions

View File

@@ -117,19 +117,20 @@ class JSCallback {
class CString extends String {
constructor(ptr, byteOffset?, byteLength?) {
super(
ptr
? typeof byteLength === "number" && Number.isSafeInteger(byteLength)
? BunCString(ptr, byteOffset || 0, byteLength)
: BunCString(ptr, byteOffset || 0)
: "",
);
const str = ptr
? typeof byteLength === "number" && Number.isSafeInteger(byteLength)
? BunCString(ptr, byteOffset || 0, byteLength)
: BunCString(ptr, byteOffset || 0)
: "";
super(str);
this.ptr = typeof ptr === "number" ? ptr : 0;
if (typeof byteOffset !== "undefined") {
this.byteOffset = byteOffset;
}
if (typeof byteLength !== "undefined") {
this.byteOffset = typeof byteOffset === "number" ? byteOffset : 0;
if (typeof byteLength === "number") {
this.byteLength = byteLength;
} else if (this.ptr) {
this.byteLength = Buffer.byteLength(str, "utf8");
} else {
this.byteLength = 0;
}
}

View File

@@ -0,0 +1,61 @@
import { CString, ptr } from "bun:ffi";
import { expect, test } from "bun:test";
test("CString byteLength and byteOffset are defined when constructed with only a pointer", () => {
const buffer = Buffer.from("Hello world!\0");
const bufferPtr = ptr(buffer);
const cString = new CString(bufferPtr);
expect(cString.byteOffset).toBe(0);
expect(cString.byteLength).toBe(12);
expect(cString.toString()).toBe("Hello world!");
});
test("CString byteOffset defaults to 0 when only ptr and byteLength are provided", () => {
const buffer = Buffer.from("Hello world!\0");
const bufferPtr = ptr(buffer);
const cString = new CString(bufferPtr, 0, 12);
expect(cString.byteOffset).toBe(0);
expect(cString.byteLength).toBe(12);
expect(cString.toString()).toBe("Hello world!");
});
test("CString with byteOffset", () => {
const buffer = Buffer.from("Hello world!\0");
const bufferPtr = ptr(buffer);
const cString = new CString(bufferPtr, 6);
expect(cString.byteOffset).toBe(6);
expect(cString.byteLength).toBe(6);
expect(cString.toString()).toBe("world!");
});
test("CString with byteOffset and byteLength", () => {
const buffer = Buffer.from("Hello world!\0");
const bufferPtr = ptr(buffer);
const cString = new CString(bufferPtr, 6, 5);
expect(cString.byteOffset).toBe(6);
expect(cString.byteLength).toBe(5);
expect(cString.toString()).toBe("world");
});
test("CString with null pointer has byteLength 0 and byteOffset 0", () => {
const cString = new CString(0);
expect(cString.byteOffset).toBe(0);
expect(cString.byteLength).toBe(0);
expect(cString.toString()).toBe("");
});
test("CString byteLength is correct for multi-byte UTF-8 strings", () => {
// "café" in UTF-8 is 5 bytes (c=1, a=1, f=1, é=2)
const buffer = Buffer.from("café\0");
const bufferPtr = ptr(buffer);
const cString = new CString(bufferPtr);
expect(cString.byteOffset).toBe(0);
expect(cString.byteLength).toBe(5);
expect(cString.toString()).toBe("café");
});