Bug 1055472 - Part 2b: Make Function.prototype.bind play nicely with subclassed functions. (r=Waldo, shoutouts for good flyby by evilpie)
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Nov 2015 18:22:21 -0800
changeset 309830 1c9325b0e173416859f2782bf94e087fadee987e
parent 309829 695d86baf80e80bb6642256ab836e9a26d5db05e
child 309831 58937c535d90b9f87cb1619b0486b44a62b260b0
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo, shoutouts
bugs1055472
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1055472 - Part 2b: Make Function.prototype.bind play nicely with subclassed functions. (r=Waldo, shoutouts for good flyby by evilpie)
js/src/jsfun.cpp
js/src/jsfun.h
js/src/tests/ecma_6/Class/boundFunctionSubclassing.js
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1587,16 +1587,32 @@ fun_isGenerator(JSContext* cx, unsigned 
         args.rval().setBoolean(false);
         return true;
     }
 
     args.rval().setBoolean(fun->isGenerator());
     return true;
 }
 
+static JSFunction*
+NewNativeFunctionWithGivenProto(JSContext* cx, Native native, unsigned nargs,
+                                HandleAtom atom, HandleObject proto)
+{
+    return NewFunctionWithProto(cx, native, nargs, JSFunction::NATIVE_FUN, nullptr, atom, proto,
+                                AllocKind::FUNCTION, GenericObject, NewFunctionGivenProto);
+}
+
+static JSFunction*
+NewNativeConstructorWithGivenProto(JSContext* cx, Native native, unsigned nargs,
+                                   HandleAtom atom, HandleObject proto)
+{
+    return NewFunctionWithProto(cx, native, nargs, JSFunction::NATIVE_CTOR, nullptr, atom, proto,
+                                AllocKind::FUNCTION, GenericObject, NewFunctionGivenProto);
+}
+
 // ES6 draft rev32 19.2.3.2
 bool
 js::fun_bind(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedValue thisv(cx, args.thisv());
@@ -1613,16 +1629,21 @@ js::fun_bind(JSContext* cx, unsigned arg
     if (args.length() > 1) {
         boundArgs = args.array() + 1;
         argslen = args.length() - 1;
     }
 
     RootedValue thisArg(cx, args.length() >= 1 ? args[0] : UndefinedValue());
     RootedObject target(cx, &thisv.toObject());
 
+    // This is part of step 4, but we're delaying allocating the function object.
+    RootedObject proto(cx);
+    if (!GetPrototype(cx, target, &proto))
+        return false;
+
     double length = 0.0;
     // Try to avoid invoking the resolve hook.
     if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedLength()) {
         uint16_t len;
         if (!target->as<JSFunction>().getLength(cx, &len))
             return false;
         length = Max(0.0, double(len) - argslen);
     } else {
@@ -1668,18 +1689,18 @@ js::fun_bind(JSContext* cx, unsigned arg
         return false;
 
     RootedAtom nameAtom(cx, sb.finishAtom());
     if (!nameAtom)
         return false;
 
     // Step 4.
     RootedFunction fun(cx, target->isConstructor() ?
-      NewNativeConstructor(cx, CallOrConstructBoundFunction, length, nameAtom) :
-      NewNativeFunction(cx, CallOrConstructBoundFunction, length, nameAtom));
+      NewNativeConstructorWithGivenProto(cx, CallOrConstructBoundFunction, length, nameAtom, proto) :
+      NewNativeFunctionWithGivenProto(cx, CallOrConstructBoundFunction, length, nameAtom, proto));
     if (!fun)
         return false;
 
     if (!fun->initBoundFunction(cx, target, thisArg, boundArgs, argslen))
         return false;
 
     // Steps 9-10. Set length again, because NewNativeFunction/NewNativeConstructor
     // sometimes truncates.
@@ -2019,30 +2040,37 @@ NewFunctionScopeIsWellFormed(ExclusiveCo
 }
 #endif
 
 JSFunction*
 js::NewFunctionWithProto(ExclusiveContext* cx, Native native,
                          unsigned nargs, JSFunction::Flags flags, HandleObject enclosingDynamicScope,
                          HandleAtom atom, HandleObject proto,
                          gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
-                         NewObjectKind newKind /* = GenericObject */)
+                         NewObjectKind newKind /* = GenericObject */,
+                         NewFunctionProtoHandling protoHandling /* = NewFunctionClassProto */)
 {
     MOZ_ASSERT(allocKind == AllocKind::FUNCTION || allocKind == AllocKind::FUNCTION_EXTENDED);
     MOZ_ASSERT_IF(native, !enclosingDynamicScope);
     MOZ_ASSERT(NewFunctionScopeIsWellFormed(cx, enclosingDynamicScope));
 
     RootedObject funobj(cx);
     // Don't mark asm.js module functions as singleton since they are
     // cloned (via CloneFunctionObjectIfNotSingleton) which assumes that
     // isSingleton implies isInterpreted.
     if (native && !IsAsmJSModuleNative(native))
         newKind = SingletonObject;
-    funobj = NewObjectWithClassProto(cx, &JSFunction::class_, proto, allocKind,
-                                     newKind);
+
+    if (protoHandling == NewFunctionClassProto) {
+        funobj = NewObjectWithClassProto(cx, &JSFunction::class_, proto, allocKind,
+                                         newKind);
+    } else {
+        funobj = NewObjectWithGivenTaggedProto(cx, &JSFunction::class_, AsTaggedProto(proto),
+                                               allocKind, newKind);
+    }
     if (!funobj)
         return nullptr;
 
     RootedFunction fun(cx, &funobj->as<JSFunction>());
 
     if (allocKind == AllocKind::FUNCTION_EXTENDED)
         flags = JSFunction::Flags(flags | JSFunction::EXTENDED);
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -631,25 +631,33 @@ NewNativeConstructor(ExclusiveContext* c
 // global will be used.  In all cases the parent of the resulting object will be
 // the global.
 extern JSFunction*
 NewScriptedFunction(ExclusiveContext* cx, unsigned nargs, JSFunction::Flags flags,
                     HandleAtom atom, gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                     NewObjectKind newKind = GenericObject,
                     HandleObject enclosingDynamicScope = nullptr);
 
-// If proto is nullptr, Function.prototype is used instead.  If
+// By default, if proto is nullptr, Function.prototype is used instead.i
+// If protoHandling is NewFunctionExactProto, and proto is nullptr, the created
+// function will use nullptr as its [[Prototype]] instead. If
 // enclosingDynamicScope is null, the function will have a null environment()
 // (yes, null, not the global).  In all cases, the global will be used as the
 // parent.
+
+enum NewFunctionProtoHandling {
+    NewFunctionClassProto,
+    NewFunctionGivenProto
+};
 extern JSFunction*
 NewFunctionWithProto(ExclusiveContext* cx, JSNative native, unsigned nargs,
                      JSFunction::Flags flags, HandleObject enclosingDynamicScope, HandleAtom atom,
                      HandleObject proto, gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
-                     NewObjectKind newKind = GenericObject);
+                     NewObjectKind newKind = GenericObject,
+                     NewFunctionProtoHandling protoHandling = NewFunctionClassProto);
 
 extern JSAtom*
 IdToFunctionName(JSContext* cx, HandleId id);
 
 extern JSFunction*
 DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native,
                unsigned nargs, unsigned flags,
                gc::AllocKind allocKind = gc::AllocKind::FUNCTION);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/boundFunctionSubclassing.js
@@ -0,0 +1,24 @@
+var test = `
+
+class func extends Function { }
+let inst = new func("x", "return this.bar + x");
+
+// First, ensure that we get sane prototype chains for the bound instance
+let bound = inst.bind({bar: 3}, 4);
+assertEq(bound instanceof func, true);
+assertEq(bound(), 7);
+
+// Check the corner case for Function.prototype.bind where the function has
+// a null [[Prototype]]
+Object.setPrototypeOf(inst, null);
+bound = Function.prototype.bind.call(inst, {bar:1}, 3);
+assertEq(Object.getPrototypeOf(bound), null);
+assertEq(bound(), 4);
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");