Skip to content
Open
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
4 changes: 3 additions & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "e6e37cda216c0292ae68c30c84a9dc8601d0fba5";
// Preview of oven-sh/WebKit#385: guard InspectorDebuggerAgent::disable() so a redundant
// Debugger.disable does not trip ASSERT(!!m_client != !!client) in JSC::Debugger::setClient.
export const WEBKIT_VERSION = "autobuild-preview-pr-385-10e1ab9b";

Check failure on line 8 in scripts/build/deps/webkit.ts

View check run for this annotation

Claude / Claude Code Review

WebKit preview may silently downgrade JSC (branched from stale base)

The description says oven-sh/WebKit#385 was "branched off `34c01d1339` (current `WEBKIT_VERSION`)", but the line being replaced here is `e6e37cda216c…` — set by #36794, which also landed matching Bun-side changes in `analyze_transpiled_module`/`js_printer`/`analyze_jsc.rs`. If the preview is still based on `34c01d1339`, this pin silently reverts #36794's WebKit upgrade while leaving its Bun-side changes in place (per #36794's commit message, that mismatch "null-derefs `hostResolveImportedModule`
Comment thread
robobun marked this conversation as resolved.

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
92 changes: 92 additions & 0 deletions test/cli/inspect/inspect-debugger-disable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { spawn } from "bun";
import { expect, test } from "bun:test";
import { bunEnv, bunExe, isASAN, isDebug } from "harness";

// InspectorDebuggerAgent::disable() used to call internalDisable() unconditionally, so a
// Debugger.disable on an already-disabled agent (or before any enable) re-ran
// m_debugger.setClient(nullptr) and tripped ASSERT(!!m_client != !!client) in
// JSC::Debugger::setClient. Frontends send Debugger.disable liberally on teardown, so this is
// an ordinary sequence. The assert is ASSERT_ENABLED-only, hence the skipIf.
test.skipIf(!isDebug && !isASAN)("Debugger.disable is idempotent and does not abort on asserts builds", async () => {
await using child = spawn({
cmd: [bunExe(), "--inspect=127.0.0.1:0", "-e", "setInterval(()=>{},1000)"],
env: bunEnv,
stdout: "ignore",
stderr: "pipe",
});

let stderr = "";
const decoder = new TextDecoder();
const { promise: urlPromise, resolve: resolveUrl, reject: rejectUrl } = Promise.withResolvers<URL>();
const stderrDrained = (async () => {
for await (const chunk of child.stderr) {
stderr += decoder.decode(chunk);
const m = stderr.match(/ws:\/\/[^\s]+/);
if (m) resolveUrl(new URL(m[0]));
}
rejectUrl(new Error("inspectee exited before printing inspector URL:\n" + stderr));
})();

const url = await urlPromise;
const ws = new WebSocket(url);
const replies: Record<number, unknown> = {};
try {
let nextId = 1;
let failed: unknown;
const pending = new Map<number, (x: unknown) => void>();
const send = (method: string, params: object = {}) =>
new Promise<unknown>(resolve => {
if (failed) return resolve(failed);
const id = nextId++;
pending.set(id, resolve);
ws.send(JSON.stringify({ id, method, params }));
});
const fail = (r: unknown) => {
failed ??= r;
for (const p of pending.values()) p(r);
pending.clear();
};
ws.addEventListener("message", ev => {
const msg = JSON.parse(String(ev.data));
if (typeof msg.id === "number" && pending.has(msg.id)) {
replies[msg.id] = msg;
pending.get(msg.id)!(msg);
pending.delete(msg.id);
}
});
ws.addEventListener("close", ({ code, reason }) => fail({ closed: { code, reason } }));
ws.addEventListener("error", cause => fail({ error: String(cause) }));
await new Promise<void>((resolve, reject) => {
ws.addEventListener("open", () => resolve());
ws.addEventListener("error", cause => reject(new Error("WebSocket error", { cause })));
});

// Disable before any enable: m_enabled is false at startup, so the agent must early-return.
await send("Debugger.disable");
// Enable, disable, disable: the second disable must be a no-op, not a second setClient(nullptr).
await send("Debugger.enable");
await send("Debugger.disable");
await send("Debugger.disable");
// The agent must still be usable afterwards.
await send("Debugger.enable");
await send("Debugger.disable");
} finally {
ws.close();
child.kill();
}

await Promise.all([child.exited, stderrDrained.catch(() => {})]);
if (child.signalCode === "SIGABRT") {
throw new Error(
`inspectee aborted on Debugger.disable (replies=${JSON.stringify(replies)}):\n${stderr.slice(-2000)}`,
);
}
expect(replies).toEqual({
1: { id: 1, result: {} },
2: { id: 2, result: {} },
3: { id: 3, result: {} },
4: { id: 4, result: {} },
5: { id: 5, result: {} },
6: { id: 6, result: {} },
});
});
Loading