ReviewOS

zig-utils/zig-js

Private ABI: URL JS-value and static string helpers (fromJS/getHrefFromJS/getHref/getHrefJoin/getFileURLString/pathFromFileURL)

#309
Closed chris opened this 9 days ago ·
9 days ago

Parent: #140 Tracked by: #134, #163, #164

Symbols (URL cluster sub-slice 2 — JS-value and static string helpers)

SymbolHome (packages/runtime/src/jsc/URL.zig @ 7ed99c02)Bun C++ (upstream/src/jsc/bindings/BunString.cpp)
URL__fromJS:32 — (JSValue, *JSGlobalObject) ?*URL:596 — value.toWTFString (exception → null), empty/invalid → null, new WTF::URL
URL__getHrefFromJS:46:613 — coerce, empty/invalid → Dead, else url.string()
URL__getHref:47 — (*String) String:630 — parse, invalid/empty → Dead
URL__getHrefJoin:49 — (*String, *String) String:650 — WTF::URL(URL(base), relative), invalid → Dead
URL__getFileURLString:48:564 — URL::fileURLWithFileSystemPath(path).stringWithoutFragmentIdentifier()
URL__pathFromFileURL:50:640 — parse, invalid → Dead, else url.fileSystemPath() (percent-decoded path)

Builds directly on #308's native parsed-URL record and the allocator-first WHATWG parse layer. Remaining URL-cluster symbols after this slice: BunString__toURL/toJSDOMURL + DOMURL__* (JS DOMURL object creation) and URL__originLength (Home declares it un-wrapped).

Acceptance criteria

  • URL__fromJS coerces any same-VM JSValue through ToString; a throwing coercion publishes the pending exception and returns null (Bun's RETURN_IF_EXCEPTION → nullptr); empty/invalid → null without an exception; success returns the #308 native record.
  • URL__getHrefFromJS/URL__getHref return the full serialization as an owned BunString, Dead for empty/invalid input; coercion exceptions publish and return Dead.
  • URL__getHrefJoin resolves the relative BunString against the parsed base through urlParse(rel, base) and returns the joined serialization, Dead when either side is invalid.
  • URL__getFileURLString percent-encodes each /-separated path segment with the WHATWG path encode set (slashes preserved, dot segments NOT resolved — WTF sets the path post-parse) and prefixes file://.
  • URL__pathFromFileURL returns the percent-decoded path (%XX only, + stays literal — path, not query) of the parsed input, Dead when invalid.
  • Engine additions stay allocator-first and minimal: urlPercentEncode made pub, one new pub urlPercentDecode helper; zero behavior change to existing callers.
  • Compiled runtime fixture covers: fromJS on string/object-with-toString/throwing-toString (exception published, null returned), getHref family roundtrips, join with relative/absolute/dot-segment references against file and http bases, file-URL encode of paths with spaces/non-ASCII, file-path decode of percent-encoded and +-containing paths, null tolerance. Fixture symbol count 307 → 313.
  • Home inventory 310/431, Bun inventory 303/421; bun-private-abi-audit + c-api-audit green; full zig build test zero new failures (worktree baseline-vs-candidate against the same pinned base).

Design constraints

  • Same-VM coercion only: a foreign-VM EncodedValue returns null/Dead with a published TypeError, matching the established private-export VM-affinity rules.
  • No new parser machinery: join uses the existing base-resolution path; file-URL encode reuses the existing path encode set; decode adds only the +-free percent loop.
9 days ago

Evidence — landed on main as runtime 5ca8373c + docs bcf9b0fd.

Implementation:

  • src/c_api.zig: the six exports. URL__fromString's record builder was extracted into privateUrlCreateFromWTF8 (shared with URL__fromJS) plus a privateUrlHrefWTF8 serializer used by the href family — zero behavior change to #308 semantics. URL__fromJS/URL__getHrefFromJS run the standard same-VM preamble (ctxForEvaluation + GC/string-arena swap + active-interpreter push), coerce via toStringV, publish a TypeError for foreign-VM values, and publish thrown coercions through privateSetPendingAbrupt; empty/invalid yields null/Dead with no exception. The static helpers are context-free BunString in/out: getHref re-serializes with no base, getHrefJoin parses the base then resolves the relative reference through the existing urlParse(rel, base) path, getFileURLString builds file:// + per-segment WHATWG path-set encoding (slashes kept, dot segments unresolved), pathFromFileURL returns the percent-decoded path with + left literal.
  • src/interpreter.zig: urlPercentEncode/UrlEncSet made pub; one new pub urlPercentDecode (the +-free percent loop mirroring the form decoder). Allocator-first; no behavior change for existing callers.

Validation:

  • Compiled runtime fixture tests/abi/home_private_value_shims.zig 307 → 313/313: fromJS/getHrefFromJS on a URL string with JS new URL parity, object-with-toString coercion, throwing-toString (exception published + null/Dead, then cleared), empty/invalid without exception, foreign-VM TypeError, getHref roundtrips, join parity across relative/absolute-path/dot-segment references against http and file bases, file-URL encode for Latin-1 and UTF-16 inputs (space/non-ASCII → %20/%C3%A9/%E2%82%AC), dot-segment preservation, pathFromFileURL decode incl. literal +, null tolerance across all six.
  • bun-private-abi-audit: implemented-private 303/421 (pending 118). c-api-audit: 319 private profile exports, 117/117 public complete. test-home-public-abi: 50/50.
  • Worktree A/B at base e9869bc4: baseline 1152 passed / 3 failed vs candidate 1152 passed / 3 failed — identical failure sets (the three known pre-existing: gc slab size, wasm.decode malformed, wasm.validate GC canonical recursive type identity). Zero new failures.

Inventories refreshed: Home 310/431 implemented (121 pending), Bun 303/421 (118 pending). Docs boundary evidence published in docs/abi/README.md.

Sign in to comment on this issue.