From b8cdecb5170a232a919df88780f6d2a2e19244aa Mon Sep 17 00:00:00 2001 From: "ager@chromium.org" Date: Wed, 4 May 2011 10:03:49 +0000 Subject: [PATCH] Add Call method to the Object class in the API Patch by Peter Varga. BUG=v8:1336 TEST=cctest/test-api/CallAsFunction Review URL: http://codereview.chromium.org/6883045 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7773 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 8 +++ src/api.cc | 31 +++++++++ src/execution.cc | 24 +++++++ src/execution.h | 2 + test/cctest/test-api.cc | 135 +++++++++++++++++++++++++++++----------- 5 files changed, 163 insertions(+), 37 deletions(-) diff --git a/include/v8.h b/include/v8.h index 4dcbf285ea..78ee7e6a0b 100644 --- a/include/v8.h +++ b/include/v8.h @@ -1606,6 +1606,14 @@ class Object : public Value { V8EXPORT ExternalArrayType GetIndexedPropertiesExternalArrayDataType(); V8EXPORT int GetIndexedPropertiesExternalArrayDataLength(); + /** + * Call an Object as a function if a callback is set by the + * ObjectTemplate::SetCallAsFunctionHandler method. + */ + V8EXPORT Local CallAsFunction(Handle recv, + int argc, + Handle argv[]); + V8EXPORT static Local New(); static inline Object* Cast(Value* obj); private: diff --git a/src/api.cc b/src/api.cc index 792e4886f8..de3c1a5dd7 100644 --- a/src/api.cc +++ b/src/api.cc @@ -3255,6 +3255,37 @@ int v8::Object::GetIndexedPropertiesExternalArrayDataLength() { } +Local Object::CallAsFunction(v8::Handle recv, int argc, + v8::Handle argv[]) { + i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); + ON_BAILOUT(isolate, "v8::Object::CallAsFunction()", + return Local()); + LOG_API(isolate, "Object::CallAsFunction"); + ENTER_V8(isolate); + HandleScope scope; + i::Handle obj = Utils::OpenHandle(this); + i::Handle recv_obj = Utils::OpenHandle(*recv); + STATIC_ASSERT(sizeof(v8::Handle) == sizeof(i::Object**)); + i::Object*** args = reinterpret_cast(argv); + i::Handle fun = i::Handle(); + if (obj->IsJSFunction()) { + fun = i::Handle::cast(obj); + } else { + EXCEPTION_PREAMBLE(isolate); + i::Handle delegate = + i::Execution::TryGetFunctionDelegate(obj, &has_pending_exception); + EXCEPTION_BAILOUT_CHECK(isolate, Local()); + fun = i::Handle::cast(delegate); + recv_obj = obj; + } + EXCEPTION_PREAMBLE(isolate); + i::Handle returned = + i::Execution::Call(fun, recv_obj, argc, args, &has_pending_exception); + EXCEPTION_BAILOUT_CHECK(isolate, Local()); + return scope.Close(Utils::ToLocal(i::Handle::cast(returned))); +} + + Local Function::NewInstance() const { return NewInstance(0, NULL); } diff --git a/src/execution.cc b/src/execution.cc index eb26438fe8..850dec561f 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -234,6 +234,30 @@ Handle Execution::GetFunctionDelegate(Handle object) { } +Handle Execution::TryGetFunctionDelegate(Handle object, + bool* has_pending_exception) { + ASSERT(!object->IsJSFunction()); + Isolate* isolate = Isolate::Current(); + + // Objects created through the API can have an instance-call handler + // that should be used when calling the object as a function. + if (object->IsHeapObject() && + HeapObject::cast(*object)->map()->has_instance_call_handler()) { + return Handle( + isolate->global_context()->call_as_function_delegate()); + } + + // If the Object doesn't have an instance-call handler we should + // throw a non-callable exception. + i::Handle error_obj = isolate->factory()->NewTypeError( + "called_non_callable", i::HandleVector(&object, 1)); + isolate->Throw(*error_obj); + *has_pending_exception = true; + + return isolate->factory()->undefined_value(); +} + + Handle Execution::GetConstructorDelegate(Handle object) { ASSERT(!object->IsJSFunction()); Isolate* isolate = Isolate::Current(); diff --git a/src/execution.h b/src/execution.h index d4b80d274e..e89d6ba397 100644 --- a/src/execution.h +++ b/src/execution.h @@ -138,6 +138,8 @@ class Execution : public AllStatic { // Get a function delegate (or undefined) for the given non-function // object. Used for support calling objects as functions. static Handle GetFunctionDelegate(Handle object); + static Handle TryGetFunctionDelegate(Handle object, + bool* has_pending_exception); // Get a function delegate (or undefined) for the given non-function // object. Used for support calling objects as constructors. diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index e2a7fb1856..c6affe53ce 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -6962,50 +6962,111 @@ THREADED_TEST(CallAsFunction) { v8::HandleScope scope; LocalContext context; - Local t = v8::FunctionTemplate::New(); - Local instance_template = t->InstanceTemplate(); - instance_template->SetCallAsFunctionHandler(call_as_function); - Local instance = t->GetFunction()->NewInstance(); - context->Global()->Set(v8_str("obj"), instance); - v8::TryCatch try_catch; - Local value; - CHECK(!try_catch.HasCaught()); + { Local t = v8::FunctionTemplate::New(); + Local instance_template = t->InstanceTemplate(); + instance_template->SetCallAsFunctionHandler(call_as_function); + Local instance = t->GetFunction()->NewInstance(); + context->Global()->Set(v8_str("obj"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); - value = CompileRun("obj(42)"); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(42, value->Int32Value()); + value = CompileRun("obj(42)"); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(42, value->Int32Value()); - value = CompileRun("(function(o){return o(49)})(obj)"); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(49, value->Int32Value()); + value = CompileRun("(function(o){return o(49)})(obj)"); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(49, value->Int32Value()); - // test special case of call as function - value = CompileRun("[obj]['0'](45)"); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(45, value->Int32Value()); + // test special case of call as function + value = CompileRun("[obj]['0'](45)"); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(45, value->Int32Value()); - value = CompileRun("obj.call = Function.prototype.call;" - "obj.call(null, 87)"); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(87, value->Int32Value()); + value = CompileRun("obj.call = Function.prototype.call;" + "obj.call(null, 87)"); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(87, value->Int32Value()); - // Regression tests for bug #1116356: Calling call through call/apply - // must work for non-function receivers. - const char* apply_99 = "Function.prototype.call.apply(obj, [this, 99])"; - value = CompileRun(apply_99); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(99, value->Int32Value()); + // Regression tests for bug #1116356: Calling call through call/apply + // must work for non-function receivers. + const char* apply_99 = "Function.prototype.call.apply(obj, [this, 99])"; + value = CompileRun(apply_99); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(99, value->Int32Value()); - const char* call_17 = "Function.prototype.call.call(obj, this, 17)"; - value = CompileRun(call_17); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(17, value->Int32Value()); + const char* call_17 = "Function.prototype.call.call(obj, this, 17)"; + value = CompileRun(call_17); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(17, value->Int32Value()); - // Check that the call-as-function handler can be called through - // new. - value = CompileRun("new obj(43)"); - CHECK(!try_catch.HasCaught()); - CHECK_EQ(-43, value->Int32Value()); + // Check that the call-as-function handler can be called through + // new. + value = CompileRun("new obj(43)"); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(-43, value->Int32Value()); + + // Check that the call-as-function handler can be called through + // the API. + v8::Handle args[] = { v8_num(28) }; + value = instance->CallAsFunction(instance, 1, args); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(28, value->Int32Value()); + } + + { Local t = v8::FunctionTemplate::New(); + Local instance_template = t->InstanceTemplate(); + Local instance = t->GetFunction()->NewInstance(); + context->Global()->Set(v8_str("obj2"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + // Call an object without call-as-function handler through the JS + value = CompileRun("obj2(28)"); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value1(try_catch.Exception()); + CHECK_EQ(*exception_value1, + "TypeError: Property 'obj2' of object " + "# is not a function"); + try_catch.Reset(); + + // Call an object without call-as-function handler through the API + value = CompileRun("obj2(28)"); + v8::Handle args[] = { v8_num(28) }; + value = instance->CallAsFunction(instance, 1, args); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value2(try_catch.Exception()); + CHECK_EQ(*exception_value2, "TypeError: [object Object] is not a function"); + try_catch.Reset(); + } + + { Local t = v8::FunctionTemplate::New(); + Local instance_template = t->InstanceTemplate(); + instance_template->SetCallAsFunctionHandler(ThrowValue); + Local instance = t->GetFunction()->NewInstance(); + context->Global()->Set(v8_str("obj3"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + // Catch the exception which is thrown by call-as-function handler + value = CompileRun("obj3(22)"); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value1(try_catch.Exception()); + CHECK_EQ(*exception_value1, "22"); + try_catch.Reset(); + + v8::Handle args[] = { v8_num(23) }; + value = instance->CallAsFunction(instance, 1, args); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value2(try_catch.Exception()); + CHECK_EQ(*exception_value2, "23"); + try_catch.Reset(); + } }