Make function proxies work as constructors.
R=kmillikin@chromium.org BUG=v8:1543 TEST= Review URL: http://codereview.chromium.org/7628021 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9310 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
ab30559fee
commit
07469fa5ae
@ -619,12 +619,12 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
// -- sp[...]: constructor arguments
|
// -- sp[...]: constructor arguments
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
|
|
||||||
Label non_function_call;
|
Label slow, non_function_call;
|
||||||
// Check that the function is not a smi.
|
// Check that the function is not a smi.
|
||||||
__ JumpIfSmi(r1, &non_function_call);
|
__ JumpIfSmi(r1, &non_function_call);
|
||||||
// Check that the function is a JSFunction.
|
// Check that the function is a JSFunction.
|
||||||
__ CompareObjectType(r1, r2, r2, JS_FUNCTION_TYPE);
|
__ CompareObjectType(r1, r2, r2, JS_FUNCTION_TYPE);
|
||||||
__ b(ne, &non_function_call);
|
__ b(ne, &slow);
|
||||||
|
|
||||||
// Jump to the function-specific construct stub.
|
// Jump to the function-specific construct stub.
|
||||||
__ ldr(r2, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
|
__ ldr(r2, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
|
||||||
@ -633,10 +633,19 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
|
|
||||||
// r0: number of arguments
|
// r0: number of arguments
|
||||||
// r1: called object
|
// r1: called object
|
||||||
|
// r2: object type
|
||||||
|
Label do_call;
|
||||||
|
__ bind(&slow);
|
||||||
|
__ cmp(r2, Operand(JS_FUNCTION_PROXY_TYPE));
|
||||||
|
__ b(ne, &non_function_call);
|
||||||
|
__ GetBuiltinEntry(r3, Builtins::CALL_FUNCTION_PROXY_AS_CONSTRUCTOR);
|
||||||
|
__ jmp(&do_call);
|
||||||
|
|
||||||
__ bind(&non_function_call);
|
__ bind(&non_function_call);
|
||||||
|
__ GetBuiltinEntry(r3, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
||||||
|
__ bind(&do_call);
|
||||||
// Set expected number of arguments to zero (not changing r0).
|
// Set expected number of arguments to zero (not changing r0).
|
||||||
__ mov(r2, Operand(0, RelocInfo::NONE));
|
__ mov(r2, Operand(0, RelocInfo::NONE));
|
||||||
__ GetBuiltinEntry(r3, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
|
||||||
__ SetCallKind(r5, CALL_AS_METHOD);
|
__ SetCallKind(r5, CALL_AS_METHOD);
|
||||||
__ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
__ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
||||||
RelocInfo::CODE_TARGET);
|
RelocInfo::CODE_TARGET);
|
||||||
|
@ -3634,6 +3634,7 @@ MaybeObject* Heap::ReinitializeJSReceiver(
|
|||||||
|
|
||||||
// Functions require some minimal initialization.
|
// Functions require some minimal initialization.
|
||||||
if (type == JS_FUNCTION_TYPE) {
|
if (type == JS_FUNCTION_TYPE) {
|
||||||
|
map->set_function_with_prototype(true);
|
||||||
String* name;
|
String* name;
|
||||||
MaybeObject* maybe_name = LookupAsciiSymbol("<freezing call trap>");
|
MaybeObject* maybe_name = LookupAsciiSymbol("<freezing call trap>");
|
||||||
if (!maybe_name->To<String>(&name)) return maybe_name;
|
if (!maybe_name->To<String>(&name)) return maybe_name;
|
||||||
|
@ -80,12 +80,12 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
// -- edi: constructor function
|
// -- edi: constructor function
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
|
|
||||||
Label non_function_call;
|
Label slow, non_function_call;
|
||||||
// Check that function is not a smi.
|
// Check that function is not a smi.
|
||||||
__ JumpIfSmi(edi, &non_function_call);
|
__ JumpIfSmi(edi, &non_function_call);
|
||||||
// Check that function is a JSFunction.
|
// Check that function is a JSFunction.
|
||||||
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
||||||
__ j(not_equal, &non_function_call);
|
__ j(not_equal, &slow);
|
||||||
|
|
||||||
// Jump to the function-specific construct stub.
|
// Jump to the function-specific construct stub.
|
||||||
__ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
|
__ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
|
||||||
@ -95,10 +95,19 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
|
|
||||||
// edi: called object
|
// edi: called object
|
||||||
// eax: number of arguments
|
// eax: number of arguments
|
||||||
|
// ecx: object map
|
||||||
|
Label do_call;
|
||||||
|
__ bind(&slow);
|
||||||
|
__ CmpInstanceType(ecx, JS_FUNCTION_PROXY_TYPE);
|
||||||
|
__ j(not_equal, &non_function_call);
|
||||||
|
__ GetBuiltinEntry(edx, Builtins::CALL_FUNCTION_PROXY_AS_CONSTRUCTOR);
|
||||||
|
__ jmp(&do_call);
|
||||||
|
|
||||||
__ bind(&non_function_call);
|
__ bind(&non_function_call);
|
||||||
|
__ GetBuiltinEntry(edx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
||||||
|
__ bind(&do_call);
|
||||||
// Set expected number of arguments to zero (not changing eax).
|
// Set expected number of arguments to zero (not changing eax).
|
||||||
__ Set(ebx, Immediate(0));
|
__ Set(ebx, Immediate(0));
|
||||||
__ GetBuiltinEntry(edx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
|
||||||
Handle<Code> arguments_adaptor =
|
Handle<Code> arguments_adaptor =
|
||||||
masm->isolate()->builtins()->ArgumentsAdaptorTrampoline();
|
masm->isolate()->builtins()->ArgumentsAdaptorTrampoline();
|
||||||
__ SetCallKind(ecx, CALL_AS_METHOD);
|
__ SetCallKind(ecx, CALL_AS_METHOD);
|
||||||
@ -751,156 +760,156 @@ void Builtins::Generate_FunctionApply(MacroAssembler* masm) {
|
|||||||
static const int kArgumentsOffset = 2 * kPointerSize;
|
static const int kArgumentsOffset = 2 * kPointerSize;
|
||||||
static const int kReceiverOffset = 3 * kPointerSize;
|
static const int kReceiverOffset = 3 * kPointerSize;
|
||||||
static const int kFunctionOffset = 4 * kPointerSize;
|
static const int kFunctionOffset = 4 * kPointerSize;
|
||||||
{
|
{
|
||||||
FrameScope frame_scope(masm, StackFrame::INTERNAL);
|
FrameScope frame_scope(masm, StackFrame::INTERNAL);
|
||||||
|
|
||||||
__ push(Operand(ebp, kFunctionOffset)); // push this
|
__ push(Operand(ebp, kFunctionOffset)); // push this
|
||||||
__ push(Operand(ebp, kArgumentsOffset)); // push arguments
|
__ push(Operand(ebp, kArgumentsOffset)); // push arguments
|
||||||
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION);
|
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION);
|
||||||
|
|
||||||
// Check the stack for overflow. We are not trying to catch
|
// Check the stack for overflow. We are not trying to catch
|
||||||
// interruptions (e.g. debug break and preemption) here, so the "real stack
|
// interruptions (e.g. debug break and preemption) here, so the "real stack
|
||||||
// limit" is checked.
|
// limit" is checked.
|
||||||
Label okay;
|
Label okay;
|
||||||
ExternalReference real_stack_limit =
|
ExternalReference real_stack_limit =
|
||||||
ExternalReference::address_of_real_stack_limit(masm->isolate());
|
ExternalReference::address_of_real_stack_limit(masm->isolate());
|
||||||
__ mov(edi, Operand::StaticVariable(real_stack_limit));
|
__ mov(edi, Operand::StaticVariable(real_stack_limit));
|
||||||
// Make ecx the space we have left. The stack might already be overflowed
|
// Make ecx the space we have left. The stack might already be overflowed
|
||||||
// here which will cause ecx to become negative.
|
// here which will cause ecx to become negative.
|
||||||
__ mov(ecx, Operand(esp));
|
__ mov(ecx, Operand(esp));
|
||||||
__ sub(ecx, Operand(edi));
|
__ sub(ecx, Operand(edi));
|
||||||
// Make edx the space we need for the array when it is unrolled onto the
|
// Make edx the space we need for the array when it is unrolled onto the
|
||||||
// stack.
|
// stack.
|
||||||
__ mov(edx, Operand(eax));
|
__ mov(edx, Operand(eax));
|
||||||
__ shl(edx, kPointerSizeLog2 - kSmiTagSize);
|
__ shl(edx, kPointerSizeLog2 - kSmiTagSize);
|
||||||
// Check if the arguments will overflow the stack.
|
// Check if the arguments will overflow the stack.
|
||||||
__ cmp(ecx, Operand(edx));
|
__ cmp(ecx, Operand(edx));
|
||||||
__ j(greater, &okay); // Signed comparison.
|
__ j(greater, &okay); // Signed comparison.
|
||||||
|
|
||||||
// Out of stack space.
|
// Out of stack space.
|
||||||
__ push(Operand(ebp, 4 * kPointerSize)); // push this
|
__ push(Operand(ebp, 4 * kPointerSize)); // push this
|
||||||
__ push(eax);
|
__ push(eax);
|
||||||
__ InvokeBuiltin(Builtins::APPLY_OVERFLOW, CALL_FUNCTION);
|
__ InvokeBuiltin(Builtins::APPLY_OVERFLOW, CALL_FUNCTION);
|
||||||
__ bind(&okay);
|
__ bind(&okay);
|
||||||
// End of stack check.
|
// End of stack check.
|
||||||
|
|
||||||
// Push current index and limit.
|
// Push current index and limit.
|
||||||
const int kLimitOffset =
|
const int kLimitOffset =
|
||||||
StandardFrameConstants::kExpressionsOffset - 1 * kPointerSize;
|
StandardFrameConstants::kExpressionsOffset - 1 * kPointerSize;
|
||||||
const int kIndexOffset = kLimitOffset - 1 * kPointerSize;
|
const int kIndexOffset = kLimitOffset - 1 * kPointerSize;
|
||||||
__ push(eax); // limit
|
__ push(eax); // limit
|
||||||
__ push(Immediate(0)); // index
|
__ push(Immediate(0)); // index
|
||||||
|
|
||||||
// Get the receiver.
|
// Get the receiver.
|
||||||
__ mov(ebx, Operand(ebp, kReceiverOffset));
|
__ mov(ebx, Operand(ebp, kReceiverOffset));
|
||||||
|
|
||||||
// Check that the function is a JS function (otherwise it must be a proxy).
|
// Check that the function is a JS function (otherwise it must be a proxy).
|
||||||
Label push_receiver;
|
Label push_receiver;
|
||||||
__ mov(edi, Operand(ebp, kFunctionOffset));
|
__ mov(edi, Operand(ebp, kFunctionOffset));
|
||||||
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
||||||
__ j(not_equal, &push_receiver);
|
__ j(not_equal, &push_receiver);
|
||||||
|
|
||||||
// Change context eagerly to get the right global object if necessary.
|
// Change context eagerly to get the right global object if necessary.
|
||||||
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
|
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
|
||||||
|
|
||||||
// Compute the receiver.
|
// Compute the receiver.
|
||||||
// Do not transform the receiver for strict mode functions.
|
// Do not transform the receiver for strict mode functions.
|
||||||
Label call_to_object, use_global_receiver;
|
Label call_to_object, use_global_receiver;
|
||||||
__ mov(ecx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
|
__ mov(ecx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
|
||||||
__ test_b(FieldOperand(ecx, SharedFunctionInfo::kStrictModeByteOffset),
|
__ test_b(FieldOperand(ecx, SharedFunctionInfo::kStrictModeByteOffset),
|
||||||
1 << SharedFunctionInfo::kStrictModeBitWithinByte);
|
1 << SharedFunctionInfo::kStrictModeBitWithinByte);
|
||||||
__ j(not_equal, &push_receiver);
|
__ j(not_equal, &push_receiver);
|
||||||
|
|
||||||
Factory* factory = masm->isolate()->factory();
|
Factory* factory = masm->isolate()->factory();
|
||||||
|
|
||||||
// Do not transform the receiver for natives (shared already in ecx).
|
// Do not transform the receiver for natives (shared already in ecx).
|
||||||
__ test_b(FieldOperand(ecx, SharedFunctionInfo::kNativeByteOffset),
|
__ test_b(FieldOperand(ecx, SharedFunctionInfo::kNativeByteOffset),
|
||||||
1 << SharedFunctionInfo::kNativeBitWithinByte);
|
1 << SharedFunctionInfo::kNativeBitWithinByte);
|
||||||
__ j(not_equal, &push_receiver);
|
__ j(not_equal, &push_receiver);
|
||||||
|
|
||||||
// Compute the receiver in non-strict mode.
|
// Compute the receiver in non-strict mode.
|
||||||
// Call ToObject on the receiver if it is not an object, or use the
|
// Call ToObject on the receiver if it is not an object, or use the
|
||||||
// global object if it is null or undefined.
|
// global object if it is null or undefined.
|
||||||
__ JumpIfSmi(ebx, &call_to_object);
|
__ JumpIfSmi(ebx, &call_to_object);
|
||||||
__ cmp(ebx, factory->null_value());
|
__ cmp(ebx, factory->null_value());
|
||||||
__ j(equal, &use_global_receiver);
|
__ j(equal, &use_global_receiver);
|
||||||
__ cmp(ebx, factory->undefined_value());
|
__ cmp(ebx, factory->undefined_value());
|
||||||
__ j(equal, &use_global_receiver);
|
__ j(equal, &use_global_receiver);
|
||||||
STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE);
|
STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE);
|
||||||
__ CmpObjectType(ebx, FIRST_SPEC_OBJECT_TYPE, ecx);
|
__ CmpObjectType(ebx, FIRST_SPEC_OBJECT_TYPE, ecx);
|
||||||
__ j(above_equal, &push_receiver);
|
__ j(above_equal, &push_receiver);
|
||||||
|
|
||||||
__ bind(&call_to_object);
|
__ bind(&call_to_object);
|
||||||
__ push(ebx);
|
__ push(ebx);
|
||||||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
|
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
|
||||||
__ mov(ebx, Operand(eax));
|
__ mov(ebx, Operand(eax));
|
||||||
__ jmp(&push_receiver);
|
__ jmp(&push_receiver);
|
||||||
|
|
||||||
// Use the current global receiver object as the receiver.
|
// Use the current global receiver object as the receiver.
|
||||||
__ bind(&use_global_receiver);
|
__ bind(&use_global_receiver);
|
||||||
const int kGlobalOffset =
|
const int kGlobalOffset =
|
||||||
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize;
|
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize;
|
||||||
__ mov(ebx, FieldOperand(esi, kGlobalOffset));
|
__ mov(ebx, FieldOperand(esi, kGlobalOffset));
|
||||||
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalContextOffset));
|
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalContextOffset));
|
||||||
__ mov(ebx, FieldOperand(ebx, kGlobalOffset));
|
__ mov(ebx, FieldOperand(ebx, kGlobalOffset));
|
||||||
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset));
|
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset));
|
||||||
|
|
||||||
// Push the receiver.
|
// Push the receiver.
|
||||||
__ bind(&push_receiver);
|
__ bind(&push_receiver);
|
||||||
__ push(ebx);
|
__ push(ebx);
|
||||||
|
|
||||||
// Copy all arguments from the array to the stack.
|
// Copy all arguments from the array to the stack.
|
||||||
Label entry, loop;
|
Label entry, loop;
|
||||||
__ mov(eax, Operand(ebp, kIndexOffset));
|
__ mov(eax, Operand(ebp, kIndexOffset));
|
||||||
__ jmp(&entry);
|
__ jmp(&entry);
|
||||||
__ bind(&loop);
|
__ bind(&loop);
|
||||||
__ mov(edx, Operand(ebp, kArgumentsOffset)); // load arguments
|
__ mov(edx, Operand(ebp, kArgumentsOffset)); // load arguments
|
||||||
|
|
||||||
// Use inline caching to speed up access to arguments.
|
// Use inline caching to speed up access to arguments.
|
||||||
Handle<Code> ic = masm->isolate()->builtins()->KeyedLoadIC_Initialize();
|
Handle<Code> ic = masm->isolate()->builtins()->KeyedLoadIC_Initialize();
|
||||||
__ call(ic, RelocInfo::CODE_TARGET);
|
__ call(ic, RelocInfo::CODE_TARGET);
|
||||||
// It is important that we do not have a test instruction after the
|
// It is important that we do not have a test instruction after the
|
||||||
// call. A test instruction after the call is used to indicate that
|
// call. A test instruction after the call is used to indicate that
|
||||||
// we have generated an inline version of the keyed load. In this
|
// we have generated an inline version of the keyed load. In this
|
||||||
// case, we know that we are not generating a test instruction next.
|
// case, we know that we are not generating a test instruction next.
|
||||||
|
|
||||||
// Push the nth argument.
|
// Push the nth argument.
|
||||||
__ push(eax);
|
__ push(eax);
|
||||||
|
|
||||||
// Update the index on the stack and in register eax.
|
// Update the index on the stack and in register eax.
|
||||||
__ mov(eax, Operand(ebp, kIndexOffset));
|
__ mov(eax, Operand(ebp, kIndexOffset));
|
||||||
__ add(Operand(eax), Immediate(1 << kSmiTagSize));
|
__ add(Operand(eax), Immediate(1 << kSmiTagSize));
|
||||||
__ mov(Operand(ebp, kIndexOffset), eax);
|
__ mov(Operand(ebp, kIndexOffset), eax);
|
||||||
|
|
||||||
__ bind(&entry);
|
__ bind(&entry);
|
||||||
__ cmp(eax, Operand(ebp, kLimitOffset));
|
__ cmp(eax, Operand(ebp, kLimitOffset));
|
||||||
__ j(not_equal, &loop);
|
__ j(not_equal, &loop);
|
||||||
|
|
||||||
// Invoke the function.
|
// Invoke the function.
|
||||||
Label call_proxy;
|
Label call_proxy;
|
||||||
ParameterCount actual(eax);
|
ParameterCount actual(eax);
|
||||||
__ SmiUntag(eax);
|
__ SmiUntag(eax);
|
||||||
__ mov(edi, Operand(ebp, kFunctionOffset));
|
__ mov(edi, Operand(ebp, kFunctionOffset));
|
||||||
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx);
|
||||||
__ j(not_equal, &call_proxy);
|
__ j(not_equal, &call_proxy);
|
||||||
__ InvokeFunction(edi, actual, CALL_FUNCTION,
|
__ InvokeFunction(edi, actual, CALL_FUNCTION,
|
||||||
NullCallWrapper(), CALL_AS_METHOD);
|
NullCallWrapper(), CALL_AS_METHOD);
|
||||||
|
|
||||||
frame_scope.GenerateLeaveFrame();
|
frame_scope.GenerateLeaveFrame();
|
||||||
__ ret(3 * kPointerSize); // remove this, receiver, and arguments
|
__ ret(3 * kPointerSize); // remove this, receiver, and arguments
|
||||||
|
|
||||||
// Invoke the function proxy.
|
// Invoke the function proxy.
|
||||||
__ bind(&call_proxy);
|
__ bind(&call_proxy);
|
||||||
__ push(edi); // add function proxy as last argument
|
__ push(edi); // add function proxy as last argument
|
||||||
__ inc(eax);
|
__ inc(eax);
|
||||||
__ Set(ebx, Immediate(0));
|
__ Set(ebx, Immediate(0));
|
||||||
__ SetCallKind(ecx, CALL_AS_METHOD);
|
__ SetCallKind(ecx, CALL_AS_METHOD);
|
||||||
__ GetBuiltinEntry(edx, Builtins::CALL_FUNCTION_PROXY);
|
__ GetBuiltinEntry(edx, Builtins::CALL_FUNCTION_PROXY);
|
||||||
__ call(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
__ call(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
||||||
RelocInfo::CODE_TARGET);
|
RelocInfo::CODE_TARGET);
|
||||||
|
|
||||||
// Leave internal frame.
|
// Leave internal frame.
|
||||||
}
|
}
|
||||||
__ ret(3 * kPointerSize); // remove this, receiver, and arguments
|
__ ret(3 * kPointerSize); // remove this, receiver, and arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
src/proxy.js
23
src/proxy.js
@ -41,14 +41,20 @@ $Proxy.createFunction = function(handler, callTrap, constructTrap) {
|
|||||||
throw MakeTypeError("handler_non_object", ["create"])
|
throw MakeTypeError("handler_non_object", ["create"])
|
||||||
if (!IS_SPEC_FUNCTION(callTrap))
|
if (!IS_SPEC_FUNCTION(callTrap))
|
||||||
throw MakeTypeError("trap_function_expected", ["createFunction", "call"])
|
throw MakeTypeError("trap_function_expected", ["createFunction", "call"])
|
||||||
|
var construct
|
||||||
if (IS_UNDEFINED(constructTrap)) {
|
if (IS_UNDEFINED(constructTrap)) {
|
||||||
constructTrap = callTrap
|
construct = DerivedConstructTrap(callTrap)
|
||||||
} else if (!IS_SPEC_FUNCTION(constructTrap)) {
|
} else if (IS_SPEC_FUNCTION(constructTrap)) {
|
||||||
|
construct = function() {
|
||||||
|
// Make sure the trap receives 'undefined' as this.
|
||||||
|
return $Function.prototype.apply.call(constructTrap, void 0, arguments)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
throw MakeTypeError("trap_function_expected",
|
throw MakeTypeError("trap_function_expected",
|
||||||
["createFunction", "construct"])
|
["createFunction", "construct"])
|
||||||
}
|
}
|
||||||
return %CreateJSFunctionProxy(
|
return %CreateJSFunctionProxy(
|
||||||
handler, callTrap, constructTrap, $Function.prototype)
|
handler, callTrap, construct, $Function.prototype)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -57,6 +63,17 @@ $Proxy.createFunction = function(handler, callTrap, constructTrap) {
|
|||||||
// Builtins
|
// Builtins
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function DerivedConstructTrap(callTrap) {
|
||||||
|
return function() {
|
||||||
|
var proto = this.prototype
|
||||||
|
if (!IS_SPEC_OBJECT(proto)) proto = $Object.prototype
|
||||||
|
var obj = new $Object()
|
||||||
|
obj.__proto__ = proto
|
||||||
|
var result = $Function.prototype.apply.call(callTrap, obj, arguments)
|
||||||
|
return IS_SPEC_OBJECT(result) ? result : obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function DelegateCallAndConstruct(callTrap, constructTrap) {
|
function DelegateCallAndConstruct(callTrap, constructTrap) {
|
||||||
return function() {
|
return function() {
|
||||||
return %Apply(%_IsConstructCall() ? constructTrap : callTrap,
|
return %Apply(%_IsConstructCall() ? constructTrap : callTrap,
|
||||||
|
@ -429,20 +429,10 @@ function CALL_FUNCTION_PROXY() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function CALL_FUNCTION_PROXY_AS_CONSTRUCTOR(proxy) {
|
function CALL_FUNCTION_PROXY_AS_CONSTRUCTOR() {
|
||||||
var arity = %_ArgumentsLength() - 1;
|
var proxy = this;
|
||||||
var trap = %GetConstructTrap(proxy);
|
var trap = %GetConstructTrap(proxy);
|
||||||
var receiver = void 0;
|
return %Apply(trap, this, arguments, 0, %_ArgumentsLength());
|
||||||
if (!IS_UNDEFINED(trap)) {
|
|
||||||
trap = %GetCallTrap(proxy);
|
|
||||||
var proto = proxy.prototype;
|
|
||||||
if (!IS_SPEC_OBJECT(proto) && proto !== null) {
|
|
||||||
throw MakeTypeError("proto_object_or_null", [proto]);
|
|
||||||
}
|
|
||||||
receiver = new global.Object();
|
|
||||||
receiver.__proto__ = proto;
|
|
||||||
}
|
|
||||||
return %Apply(trap, this, arguments, 1, arity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1049,6 +1049,15 @@ function ProxyFix(obj) {
|
|||||||
var code = DelegateCallAndConstruct(callTrap, constructTrap);
|
var code = DelegateCallAndConstruct(callTrap, constructTrap);
|
||||||
%Fix(obj); // becomes a regular function
|
%Fix(obj); // becomes a regular function
|
||||||
%SetCode(obj, code);
|
%SetCode(obj, code);
|
||||||
|
// TODO(rossberg): What about length and other properties? Not specified.
|
||||||
|
// We just put in some half-reasonable defaults for now.
|
||||||
|
var prototype = new $Object();
|
||||||
|
$Object.defineProperty(prototype, "constructor",
|
||||||
|
{value: obj, writable: true, enumerable: false, configrable: true});
|
||||||
|
$Object.defineProperty(obj, "prototype",
|
||||||
|
{value: prototype, writable: true, enumerable: false, configrable: false})
|
||||||
|
$Object.defineProperty(obj, "length",
|
||||||
|
{value: 0, writable: true, enumerable: false, configrable: false});
|
||||||
} else {
|
} else {
|
||||||
%Fix(obj);
|
%Fix(obj);
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,12 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
// -- rdi: constructor function
|
// -- rdi: constructor function
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
|
|
||||||
Label non_function_call;
|
Label slow, non_function_call;
|
||||||
// Check that function is not a smi.
|
// Check that function is not a smi.
|
||||||
__ JumpIfSmi(rdi, &non_function_call);
|
__ JumpIfSmi(rdi, &non_function_call);
|
||||||
// Check that function is a JSFunction.
|
// Check that function is a JSFunction.
|
||||||
__ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx);
|
__ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx);
|
||||||
__ j(not_equal, &non_function_call);
|
__ j(not_equal, &slow);
|
||||||
|
|
||||||
// Jump to the function-specific construct stub.
|
// Jump to the function-specific construct stub.
|
||||||
__ movq(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
|
__ movq(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
|
||||||
@ -94,10 +94,19 @@ void Builtins::Generate_JSConstructCall(MacroAssembler* masm) {
|
|||||||
|
|
||||||
// rdi: called object
|
// rdi: called object
|
||||||
// rax: number of arguments
|
// rax: number of arguments
|
||||||
|
// rcx: object map
|
||||||
|
Label do_call;
|
||||||
|
__ bind(&slow);
|
||||||
|
__ CmpInstanceType(rcx, JS_FUNCTION_PROXY_TYPE);
|
||||||
|
__ j(not_equal, &non_function_call);
|
||||||
|
__ GetBuiltinEntry(rdx, Builtins::CALL_FUNCTION_PROXY_AS_CONSTRUCTOR);
|
||||||
|
__ jmp(&do_call);
|
||||||
|
|
||||||
__ bind(&non_function_call);
|
__ bind(&non_function_call);
|
||||||
|
__ GetBuiltinEntry(rdx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
||||||
|
__ bind(&do_call);
|
||||||
// Set expected number of arguments to zero (not changing rax).
|
// Set expected number of arguments to zero (not changing rax).
|
||||||
__ Set(rbx, 0);
|
__ Set(rbx, 0);
|
||||||
__ GetBuiltinEntry(rdx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR);
|
|
||||||
__ SetCallKind(rcx, CALL_AS_METHOD);
|
__ SetCallKind(rcx, CALL_AS_METHOD);
|
||||||
__ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
__ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(),
|
||||||
RelocInfo::CODE_TARGET);
|
RelocInfo::CODE_TARGET);
|
||||||
@ -717,7 +726,7 @@ void Builtins::Generate_FunctionCall(MacroAssembler* masm) {
|
|||||||
__ push(rbx);
|
__ push(rbx);
|
||||||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
|
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
|
||||||
__ movq(rbx, rax);
|
__ movq(rbx, rax);
|
||||||
__ Set(rdx, 0); // indicate regular JS_FUNCTION
|
__ Set(rdx, 0); // indicate regular JS_FUNCTION
|
||||||
|
|
||||||
__ pop(rax);
|
__ pop(rax);
|
||||||
__ SmiToInteger32(rax, rax);
|
__ SmiToInteger32(rax, rax);
|
||||||
|
@ -30,8 +30,6 @@
|
|||||||
|
|
||||||
// TODO(rossberg): for-in for proxies not implemented.
|
// TODO(rossberg): for-in for proxies not implemented.
|
||||||
// TODO(rossberg): inheritance from proxies not implemented.
|
// TODO(rossberg): inheritance from proxies not implemented.
|
||||||
// TODO(rossberg): function proxies as constructors not implemented.
|
|
||||||
|
|
||||||
|
|
||||||
// Helper.
|
// Helper.
|
||||||
|
|
||||||
@ -1586,23 +1584,30 @@ TestIsEnumerableThrow(Proxy.create({
|
|||||||
// Calling (call, Function.prototype.call, Function.prototype.apply,
|
// Calling (call, Function.prototype.call, Function.prototype.apply,
|
||||||
// Function.prototype.bind).
|
// Function.prototype.bind).
|
||||||
|
|
||||||
var global = this
|
var global_object = this
|
||||||
var receiver
|
var receiver
|
||||||
|
|
||||||
|
function CreateFrozen(handler, callTrap, constructTrap) {
|
||||||
|
if (handler.fix === undefined) handler.fix = function() { return {} }
|
||||||
|
var f = Proxy.createFunction(handler, callTrap, constructTrap)
|
||||||
|
Object.freeze(f)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
function TestCall(isStrict, callTrap) {
|
function TestCall(isStrict, callTrap) {
|
||||||
assertEquals(42, callTrap(5, 37))
|
assertEquals(42, callTrap(5, 37))
|
||||||
// TODO(rossberg): unrelated bug: this does not succeed for optimized code.
|
// TODO(rossberg): unrelated bug: this does not succeed for optimized code.
|
||||||
// assertEquals(isStrict ? undefined : global, receiver)
|
// assertEquals(isStrict ? undefined : global_object, receiver)
|
||||||
|
|
||||||
var f = Proxy.createFunction({fix: function() { return {} }}, callTrap)
|
var f = Proxy.createFunction({}, callTrap)
|
||||||
receiver = 333
|
receiver = 333
|
||||||
assertEquals(42, f(11, 31))
|
assertEquals(42, f(11, 31))
|
||||||
assertEquals(isStrict ? undefined : global, receiver)
|
assertEquals(isStrict ? undefined : global_object, receiver)
|
||||||
var o = {}
|
var o = {}
|
||||||
assertEquals(42, Function.prototype.call.call(f, o, 20, 22))
|
assertEquals(42, Function.prototype.call.call(f, o, 20, 22))
|
||||||
assertEquals(o, receiver)
|
assertEquals(o, receiver)
|
||||||
assertEquals(43, Function.prototype.call.call(f, null, 20, 23))
|
assertEquals(43, Function.prototype.call.call(f, null, 20, 23))
|
||||||
assertEquals(isStrict ? null : global, receiver)
|
assertEquals(isStrict ? null : global_object, receiver)
|
||||||
assertEquals(44, Function.prototype.call.call(f, 2, 21, 23))
|
assertEquals(44, Function.prototype.call.call(f, 2, 21, 23))
|
||||||
assertEquals(2, receiver.valueOf())
|
assertEquals(2, receiver.valueOf())
|
||||||
receiver = 333
|
receiver = 333
|
||||||
@ -1616,7 +1621,7 @@ function TestCall(isStrict, callTrap) {
|
|||||||
assertEquals(32, Function.prototype.apply.call(ff, {}, [20]))
|
assertEquals(32, Function.prototype.apply.call(ff, {}, [20]))
|
||||||
assertEquals(o, receiver)
|
assertEquals(o, receiver)
|
||||||
|
|
||||||
Object.freeze(f)
|
var f = CreateFrozen({}, callTrap)
|
||||||
receiver = 333
|
receiver = 333
|
||||||
assertEquals(42, f(11, 31))
|
assertEquals(42, f(11, 31))
|
||||||
// TODO(rossberg): unrelated bug: this does not succeed for optimized code.
|
// TODO(rossberg): unrelated bug: this does not succeed for optimized code.
|
||||||
@ -1653,21 +1658,18 @@ TestCall(true, Proxy.createFunction({}, function(x, y) {
|
|||||||
receiver = this; return x + y
|
receiver = this; return x + y
|
||||||
}))
|
}))
|
||||||
|
|
||||||
var p = Proxy.createFunction({fix: function() {return {}}}, function(x, y) {
|
TestCall(false, CreateFrozen({}, function(x, y) {
|
||||||
receiver = this; return x + y
|
receiver = this; return x + y
|
||||||
})
|
}))
|
||||||
TestCall(false, p)
|
|
||||||
Object.freeze(p)
|
|
||||||
TestCall(false, p)
|
|
||||||
|
|
||||||
|
|
||||||
function TestCallThrow(callTrap) {
|
function TestCallThrow(callTrap) {
|
||||||
var f = Proxy.createFunction({fix: function() {return {}}}, callTrap)
|
var f = Proxy.createFunction({}, callTrap)
|
||||||
assertThrows(function(){ f(11) }, "myexn")
|
assertThrows(function(){ f(11) }, "myexn")
|
||||||
assertThrows(function(){ Function.prototype.call.call(f, {}, 2) }, "myexn")
|
assertThrows(function(){ Function.prototype.call.call(f, {}, 2) }, "myexn")
|
||||||
assertThrows(function(){ Function.prototype.apply.call(f, {}, [1]) }, "myexn")
|
assertThrows(function(){ Function.prototype.apply.call(f, {}, [1]) }, "myexn")
|
||||||
|
|
||||||
Object.freeze(f)
|
var f = CreateFrozen({}, callTrap)
|
||||||
assertThrows(function(){ f(11) }, "myexn")
|
assertThrows(function(){ f(11) }, "myexn")
|
||||||
assertThrows(function(){ Function.prototype.call.call(f, {}, 2) }, "myexn")
|
assertThrows(function(){ Function.prototype.call.call(f, {}, 2) }, "myexn")
|
||||||
assertThrows(function(){ Function.prototype.apply.call(f, {}, [1]) }, "myexn")
|
assertThrows(function(){ Function.prototype.apply.call(f, {}, [1]) }, "myexn")
|
||||||
@ -1675,8 +1677,139 @@ function TestCallThrow(callTrap) {
|
|||||||
|
|
||||||
TestCallThrow(function() { throw "myexn" })
|
TestCallThrow(function() { throw "myexn" })
|
||||||
TestCallThrow(Proxy.createFunction({}, function() { throw "myexn" }))
|
TestCallThrow(Proxy.createFunction({}, function() { throw "myexn" }))
|
||||||
|
TestCallThrow(CreateFrozen({}, function() { throw "myexn" }))
|
||||||
|
|
||||||
var p = Proxy.createFunction(
|
|
||||||
{fix: function() {return {}}}, function() { throw "myexn" })
|
|
||||||
Object.freeze(p)
|
// Construction (new).
|
||||||
TestCallThrow(p)
|
|
||||||
|
var prototype = {}
|
||||||
|
var receiver
|
||||||
|
|
||||||
|
var handlerWithPrototype = {
|
||||||
|
fix: function() { return {prototype: prototype} },
|
||||||
|
get: function(r, n) { assertEquals("prototype", n); return prototype }
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlerSansPrototype = {
|
||||||
|
fix: function() { return {} },
|
||||||
|
get: function(r, n) { assertEquals("prototype", n); return undefined }
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReturnUndef(x, y) { "use strict"; receiver = this; this.sum = x + y }
|
||||||
|
function ReturnThis(x, y) { "use strict"; receiver = this; this.sum = x + y; return this }
|
||||||
|
function ReturnNew(x, y) { "use strict"; receiver = this; return {sum: x + y} }
|
||||||
|
function ReturnNewWithProto(x, y) {
|
||||||
|
"use strict";
|
||||||
|
receiver = this;
|
||||||
|
var result = Object.create(prototype)
|
||||||
|
result.sum = x + y
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestConstruct(proto, constructTrap) {
|
||||||
|
TestConstruct2(proto, constructTrap, handlerWithPrototype)
|
||||||
|
TestConstruct2(proto, constructTrap, handlerSansPrototype)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestConstruct2(proto, constructTrap, handler) {
|
||||||
|
var f = Proxy.createFunction(handler, function() {}, constructTrap)
|
||||||
|
var o = new f(11, 31)
|
||||||
|
// TODO(rossberg): doesn't hold, due to unrelated bug.
|
||||||
|
// assertEquals(undefined, receiver)
|
||||||
|
assertEquals(42, o.sum)
|
||||||
|
assertSame(proto, Object.getPrototypeOf(o))
|
||||||
|
|
||||||
|
var f = CreateFrozen(handler, function() {}, constructTrap)
|
||||||
|
var o = new f(11, 32)
|
||||||
|
// TODO(rossberg): doesn't hold, due to unrelated bug.
|
||||||
|
// assertEquals(undefined, receiver)
|
||||||
|
assertEquals(43, o.sum)
|
||||||
|
assertSame(proto, Object.getPrototypeOf(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
TestConstruct(Object.prototype, ReturnNew)
|
||||||
|
TestConstruct(prototype, ReturnNewWithProto)
|
||||||
|
|
||||||
|
TestConstruct(Object.prototype, Proxy.createFunction({}, ReturnNew))
|
||||||
|
TestConstruct(prototype, Proxy.createFunction({}, ReturnNewWithProto))
|
||||||
|
|
||||||
|
TestConstruct(Object.prototype, CreateFrozen({}, ReturnNew))
|
||||||
|
TestConstruct(prototype, CreateFrozen({}, ReturnNewWithProto))
|
||||||
|
|
||||||
|
|
||||||
|
function TestConstructFromCall(proto, returnsThis, callTrap) {
|
||||||
|
TestConstructFromCall2(proto, returnsThis, callTrap, handlerWithPrototype)
|
||||||
|
TestConstructFromCall2(proto, returnsThis, callTrap, handlerSansPrototype)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestConstructFromCall2(proto, returnsThis, callTrap, handler) {
|
||||||
|
var f = Proxy.createFunction(handler, callTrap)
|
||||||
|
var o = new f(11, 31)
|
||||||
|
if (returnsThis) assertEquals(o, receiver)
|
||||||
|
assertEquals(42, o.sum)
|
||||||
|
assertSame(proto, Object.getPrototypeOf(o))
|
||||||
|
|
||||||
|
var f = CreateFrozen(handler, callTrap)
|
||||||
|
var o = new f(11, 32)
|
||||||
|
if (returnsThis) assertEquals(o, receiver)
|
||||||
|
assertEquals(43, o.sum)
|
||||||
|
assertSame(proto, Object.getPrototypeOf(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
TestConstructFromCall(Object.prototype, true, ReturnUndef)
|
||||||
|
TestConstructFromCall(Object.prototype, true, ReturnThis)
|
||||||
|
TestConstructFromCall(Object.prototype, false, ReturnNew)
|
||||||
|
TestConstructFromCall(prototype, false, ReturnNewWithProto)
|
||||||
|
|
||||||
|
TestConstructFromCall(Object.prototype, true, Proxy.createFunction({}, ReturnUndef))
|
||||||
|
TestConstructFromCall(Object.prototype, true, Proxy.createFunction({}, ReturnThis))
|
||||||
|
TestConstructFromCall(Object.prototype, false, Proxy.createFunction({}, ReturnNew))
|
||||||
|
TestConstructFromCall(prototype, false, Proxy.createFunction({}, ReturnNewWithProto))
|
||||||
|
|
||||||
|
TestConstructFromCall(Object.prototype, true, CreateFrozen({}, ReturnUndef))
|
||||||
|
TestConstructFromCall(Object.prototype, true, CreateFrozen({}, ReturnThis))
|
||||||
|
TestConstructFromCall(Object.prototype, false, CreateFrozen({}, ReturnNew))
|
||||||
|
TestConstructFromCall(prototype, false, CreateFrozen({}, ReturnNewWithProto))
|
||||||
|
|
||||||
|
ReturnUndef.prototype = prototype
|
||||||
|
ReturnThis.prototype = prototype
|
||||||
|
ReturnNew.prototype = prototype
|
||||||
|
ReturnNewWithProto.prototype = prototype
|
||||||
|
|
||||||
|
TestConstructFromCall(prototype, true, ReturnUndef)
|
||||||
|
TestConstructFromCall(prototype, true, ReturnThis)
|
||||||
|
TestConstructFromCall(Object.prototype, false, ReturnNew)
|
||||||
|
TestConstructFromCall(prototype, false, ReturnNewWithProto)
|
||||||
|
|
||||||
|
TestConstructFromCall(Object.prototype, true, Proxy.createFunction({}, ReturnUndef))
|
||||||
|
TestConstructFromCall(Object.prototype, true, Proxy.createFunction({}, ReturnThis))
|
||||||
|
TestConstructFromCall(Object.prototype, false, Proxy.createFunction({}, ReturnNew))
|
||||||
|
TestConstructFromCall(prototype, false, Proxy.createFunction({}, ReturnNewWithProto))
|
||||||
|
|
||||||
|
TestConstructFromCall(prototype, true, Proxy.createFunction(handlerWithPrototype, ReturnUndef))
|
||||||
|
TestConstructFromCall(prototype, true, Proxy.createFunction(handlerWithPrototype, ReturnThis))
|
||||||
|
TestConstructFromCall(Object.prototype, false, Proxy.createFunction(handlerWithPrototype, ReturnNew))
|
||||||
|
TestConstructFromCall(prototype, false, Proxy.createFunction(handlerWithPrototype, ReturnNewWithProto))
|
||||||
|
|
||||||
|
TestConstructFromCall(prototype, true, CreateFrozen(handlerWithPrototype, ReturnUndef))
|
||||||
|
TestConstructFromCall(prototype, true, CreateFrozen(handlerWithPrototype, ReturnThis))
|
||||||
|
TestConstructFromCall(Object.prototype, false, CreateFrozen(handlerWithPrototype, ReturnNew))
|
||||||
|
TestConstructFromCall(prototype, false, CreateFrozen(handlerWithPrototype, ReturnNewWithProto))
|
||||||
|
|
||||||
|
|
||||||
|
function TestConstructThrow(trap) {
|
||||||
|
TestConstructThrow2(Proxy.createFunction({fix: function() {return {}}}, trap))
|
||||||
|
TestConstructThrow2(Proxy.createFunction({fix: function() {return {}}},
|
||||||
|
function() {}, trap))
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestConstructThrow2(f) {
|
||||||
|
assertThrows(function(){ new f(11) }, "myexn")
|
||||||
|
Object.freeze(f)
|
||||||
|
assertThrows(function(){ new f(11) }, "myexn")
|
||||||
|
}
|
||||||
|
|
||||||
|
TestConstructThrow(function() { throw "myexn" })
|
||||||
|
TestConstructThrow(Proxy.createFunction({}, function() { throw "myexn" }))
|
||||||
|
TestConstructThrow(CreateFrozen({}, function() { throw "myexn" }))
|
||||||
|
Loading…
Reference in New Issue
Block a user