Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions src/jsc/bindings/webcore/JSEventListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,13 @@ void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext
if (!globalObject)
return;

// JSObject::globalObject() is null for a cell with a realmless structure. A WebAssembly GC
// object (a struct or array reference) is the one kind of such cell that plain JS can hold,
// and addEventListener() accepts any object. Such an object is never callable, so it reaches
// the handleEvent lookup below. Use the realm of the event target instead.
Comment thread
robobun marked this conversation as resolved.
Resolved
JSGlobalObject* lexicalGlobalObject = jsFunction->globalObject();
if (!lexicalGlobalObject) [[unlikely]]
lexicalGlobalObject = globalObject;

JSValue handleEventFunction = jsFunction;

Expand Down
60 changes: 60 additions & 0 deletions test/js/web/event-target.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

// A WebAssembly GC object is the one kind of object that plain JS can hold whose structure
// has no realm, so JSObject::globalObject() is null for it. addEventListener() accepts any
// object as a listener, which used to make the dispatch read that null realm.
//
// (module
// (type $s (struct (field (mut i32))))
// (func (export "mk") (result (ref null $s)) struct.new_default $s))
const makeWasmGCObject = `
const bytes = new Uint8Array([0,0x61,0x73,0x6d,1,0,0,0, 1,10,2, 0x5f,1,0x7f,1, 0x60,0,1,0x63,0, 3,2,1,1, 7,6,1,2,0x6d,0x6b,0,0, 10,7,1,5,0,0xfb,1,0,0x0b]);
const listener = new WebAssembly.Instance(new WebAssembly.Module(bytes)).exports.mk();
`;

test.concurrent("dispatchEvent() does not crash on a WebAssembly GC object listener", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`${makeWasmGCObject}
const target = new EventTarget();
target.addEventListener("a", listener);
target.dispatchEvent(new Event("a"));
console.log("survived");`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("survived\n");
expect(stderr).toContain("'handleEvent' property of event listener should be callable");
expect(exitCode).toBe(1);
});

test.concurrent("AbortSignal abort does not crash on a WebAssembly GC object listener", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`${makeWasmGCObject}
const controller = new AbortController();
controller.signal.addEventListener("abort", listener);
controller.abort();
console.log("survived");`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("survived\n");
expect(stderr).toContain("'handleEvent' property of event listener should be callable");
expect(exitCode).toBe(1);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Loading