Skip to content

fix: use Object.setPrototypeOf in JS bindings #2922

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 29, 2025
Merged
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
Use Object.setPrototypeOf instead of Object.create+assign for bindings
Apparently some bundlers use objects with read-only properties for
imported modules. This conflicts with Object.assign even though the
object being assigned to only has read-only properties on its prototype.
Regardless, if the assignment is done before the prototype is set,
everything works just fine.

Fixes #2659
  • Loading branch information
CountBleck committed May 29, 2025
commit 5b5b0babad42a76e39de32c0158a7f4dbbea0c4b
26 changes: 13 additions & 13 deletions src/bindings/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,18 +554,10 @@ export class JSBuilder extends ExportsWalker {
continue;
}
let resetPos = sb.length;
sb.push(": Object.assign(Object.create(");
if (moduleName == "env") {
sb.push("globalThis");
} else {
sb.push("__module");
sb.push(moduleId.toString());
}
sb.push("), ");
if (moduleName == "env") {
sb.push("imports.env || {}, ");
}
sb.push("{\n");

// Use Object.setPrototypeOf to avoid issues with read-only properties
// on module objects created by bundlers (issue #2659)
sb.push(": Object.setPrototypeOf({\n");
++this.indentLevel;
let numInstrumented = 0;
for (let _keys2 = Map_keys(module), j = 0, l = _keys2.length; j < l; ++j) {
Expand Down Expand Up @@ -598,7 +590,15 @@ export class JSBuilder extends ExportsWalker {
sb.push(",\n");
} else {
indent(sb, this.indentLevel);
sb.push("}),\n");
sb.push("}, ");
if (moduleName == "env") {
// TODO: If necessary, use "Object.setPrototypeOf(Object.assign({}, imports.env || {}), globalThis)"
sb.push("Object.assign(Object.create(globalThis), imports.env || {})");
} else {
sb.push("__module");
sb.push(moduleId.toString());
}
sb.push("),\n");
}
}
--this.indentLevel;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/esm.debug.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
trace(message, n, a0, a1, a2, a3, a4) {
// ~lib/builtins/trace(~lib/string/String, i32?, f64?, f64?, f64?, f64?, f64?) => void
message = __liftString(message >>> 0);
Expand Down Expand Up @@ -44,7 +44,7 @@ async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/esm.release.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
trace(message, n, a0, a1, a2, a3, a4) {
// ~lib/builtins/trace(~lib/string/String, i32?, f64?, f64?, f64?, f64?, f64?) => void
message = __liftString(message >>> 0);
Expand Down Expand Up @@ -44,7 +44,7 @@ async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/noExportRuntime.debug.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
abort(message, fileName, lineNumber, columnNumber) {
// ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
message = __liftString(message >>> 0);
Expand All @@ -12,7 +12,7 @@ async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/noExportRuntime.release.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
abort(message, fileName, lineNumber, columnNumber) {
// ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
message = __liftString(message >>> 0);
Expand All @@ -12,7 +12,7 @@ async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/raw.debug.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
trace(message, n, a0, a1, a2, a3, a4) {
// ~lib/builtins/trace(~lib/string/String, i32?, f64?, f64?, f64?, f64?, f64?) => void
message = __liftString(message >>> 0);
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/bindings/raw.release.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export async function instantiate(module, imports = {}) {
const adaptedImports = {
env: Object.assign(Object.create(globalThis), imports.env || {}, {
env: Object.setPrototypeOf({
trace(message, n, a0, a1, a2, a3, a4) {
// ~lib/builtins/trace(~lib/string/String, i32?, f64?, f64?, f64?, f64?, f64?) => void
message = __liftString(message >>> 0);
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function instantiate(module, imports = {}) {
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
}),
}, Object.assign(Object.create(globalThis), imports.env || {})),
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
Expand Down