Bug 1073816 - Implement ES6 Function.prototype.bind. r=Till
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -499,20 +499,20 @@ js::fun_resolve(JSContext *cx, HandleObj
// assertEq(f.length, 0); // gets Function.prototype.length!
// assertEq(f.name, ""); // gets Function.prototype.name!
// We use the RESOLVED_LENGTH and RESOLVED_NAME flags as a hack to prevent this
// bug.
if (isLength) {
if (fun->hasResolvedLength())
return true;
- if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
+ uint16_t length;
+ if (!fun->getLength(cx, &length))
return false;
- uint16_t length = fun->hasScript() ? fun->nonLazyScript()->funLength() :
- fun->nargs() - fun->hasRest();
+
v.setInt32(length);
} else {
if (fun->hasResolvedName())
return true;
v.setString(fun->atom() == nullptr ? cx->runtime()->emptyString : fun->atom());
}
@@ -1621,83 +1621,130 @@ fun_isGenerator(JSContext *cx, unsigned
args.rval().setBoolean(false);
return true;
}
args.rval().setBoolean(fun->isGenerator());
return true;
}
-/* ES5 15.3.4.5. */
+// ES6 draft rev32 19.2.3.2
bool
js::fun_bind(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
- /* Step 1. */
+ // Step 1.
RootedValue thisv(cx, args.thisv());
- /* Step 2. */
+ // Step 2.
if (!IsCallable(thisv)) {
ReportIncompatibleMethod(cx, args, &JSFunction::class_);
return false;
}
- /* Step 3. */
+ // Step 3.
Value *boundArgs = nullptr;
unsigned argslen = 0;
if (args.length() > 1) {
boundArgs = args.array() + 1;
argslen = args.length() - 1;
}
- /* Steps 7-9. */
+ // Steps 4-14.
RootedValue thisArg(cx, args.length() >= 1 ? args[0] : UndefinedValue());
RootedObject target(cx, &thisv.toObject());
JSObject *boundFunction = js_fun_bind(cx, target, thisArg, boundArgs, argslen);
if (!boundFunction)
return false;
- /* Step 22. */
+ // Step 15.
args.rval().setObject(*boundFunction);
return true;
}
JSObject*
js_fun_bind(JSContext *cx, HandleObject target, HandleValue thisArg,
Value *boundArgs, unsigned argslen)
{
- /* Steps 15-16. */
- unsigned length = 0;
- if (target->is<JSFunction>()) {
- unsigned nargs = target->as<JSFunction>().nargs();
- if (nargs > argslen)
- length = nargs - argslen;
+ 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 nullptr;
+ length = Max(0.0, double(len) - argslen);
+ } else {
+ // Steps 5-6.
+ RootedId id(cx, NameToId(cx->names().length));
+ bool hasLength;
+ if (!HasOwnProperty(cx, target, id, &hasLength))
+ return nullptr;
+
+ // Step 7-8.
+ if (hasLength) {
+ // a-b.
+ RootedValue targetLen(cx);
+ if (!GetProperty(cx, target, target, id, &targetLen))
+ return nullptr;
+ // d.
+ if (targetLen.isNumber())
+ length = Max(0.0, JS::ToInteger(targetLen.toNumber()) - argslen);
+ }
}
- /* Step 4-6, 10-11. */
- RootedAtom name(cx, target->is<JSFunction>() ? target->as<JSFunction>().atom() : nullptr);
+ RootedString name(cx, cx->names().empty);
+ if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedName()) {
+ if (target->as<JSFunction>().atom())
+ name = target->as<JSFunction>().atom();
+ } else {
+ // Steps 11-12.
+ RootedValue targetName(cx);
+ if (!GetProperty(cx, target, target, cx->names().name, &targetName))
+ return nullptr;
+ // Step 13.
+ if (targetName.isString())
+ name = targetName.toString();
+ }
+
+ // Step 14. Relevant bits from SetFunctionName.
+ StringBuffer sb(cx);
+ if (!sb.append("bound ") || !sb.append(name))
+ return nullptr;
+
+ RootedAtom nameAtom(cx, sb.finishAtom());
+ if (!nameAtom)
+ return nullptr;
+
+ // Step 4.
JSFunction::Flags flags = target->isConstructor() ? JSFunction::NATIVE_CTOR
: JSFunction::NATIVE_FUN;
- RootedObject funobj(cx, NewFunction(cx, js::NullPtr(), CallOrConstructBoundFunction, length,
- flags, target, name));
- if (!funobj)
+ RootedFunction fun(cx, NewFunction(cx, js::NullPtr(), CallOrConstructBoundFunction, length,
+ flags, target, nameAtom));
+ if (!fun)
+ return nullptr;
+
+ // NB: Bound functions abuse |parent| to store their target.
+ MOZ_ASSERT(fun->getParent() == target);
+
+ if (!fun->initBoundFunction(cx, thisArg, boundArgs, argslen))
return nullptr;
- /* NB: Bound functions abuse |parent| to store their target. */
- if (!JSObject::setParent(cx, funobj, target))
- return nullptr;
+ // Steps 9-10. Set length again, because NewFunction sometimes truncates.
+ if (length != fun->nargs()) {
+ RootedValue lengthVal(cx, NumberValue(length));
+ if (!DefineProperty(cx, fun, cx->names().length, lengthVal, nullptr, nullptr,
+ JSPROP_READONLY))
+ {
+ return nullptr;
+ }
+ }
- if (!funobj->as<JSFunction>().initBoundFunction(cx, thisArg, boundArgs, argslen))
- return nullptr;
-
- /* Steps 17, 19-21 are handled by fun_resolve. */
- /* Step 18 is the default for new functions. */
- return funobj;
+ return fun;
}
/*
* Report "malformed formal parameter" iff no illegal char or similar scanner
* error was already reported.
*/
static bool
OnBadFormal(JSContext *cx)
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -319,16 +319,26 @@ class JSFunction : public js::NativeObje
}
JSScript *nonLazyScript() const {
MOZ_ASSERT(hasScript());
MOZ_ASSERT(u.i.s.script_);
return u.i.s.script_;
}
+ bool getLength(JSContext *cx, uint16_t *length) {
+ JS::RootedFunction self(cx, this);
+ if (self->isInterpretedLazy() && !self->getOrCreateScript(cx))
+ return false;
+
+ *length = self->hasScript() ? self->nonLazyScript()->funLength()
+ : (self->nargs() - self->hasRest());
+ return true;
+ }
+
// Returns non-callsited-clone version of this. Use when return
// value can flow to arbitrary JS (see Bug 944975).
JSFunction* originalFunction() {
if (this->hasScript() && this->nonLazyScript()->isCallsiteClone()) {
return this->nonLazyScript()->donorFunction();
} else {
return this;
}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/bound-length-and-name.js
@@ -0,0 +1,40 @@
+var proxy = new Proxy(function() {}, {
+ getOwnPropertyDescriptor(target, name) {
+ assertEq(name, "length");
+ return {value: 3, configurable: true};
+ },
+
+ get(target, name) {
+ if (name == "length")
+ return 3;
+ if (name == "name")
+ return "hello world";
+ assertEq(false, true);
+ }
+})
+
+var bound = Function.prototype.bind.call(proxy);
+assertEq(bound.name, "bound hello world");
+assertEq(bound.length, 3);
+
+var fun = function() {};
+Object.defineProperty(fun, "name", {value: 1337});
+Object.defineProperty(fun, "length", {value: "15"});
+bound = fun.bind();
+assertEq(bound.name, "bound ");
+assertEq(bound.length, 0);
+
+Object.defineProperty(fun, "length", {value: Number.MAX_SAFE_INTEGER});
+bound = fun.bind();
+assertEq(bound.length, Number.MAX_SAFE_INTEGER);
+
+Object.defineProperty(fun, "length", {value: -100});
+bound = fun.bind();
+assertEq(bound.length, 0);
+
+fun = function f(a, ...b) { };
+assertEq(fun.length, 1);
+bound = fun.bind();
+assertEq(bound.length, 1);
+
+reportCompare(0, 0, 'ok');
--- a/js/src/tests/js1_8_5/extensions/scripted-proxies.js
+++ b/js/src/tests/js1_8_5/extensions/scripted-proxies.js
@@ -26,37 +26,49 @@ function test() {
testObj(new Array());
testObj(new RegExp());
testObj(Date);
testObj(Array);
testObj(RegExp);
/* Test function proxies. */
var proxy = Proxy.createFunction({
- get: function(obj,name) { return Function.prototype[name]; },
- fix: function() {
- return ({});
- }
+ get: function(obj, name) {
+ return Function.prototype[name];
+ },
+ getOwnPropertyDescriptor: function(obj, name) {
+ return Object.getOwnPropertyDescriptor(Function.prototype, name);
+ },
+ fix: function() {
+ return ({});
+ }
}, function() { return "call"; });
assertEq(proxy(), "call");
assertEq(Function.prototype.bind.call(proxy)(), "call");
assertEq(typeof proxy, "function");
if ("isTrapping" in Proxy) {
assertEq(Proxy.isTrapping(proxy), true);
assertEq(Proxy.fix(proxy), true);
assertEq(Proxy.isTrapping(proxy), false);
assertEq(typeof proxy, "function");
assertEq(proxy(), "call");
}
/* Test function proxies as constructors. */
var proxy = Proxy.createFunction({
- get: function(obj, name) { return Function.prototype[name]; },
- fix: function() { return ({}); }
+ get: function(obj, name) {
+ return Function.prototype[name];
+ },
+ getOwnPropertyDescriptor: function(obj, name) {
+ return Object.getOwnPropertyDescriptor(Function.prototype, name);
+ },
+ fix: function() {
+ return ({});
+ }
},
function() { var x = {}; x.origin = "call"; return x; },
function() { var x = {}; x.origin = "new"; return x; })
assertEq(proxy().origin, "call");
assertEq(Function.prototype.bind.call(proxy)().origin, "call");
assertEq((new proxy()).origin, "new");
assertEq(new (Function.prototype.bind.call(proxy))().origin, "new");