Commit 0d83aad5 authored by neis's avatar neis Committed by Commit bot

[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}
parent a0c7e25f
......@@ -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) {
......
// 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());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment