diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index faadfda0aaaf..76a81200e1b1 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` 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. diff --git a/src/jsc/bindings/VectorSizeLimit.h b/src/jsc/bindings/VectorSizeLimit.h index 56d49a53b165..ef304b52ecf7 100644 --- a/src/jsc/bindings/VectorSizeLimit.h +++ b/src/jsc/bindings/VectorSizeLimit.h @@ -9,13 +9,14 @@ extern "C" size_t Bun__stringSyntheticAllocationLimit; namespace Bun { // The most elements a Vector 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 size_t maxVectorSize() { - constexpr size_t maxBytes = std::numeric_limits::max() >> 1; - static_assert(WTF::isValidCapacityForVector(maxBytes / sizeof(T))); - static_assert(!WTF::isValidCapacityForVector(maxBytes / sizeof(T) + 1)); - return std::min(maxBytes, Bun__stringSyntheticAllocationLimit) / sizeof(T); + constexpr size_t maxCapacity = std::min(std::numeric_limits::max() / sizeof(T), std::numeric_limits::max() >> 1); + static_assert(WTF::isValidCapacityForVector(maxCapacity)); + static_assert(!WTF::isValidCapacityForVector(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. diff --git a/src/jsc/bindings/helpers.h b/src/jsc/bindings/helpers.h index e4f4b8730c4d..785667545706 100644 --- a/src/jsc/bindings/helpers.h +++ b/src/jsc/bindings/helpers.h @@ -74,7 +74,7 @@ static void free_global_string(void* str, void* ptr, unsigned len) static WTF::String convertUTF8ToString(std::span bytes) { - // fromUTF8ReplacingInvalidSequences CRASH()es past a ~2^30-byte input + // fromUTF8ReplacingInvalidSequences CRASH()es past a ~2^31-byte input // (it sizes an intermediate Vector by byte count). if (WTF::isValidCapacityForVector(bytes.size())) [[likely]] return WTF::String::fromUTF8ReplacingInvalidSequences(bytes); diff --git a/test/js/bun/util/fileUrl.test.js b/test/js/bun/util/fileUrl.test.js index a9bcd96a9b23..0bf194efaa29 100644 --- a/test/js/bun/util/fileUrl.test.js +++ b/test/js/bun/util/fileUrl.test.js @@ -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", () => { @@ -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 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, + ); });