Skip to content

util.inherits(SomeClass, …) throws "Object.defineProperty called on non-object" — set_super_property has no class-ref arm #9362

Description

@proggeramlug

Repro

// util_inherits_class.ts
const util = require("util");
class Base {}
class Derived {}
util.inherits(Derived, Base);
console.log(typeof Derived.super_, Derived.super_ === Base);
node : function true
perry: Uncaught TypeError: Object.defineProperty called on non-object    rc=1

Note the asymmetry — only a class in the first position fails:

call node perry
util.inherits(function(){}, function(){}) ok ok
util.inherits(function(){}, SomeClass) ok ok
util.inherits(SomeClass, function(){}) ok TypeError
util.inherits(SomeClass, OtherClass) ok TypeError

Long-standing, not a regression: it reproduces identically on 83754818e (#9242) and on a03be729c (#9336).

Cause

set_super_property in crates/perry-runtime/src/util_inherits.rs handles exactly two receiver shapes — a closure ctor and a heap-object ctor:

fn set_super_property(ctor: f64, super_ctor: f64) {
    let ctor_closure = closure_ptr(ctor);
    if ctor_closure != 0 { /* closure dynamic prop */ return; }
    let obj = object_ptr(ctor);
    if obj.is_null() {
        crate::object::throw_object_type_error(b"Object.defineProperty called on non-object");
    }}

A class constructor arrives as an INT32-tagged class ref (0x7FFE_…), which is neither a closure nor a heap object, so it falls to the throw. Confirmed by instrumenting the site — the ctor value is 0x7ffe000000000008, i.e. class ref, class id 8.

Object.defineProperty(SomeClass, "super_", …) on the same value works, because define_property.rs has the third arm this function is missing:

let target_is_class_ref = super::super::class_ref_id(obj_value).is_some();
if !target_is_class_ref && !value_is_object_like(obj_value) { … throw … }

Suggested fix

Give set_super_property the class-ref arm, mirroring what define_property.rs does for a static own property — for a constructor-half class ref (class_ref_id(ctor).is_some() && class_prototype_ref_id(ctor).is_none()):

class_registry::class_dynamic_prop_root_store(cid, "super_", super_ctor);
class_registry::class_static_set_defined_attrs(cid, "super_", /*writable*/ true, /*enumerable*/ false, /*configurable*/ true);

which matches Node's descriptor ({ value: superCtor, writable: true, configurable: true }) and the attrs the closure arm already uses. ensure_function_prototype / js_object_set_prototype_of on the ctor side should be checked at the same time, since the throw currently masks whatever they do with a class ref.

Why it is worth a fixture

Found while bisecting #9341: this is the other of the only two sites in the tree that emit Object.defineProperty called on non-object, and the two are byte-identical strings, so this shape is a standing source of misattribution when that message shows up in a bundle. A regression test pinning util.inherits across {function, class} x {function, class} would keep them distinguishable.

Found with the fixture above; not filed as a blocker for #9341 — that one is the define_property.rs site.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions