[proxies] Correctly handle proxies in Function.prototype.bind
- Before getting the length property, we must check for it using [[GetOwnProperty]]. Also, if the obtained length is a number, we must properly convert it to an integer. - In order to get the prototype we must use [[GetPrototypeOf]], and do so before checking the length. R=cbruni@chromium.org, jkummerow@chromium.org BUG=v8:1543 LOG=n Review URL: https://codereview.chromium.org/1530893002 Cr-Commit-Position: refs/heads/master@{#32934}
This commit is contained in:
parent
a0c7e25f99
commit
0d83aad557
@ -1302,9 +1302,7 @@ function FunctionToString() {
|
||||
}
|
||||
|
||||
|
||||
// ES5 15.3.4.5
|
||||
// ES6 9.2.3.2 Function.prototype.bind(thisArg , ...args)
|
||||
// TODO(cbruni): check again and remove FunctionProxies section further down
|
||||
function FunctionBind(this_arg) { // Length is 1.
|
||||
if (!IS_CALLABLE(this)) throw MakeTypeError(kFunctionBind);
|
||||
|
||||
@ -1336,20 +1334,23 @@ function FunctionBind(this_arg) { // Length is 1.
|
||||
return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
|
||||
};
|
||||
|
||||
var proto = %_GetPrototype(this); // in ES6 9.4.1.3 BoundFunctionCreate
|
||||
|
||||
var new_length = 0;
|
||||
var old_length = this.length;
|
||||
// FunctionProxies might provide a non-UInt32 value. If so, ignore it.
|
||||
if ((typeof old_length === "number") &&
|
||||
((old_length >>> 0) === old_length)) {
|
||||
var argc = %_ArgumentsLength();
|
||||
if (argc > 0) argc--; // Don't count the thisArg as parameter.
|
||||
new_length = old_length - argc;
|
||||
if (new_length < 0) new_length = 0;
|
||||
if (ObjectGetOwnPropertyDescriptor(this, "length") !== UNDEFINED) {
|
||||
var old_length = this.length;
|
||||
if (IS_NUMBER(old_length)) {
|
||||
var argc = %_ArgumentsLength();
|
||||
if (argc > 0) argc--; // Don't count the thisArg as parameter.
|
||||
new_length = TO_INTEGER(old_length) - argc;
|
||||
if (new_length < 0) new_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// This runtime function finds any remaining arguments on the stack,
|
||||
// so we don't pass the arguments object.
|
||||
var result = %FunctionBindArguments(boundFunction, this,
|
||||
this_arg, new_length);
|
||||
var result = %FunctionBindArguments(boundFunction, this, this_arg,
|
||||
new_length, proto);
|
||||
|
||||
var name = this.name;
|
||||
var bound_name = IS_STRING(name) ? name : "";
|
||||
|
@ -390,11 +390,12 @@ base::SmartArrayPointer<Handle<Object>> Runtime::GetCallerArguments(
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 4);
|
||||
DCHECK(args.length() == 5);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSFunction, bound_function, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, bindee, 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, this_object, 2);
|
||||
CONVERT_NUMBER_ARG_HANDLE_CHECKED(new_length, 3);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, proto, 4);
|
||||
|
||||
// TODO(lrn): Create bound function in C++ code from premade shared info.
|
||||
bound_function->shared()->set_bound(true);
|
||||
@ -446,13 +447,11 @@ RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
|
||||
// is happy about the number of fields.
|
||||
RUNTIME_ASSERT(bound_function->RemovePrototype());
|
||||
|
||||
// The new function should have the same [[Prototype]] as the bindee.
|
||||
// The new function should have the given prototype.
|
||||
Handle<Map> bound_function_map =
|
||||
bindee->IsConstructor()
|
||||
? isolate->bound_function_with_constructor_map()
|
||||
: isolate->bound_function_without_constructor_map();
|
||||
PrototypeIterator iter(isolate, bindee);
|
||||
Handle<Object> proto = PrototypeIterator::GetCurrent(iter);
|
||||
if (bound_function_map->prototype() != *proto) {
|
||||
bound_function_map = Map::TransitionToPrototype(bound_function_map, proto,
|
||||
REGULAR_PROTOTYPE);
|
||||
|
@ -254,7 +254,7 @@ namespace internal {
|
||||
F(ThrowStrongModeTooFewArguments, 0, 1) \
|
||||
F(IsConstructor, 1, 1) \
|
||||
F(SetForceInlineFlag, 1, 1) \
|
||||
F(FunctionBindArguments, 4, 1) \
|
||||
F(FunctionBindArguments, 5, 1) \
|
||||
F(BoundFunctionGetBindings, 1, 1) \
|
||||
F(NewObjectFromBound, 1, 1) \
|
||||
F(Call, -1 /* >= 2 */, 1) \
|
||||
|
@ -27,7 +27,8 @@
|
||||
|
||||
// Flags: --allow-natives-syntax
|
||||
|
||||
// Tests the Function.prototype.bind (ES 15.3.4.5) method.
|
||||
// Tests the Function.prototype.bind method.
|
||||
|
||||
|
||||
// Simple tests.
|
||||
function foo(x, y, z) {
|
||||
|
137
test/mjsunit/harmony/proxies-bind.js
Normal file
137
test/mjsunit/harmony/proxies-bind.js
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2015 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --harmony-proxies --harmony-reflect
|
||||
|
||||
// Tests the interaction of Function.prototype.bind with proxies.
|
||||
|
||||
|
||||
// (Helper)
|
||||
|
||||
var log = [];
|
||||
var logger = {};
|
||||
var handler = new Proxy({}, logger);
|
||||
|
||||
logger.get = function(t, trap, r) {
|
||||
return function() {
|
||||
log.push([trap, ...arguments]);
|
||||
return Reflect[trap](...arguments);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Simple case
|
||||
|
||||
var target = function(a, b, c) { "use strict"; return this };
|
||||
var proxy = new Proxy(target, handler);
|
||||
var this_value = Symbol();
|
||||
|
||||
log.length = 0;
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(2, result.length);
|
||||
assertEquals(target.__proto__, result.__proto__);
|
||||
assertEquals(this_value, result());
|
||||
assertEquals(5, log.length);
|
||||
for (var i in log) assertSame(target, log[i][1]);
|
||||
assertEquals(["getPrototypeOf", target], log[0]);
|
||||
assertEquals(["getOwnPropertyDescriptor", target, "length"], log[1]);
|
||||
assertEquals(["get", target, "length", proxy], log[2]);
|
||||
assertEquals(["get", target, "name", proxy], log[3]);
|
||||
assertEquals(["apply", target, this_value, ["foo"]], log[4]);
|
||||
assertEquals(new target(), new result());
|
||||
|
||||
|
||||
// Custom prototype
|
||||
|
||||
log.length = 0;
|
||||
target.__proto__ = {radio: "gaga"};
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(2, result.length);
|
||||
assertSame(target.__proto__, result.__proto__);
|
||||
assertEquals(this_value, result());
|
||||
assertEquals(5, log.length);
|
||||
for (var i in log) assertSame(target, log[i][1]);
|
||||
assertEquals(["getPrototypeOf", target], log[0]);
|
||||
assertEquals(["getOwnPropertyDescriptor", target, "length"], log[1]);
|
||||
assertEquals(["get", target, "length", proxy], log[2]);
|
||||
assertEquals(["get", target, "name", proxy], log[3]);
|
||||
assertEquals(["apply", target, this_value, ["foo"]], log[4]);
|
||||
|
||||
|
||||
// Custom length
|
||||
|
||||
handler = {
|
||||
get() {return 42},
|
||||
getOwnPropertyDescriptor() {return {configurable: true}}
|
||||
};
|
||||
proxy = new Proxy(target, handler);
|
||||
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(41, result.length);
|
||||
assertEquals(this_value, result());
|
||||
|
||||
|
||||
// Long length
|
||||
|
||||
handler = {
|
||||
get() {return Math.pow(2, 100)},
|
||||
getOwnPropertyDescriptor() {return {configurable: true}}
|
||||
};
|
||||
proxy = new Proxy(target, handler);
|
||||
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(Math.pow(2, 100) - 1, result.length);
|
||||
assertEquals(this_value, result());
|
||||
|
||||
|
||||
// Very long length
|
||||
|
||||
handler = {
|
||||
get() {return 1/0},
|
||||
getOwnPropertyDescriptor() {return {configurable: true}}
|
||||
};
|
||||
proxy = new Proxy(target, handler);
|
||||
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(1/0, result.length);
|
||||
assertEquals(this_value, result());
|
||||
|
||||
|
||||
// Non-integer length
|
||||
|
||||
handler = {
|
||||
get() {return 4.2},
|
||||
getOwnPropertyDescriptor() {return {configurable: true}}
|
||||
};
|
||||
proxy = new Proxy(target, handler);
|
||||
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(3, result.length);
|
||||
assertEquals(this_value, result());
|
||||
|
||||
|
||||
// Undefined length
|
||||
|
||||
handler = {
|
||||
get() {},
|
||||
getOwnPropertyDescriptor() {return {configurable: true}}
|
||||
};
|
||||
proxy = new Proxy(target, handler);
|
||||
|
||||
result = Function.prototype.bind.call(proxy, this_value, "foo");
|
||||
assertEquals(0, result.length);
|
||||
assertEquals(this_value, result());
|
||||
|
||||
|
||||
// Non-callable
|
||||
|
||||
assertThrows(() => Function.prototype.bind.call(new Proxy({}, {})), TypeError);
|
||||
assertThrows(() => Function.prototype.bind.call(new Proxy([], {})), TypeError);
|
||||
|
||||
|
||||
// Non-constructable
|
||||
|
||||
result = Function.prototype.bind.call(() => 42, this_value, "foo");
|
||||
assertEquals(42, result());
|
||||
assertThrows(() => new result());
|
Loading…
Reference in New Issue
Block a user