Skip to content
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
29 changes: 27 additions & 2 deletions crates/perry-runtime/src/object/object_ops/prototype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,23 @@ pub extern "C" fn js_object_create(proto_value: f64) -> f64 {
// instanceof C` was always false even though property /
// getter dispatch through the chain worked correctly.
let parent_class_id = unsafe { (*proto_ptr).class_id };
if parent_class_id != 0 && parent_class_id != cid {
// #8343 followup: `NATIVE_MODULE_CLASS_ID` (0xFFFFFFFE) is a
// sentinel tagging native-module namespace objects, NOT a real
// declared class. Registering it as a synthetic class's parent
// makes `get_parent_class_id` return it, and
// `js_object_get_prototype_of`'s class-ref branch then returns
// the raw sentinel as an INT32-tagged class ref (`-2`).
// `Object.create(that)` rejects it with
// `TypeError: Object prototype may only be an Object or null: -2`
// (rolldown's `__toESM` → `Object.create(Object.getPrototypeOf(mod))`
// chain). Skip the registration so the synthetic class is treated
// as a root — its prototype is already stored in
// `CLASS_PROTOTYPE_OBJECTS` by `class_prototype_object_root_store`
// above, which is what `getPrototypeOf` reads.
if parent_class_id != 0
&& parent_class_id != cid
&& parent_class_id != super::super::native_module::NATIVE_MODULE_CLASS_ID
{
register_class(cid, parent_class_id);
}
class_id = cid;
Expand Down Expand Up @@ -324,7 +340,16 @@ pub extern "C" fn js_object_get_prototype_of(obj_value: f64) -> f64 {
if top16 == 0x7FFE {
let class_id = (bits & 0xFFFF_FFFF) as u32;
if let Some(parent_id) = get_parent_class_id(class_id) {
if parent_id != 0 {
// #8343 followup: `NATIVE_MODULE_CLASS_ID` (0xFFFFFFFE) is a
// sentinel, not a real class. A prior `Object.create(proto)` whose
// `proto` was a native-module namespace object registered it as a
// synthetic class's parent. Returning the raw sentinel as an
// INT32-tagged class ref (`-2`) trips `Object.create` with
// `TypeError: Object prototype may only be an Object or null: -2`.
// Treat it as a root: a native-module namespace's [[Prototype]] is
// %Object.prototype%, so the synthetic class whose proto was that
// namespace inherits Object.prototype too.
if parent_id != 0 && parent_id != super::super::native_module::NATIVE_MODULE_CLASS_ID {
let parent_bits = 0x7FFE_0000_0000_0000u64 | (parent_id as u64);
return f64::from_bits(parent_bits);
}
Expand Down
77 changes: 77 additions & 0 deletions crates/perry/tests/cjs_wrap_builtin_require.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,80 @@ console.log("join:", join("a", "b"));
format!("platform: {expected_platform}\narch: string\njoin: a/b\n")
);
}

/// #8343 followup: `Object.create(require("process"))` stamps the result with
/// a synthetic class_id and — pre-fix — registered `NATIVE_MODULE_CLASS_ID`
/// (0xFFFFFFFE, the `-2` sentinel) as that synthetic class's parent via
/// `register_class`. Later, `Object.getPrototypeOf` on the synthetic class
/// ref (returned by `instance.constructor`) walked the parent chain and
/// returned the raw sentinel as an INT32-tagged class ref (`-2`).
/// `Object.create(-2)` then threw
/// `TypeError: Object prototype may only be an Object or null: -2` — the
/// blocker for `sdxgen --help` (rolldown's `__toESM` calls
/// `Object.create(Object.getPrototypeOf(mod))`).
///
/// This witness creates the same `Object.create(builtin_namespace)` shape,
/// reads `.constructor` to obtain the synthetic class ref, and then runs the
/// `Object.create(Object.getPrototypeOf(ctor))` chain. Pre-fix it threw the
/// `-2` TypeError; post-fix it succeeds.
#[test]
fn cjs_wrap_object_create_on_builtin_namespace_get_prototype_of_not_sentinel() {
let dir = tempfile::tempdir().expect("tempdir");
let stdout = compile_and_run_cjs(
dir.path(),
r#"
const ns = require("process");
const obj = Object.create(ns);
const ctor = obj.constructor;
const proto = Object.getPrototypeOf(ctor);
const obj2 = Object.create(proto);
console.log("ok");
"#,
);
assert_eq!(stdout, "ok\n");
}

/// The full rolldown `__toESM` shape (the real one using
/// `Object.create(Object.getPrototypeOf(mod))`) must not throw the `-2`
/// TypeError when `mod` is a built-in module namespace AND a prior
/// `Object.create(builtin_namespace)` has registered the sentinel parent.
/// This mirrors the `external-pack.js` startup sequence: `__toESM` calls on
/// `require("process")` etc. interleave with `isPlainObject`/`deepMerge`
/// prototype-chain walks that hit `getPrototypeOf` on synthetic class refs.
#[test]
fn cjs_wrap_rolldown_toesm_after_object_create_on_builtin_namespace() {
let dir = tempfile::tempdir().expect("tempdir");
let stdout = compile_and_run_cjs(
dir.path(),
r#"
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// First: Object.create on a built-in namespace registers the sentinel parent.
const ns = require("process");
const obj = Object.create(ns);
const ctor = obj.constructor;
// Now run __toESM on another built-in — its Object.create(getPrototypeOf(mod))
// must not trip on the registered sentinel.
let node_os = require("os");
node_os = __toESM(node_os, 1);
console.log("os.cpus:", typeof node_os.cpus);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"#,
);
assert_eq!(stdout, "os.cpus: function\n");
}
Loading