Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "c28156899e5f90ce22e2b5d780e117c6268edfc4";
export const WEBKIT_VERSION = "autobuild-preview-pr-691-f63021bf";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
9 changes: 5 additions & 4 deletions src/jsc/bindings/VectorSizeLimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ extern "C" size_t Bun__stringSyntheticAllocationLimit;
namespace Bun {

// The most elements a Vector<T> can hold, lowered by Bun__stringSyntheticAllocationLimit so tests reach it cheaply.
// The size in bytes fits in an unsigned, and the capacity in the 31 bits that the borrow bit leaves.
template<typename T>
size_t maxVectorSize()
{
constexpr size_t maxBytes = std::numeric_limits<unsigned>::max() >> 1;
static_assert(WTF::isValidCapacityForVector<T>(maxBytes / sizeof(T)));
static_assert(!WTF::isValidCapacityForVector<T>(maxBytes / sizeof(T) + 1));
return std::min(maxBytes, Bun__stringSyntheticAllocationLimit) / sizeof(T);
constexpr size_t maxCapacity = std::min<size_t>(std::numeric_limits<unsigned>::max() / sizeof(T), std::numeric_limits<unsigned>::max() >> 1);
static_assert(WTF::isValidCapacityForVector<T>(maxCapacity));
static_assert(!WTF::isValidCapacityForVector<T>(maxCapacity + 1));
return std::min(maxCapacity, Bun__stringSyntheticAllocationLimit / sizeof(T));
}

// A Deque's capacity is a power of two within the Vector bound, and the ring keeps one slot empty.
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static void free_global_string(void* str, void* ptr, unsigned len)

static WTF::String convertUTF8ToString(std::span<const unsigned char> bytes)
{
// fromUTF8ReplacingInvalidSequences CRASH()es past a ~2^30-byte input
// fromUTF8ReplacingInvalidSequences CRASH()es past a ~2^31-byte input
// (it sizes an intermediate Vector<char16_t> by byte count).
if (WTF::isValidCapacityForVector<char16_t>(bytes.size())) [[likely]]
return WTF::String::fromUTF8ReplacingInvalidSequences(bytes);
Expand Down
37 changes: 36 additions & 1 deletion test/js/bun/util/fileUrl.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { fileURLToPath, pathToFileURL } from "bun";
import { describe, expect, it } from "bun:test";
import { isWindows } from "harness";
import { bunEnv, bunExe, isASAN, isDebug, isWindows } from "harness";
import { totalmem } from "node:os";

describe("pathToFileURL", () => {
it("should convert a path to a file url", () => {
Expand Down Expand Up @@ -61,4 +62,38 @@ describe("fileURLToPath", () => {
expect(fileURLToPath(url)).toBe(import.meta.path);
expect(fileURLToPath(import.meta.url)).toBe(import.meta.path);
});

// WTF::String::fromUTF8 converts into a Vector<char16_t> of one code unit for each byte, and WTF::URL::fileSystemPath()
// decodes the path through it. WebKit 310668@main halved the capacity of every Vector whose element is wider than a
// byte, so a decoded path of 2**30 bytes aborted the process: `panic(main thread): abort() called`, exit code 134.
// Bun 1.3.12 returns the path. A debug build takes minutes to parse a URL of this size, and the child peaks at 7 GiB.
it.skipIf(isDebug || isASAN || totalmem() < 12 * 1024 ** 3)(
"decodes a path of 2**30 bytes",
async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
import { fileURLToPath } from "node:url";
// U+4E00 keeps the decoded path from being all ASCII, which converts with no UTF-16 buffer.
// repeat() makes a rope, so the child holds the long URL once.
const path = fileURLToPath(${JSON.stringify(isWindows ? "file:///C:/" : "file:///")} + "%E4%B8%80" + "q".repeat(2 ** 30));
console.log(JSON.stringify({ length: path.length, head: path.slice(0, 5), tail: path.slice(-2) }));
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
const root = isWindows ? "C:\\" : "/";
expect({ stdout: JSON.parse(stdout || "null"), stderr, exitCode }).toEqual({
stdout: { length: root.length + 1 + 2 ** 30, head: (root + "\u4e00qqq").slice(0, 5), tail: "qq" },
stderr: "",
exitCode: 0,
});
},
60_000,
);
});
Loading