Closed Bug 1536851 Opened 5 years ago Closed 5 years ago

JS_NewObjectForConstructor doesn't cooperate with JS subclassing

Categories

(Core :: JavaScript Engine, defect, P5)

64 Branch
defect

Tracking

()

RESOLVED FIXED
mozilla69
Tracking Status
firefox69 --- fixed

People

(Reporter: np.shinigami, Assigned: jorendorff)

Details

Attachments

(2 files)

User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36

Steps to reproduce:

I have write a native class based in the examples contained in test:

struct mybase { int val; };

static JSClassOps jsmybase_ops = {
	nullptr
};

static JSClass jsmybase_class = {
	"mybase", JSCLASS_HAS_PRIVATE, &jsmybase_ops
};

// jbase constructor.
static bool jsmybase_ctor(JSContext* ctx, unsigned argc, JS::Value* vp)
{
      JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
      {
           JS::RootedObject jsmybaseObj(ctx, JS_NewObjectForConstructor(ctx, &jsmybase_class, args));
	   {
	          JS_SetPrivate(jsmybaseObj, new mybase);
	   }
	   args.rval().setObject(*jswxAppObj);
       }
       return true;
}

This is called in main function after SM initialization:

          JSObject* proto = JS_InitClass(ctx, globalObj, nullptr, &jsmybase_class, jsbase_ctor, 0, nullptr, nullptr, nullptr, nullptr);

I have executed this script in a file:

class myclass extends mybase {
    OnInit() {}
}

let a = new myclass();

a.OnInit();

I tested in 3 version of SpiderMonkey 64, 65, 66. With same result.

Actual results:

class myclass extends mybase {
    OnInit() {}
}

let a = new myclass();

a.OnInit();  // <-- OnInit is undefined

let a_proto = a.__proto__; // a_proto is mybase.prototype not myclass.prototype
let a_proto_proto = a.__proto__.__proto__; // a_proto_proto is Object.prototype not mybase.prototype

I tried to set this in jsmybase_ctor like this:

        args.rval().setObject(*jswxAppObj);
        args.setThis(JS::ObjectValue(*jswxAppObj));

But don't work.

Expected results:

class myclass extends mybase {
    OnInit() {}
}

let a = new myclass();

a.OnInit();  // <-- OnInit is undefined

let a_proto = a.__proto__; // a_proto should be myclass.prototype
let a_proto_proto = a.__proto__.__proto__; // a_proto_proto should be mybase.prototype
Has STR: --- → yes

Bobby, do you have ideas on this?

Flags: needinfo?(bobbyholley)
Component: XPConnect → JavaScript Engine
Flags: needinfo?(bobbyholley)

Yeah, our own builtin constructors all do this in order to support subclassing, but JS_NewObjectForConstructor does this.

I think we could just change JS_NewObjectForConstructor.

Priority: -- → P5

Classes defined in JS using the class keyword, without using extends,
implicitly construct a new this object before running the body of the
constructor, and they use new.target.prototype as the new object's
[[Prototype]]. This is one of the ways that JS classes support subclassing.
When the subclass calls the base class constructor, the subclass's .prototype
is used.

Until now, we did not do this for classes defined in C++ using
JS_NewObjectForConstructor. But there's no reason it shouldn't work the same
way. Changing this is a nicety for embedders -- there's nothing using it in
Firefox or internally right now, except for testing.

Attachment #9065454 - Attachment description: Bug 1536851 - Change JS_NewObjectForConstructor to respect `new.target`, like non-derived JS classes. r?jwalden → Bug 1536851 - Part 1: Change JS_NewObjectForConstructor to respect `new.target`, like non-derived JS classes. r?jwalden

I just realized the change I made in part 1 introduces an unintended gotcha for embedders: if there's no new.target (which is the case if the base class is called directly, without new) then it asserts and crashes.

The most idiomatic thing is for Base() without new to be a TypeError, so that's what part 2 does. Alternatively, it could fall back on args.callee() in that case, for maximum backward compatibility, but that seems too weird. I copied the approach from the Promise constructor.

Pushed by jorendorff@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/459c574bd2d0
Part 1: Change JS_NewObjectForConstructor to respect `new.target`, like non-derived JS classes. r=jwalden
https://hg.mozilla.org/integration/autoland/rev/5f5575bb2b93
Part 2: JS_NewObjectForConstructor should fail (not crash) if not constructing. r=jwalden
Assignee: nobody → jorendorff
Summary: Class declaration problem with native class inheritance → JS_NewObjectForConstructor doesn't cooperate with JS subclassing
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Status: ASSIGNED → RESOLVED
Closed: 5 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla69
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: