From 3d6d4249878f7960eac4c9c94e0f2529f9a58c4a Mon Sep 17 00:00:00 2001 From: "ager@chromium.org" Date: Fri, 6 May 2011 11:07:52 +0000 Subject: [PATCH 10/16] Implement CallAsConstructor method for Object in the API Patch by Peter Varga. BUG=v8:1348 TEST=cctest/test-api/ConstructorForObject Review URL: http://codereview.chromium.org/6902108 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@7803 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 8 ++ src/api.cc | 41 +++++++++- src/execution.cc | 28 +++++++ src/execution.h | 2 + test/cctest/test-api.cc | 205 +++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 276 insertions(+), 8 deletions(-) diff --git a/include/v8.h b/include/v8.h index 8a8e1cd..84462b5 100644 --- a/include/v8.h +++ b/include/v8.h @@ -1765,6 +1765,14 @@ class Object : public Value { int argc, Handle argv[]); + /** + * Call an Object as a consturctor if a callback is set by the + * ObjectTemplate::SetCallAsFunctionHandler method. + * Note: This method behaves like the Function::NewInstance method. + */ + V8EXPORT Local CallAsConstructor(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 e412e51..1a585d6 100644 --- a/src/api.cc +++ b/src/api.cc @@ -3266,7 +3266,7 @@ Local Object::CallAsFunction(v8::Handle recv, int argc, return Local()); LOG_API(isolate, "Object::CallAsFunction"); ENTER_V8(isolate); - HandleScope scope; + i::HandleScope scope(isolate); i::Handle obj = Utils::OpenHandle(this); i::Handle recv_obj = Utils::OpenHandle(*recv); STATIC_ASSERT(sizeof(v8::Handle) == sizeof(i::Object**)); @@ -3286,7 +3286,44 @@ Local Object::CallAsFunction(v8::Handle recv, int argc, i::Handle returned = i::Execution::Call(fun, recv_obj, argc, args, &has_pending_exception); EXCEPTION_BAILOUT_CHECK(isolate, Local()); - return scope.Close(Utils::ToLocal(returned)); + return Utils::ToLocal(scope.CloseAndEscape(returned)); +} + + +Local Object::CallAsConstructor(int argc, + v8::Handle argv[]) { + i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); + ON_BAILOUT(isolate, "v8::Object::CallAsConstructor()", + return Local()); + LOG_API(isolate, "Object::CallAsConstructor"); + ENTER_V8(isolate); + i::HandleScope scope(isolate); + i::Handle obj = Utils::OpenHandle(this); + STATIC_ASSERT(sizeof(v8::Handle) == sizeof(i::Object**)); + i::Object*** args = reinterpret_cast(argv); + if (obj->IsJSFunction()) { + i::Handle fun = i::Handle::cast(obj); + EXCEPTION_PREAMBLE(isolate); + i::Handle returned = + i::Execution::New(fun, argc, args, &has_pending_exception); + EXCEPTION_BAILOUT_CHECK(isolate, Local()); + return Utils::ToLocal(scope.CloseAndEscape( + i::Handle::cast(returned))); + } + EXCEPTION_PREAMBLE(isolate); + i::Handle delegate = + i::Execution::TryGetConstructorDelegate(obj, &has_pending_exception); + EXCEPTION_BAILOUT_CHECK(isolate, Local()); + if (!delegate->IsUndefined()) { + i::Handle fun = i::Handle::cast(delegate); + EXCEPTION_PREAMBLE(isolate); + i::Handle returned = + i::Execution::Call(fun, obj, argc, args, &has_pending_exception); + EXCEPTION_BAILOUT_CHECK(isolate, Local()); + ASSERT(!delegate->IsUndefined()); + return Utils::ToLocal(scope.CloseAndEscape(returned)); + } + return Local(); } diff --git a/src/execution.cc b/src/execution.cc index 894d741..afb352c 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -297,6 +297,34 @@ Handle Execution::GetConstructorDelegate(Handle object) { } +Handle Execution::TryGetConstructorDelegate( + Handle object, + bool* has_pending_exception) { + ASSERT(!object->IsJSFunction()); + Isolate* isolate = Isolate::Current(); + + // If you return a function from here, it will be called when an + // attempt is made to call the given object as a constructor. + + // 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_constructor_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(); +} + + bool StackGuard::IsStackOverflow() { ExecutionAccess access(isolate_); return (thread_local_.jslimit_ != kInterruptLimit && diff --git a/src/execution.h b/src/execution.h index 0a0be51..ec2a195 100644 --- a/src/execution.h +++ b/src/execution.h @@ -150,6 +150,8 @@ class Execution : public AllStatic { // Get a function delegate (or undefined) for the given non-function // object. Used for support calling objects as constructors. static Handle GetConstructorDelegate(Handle object); + static Handle TryGetConstructorDelegate(Handle object, + bool* has_pending_exception); }; diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 693d51e..1334f63 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -6746,6 +6746,200 @@ THREADED_TEST(Constructor) { CHECK(value->BooleanValue()); } + +static Handle ConstructorCallback(const Arguments& args) { + ApiTestFuzzer::Fuzz(); + Local This; + + if (args.IsConstructCall()) { + Local Holder = args.Holder(); + This = Object::New(); + Local proto = Holder->GetPrototype(); + if (proto->IsObject()) { + This->SetPrototype(proto); + } + } else { + This = args.This(); + } + + This->Set(v8_str("a"), args[0]); + return This; +} + + +static Handle FakeConstructorCallback(const Arguments& args) { + ApiTestFuzzer::Fuzz(); + return args[0]; +} + + +THREADED_TEST(ConstructorForObject) { + v8::HandleScope handle_scope; + LocalContext context; + + { Local instance_template = ObjectTemplate::New(); + instance_template->SetCallAsFunctionHandler(ConstructorCallback); + Local instance = instance_template->NewInstance(); + context->Global()->Set(v8_str("obj"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + // Call the Object's constructor with a 32-bit signed integer. + value = CompileRun("(function() { var o = new obj(28); return o.a; })()"); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsInt32()); + CHECK_EQ(28, value->Int32Value()); + + Local args1[] = { v8_num(28) }; + Local value_obj1 = instance->CallAsConstructor(1, args1); + CHECK(value_obj1->IsObject()); + Local object1 = Local::Cast(value_obj1); + value = object1->Get(v8_str("a")); + CHECK(value->IsInt32()); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(28, value->Int32Value()); + + // Call the Object's constructor with a String. + value = CompileRun( + "(function() { var o = new obj('tipli'); return o.a; })()"); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsString()); + String::AsciiValue string_value1(value->ToString()); + CHECK_EQ("tipli", *string_value1); + + Local args2[] = { v8_str("tipli") }; + Local value_obj2 = instance->CallAsConstructor(1, args2); + CHECK(value_obj2->IsObject()); + Local object2 = Local::Cast(value_obj2); + value = object2->Get(v8_str("a")); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsString()); + String::AsciiValue string_value2(value->ToString()); + CHECK_EQ("tipli", *string_value2); + + // Call the Object's constructor with a Boolean. + value = CompileRun("(function() { var o = new obj(true); return o.a; })()"); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsBoolean()); + CHECK_EQ(true, value->BooleanValue()); + + Handle args3[] = { v8::Boolean::New(true) }; + Local value_obj3 = instance->CallAsConstructor(1, args3); + CHECK(value_obj3->IsObject()); + Local object3 = Local::Cast(value_obj3); + value = object3->Get(v8_str("a")); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsBoolean()); + CHECK_EQ(true, value->BooleanValue()); + + // Call the Object's constructor with undefined. + Handle args4[] = { v8::Undefined() }; + Local value_obj4 = instance->CallAsConstructor(1, args4); + CHECK(value_obj4->IsObject()); + Local object4 = Local::Cast(value_obj4); + value = object4->Get(v8_str("a")); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsUndefined()); + + // Call the Object's constructor with null. + Handle args5[] = { v8::Null() }; + Local value_obj5 = instance->CallAsConstructor(1, args5); + CHECK(value_obj5->IsObject()); + Local object5 = Local::Cast(value_obj5); + value = object5->Get(v8_str("a")); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsNull()); + } + + // Check exception handling when there is no constructor set for the Object. + { Local instance_template = ObjectTemplate::New(); + Local instance = instance_template->NewInstance(); + context->Global()->Set(v8_str("obj2"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + value = CompileRun("new obj2(28)"); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value1(try_catch.Exception()); + CHECK_EQ("TypeError: object is not a function", *exception_value1); + try_catch.Reset(); + + Local args[] = { v8_num(29) }; + value = instance->CallAsConstructor(1, args); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value2(try_catch.Exception()); + CHECK_EQ("TypeError: # is not a function", *exception_value2); + try_catch.Reset(); + } + + // Check the case when constructor throws exception. + { Local instance_template = ObjectTemplate::New(); + instance_template->SetCallAsFunctionHandler(ThrowValue); + Local instance = instance_template->NewInstance(); + context->Global()->Set(v8_str("obj3"), instance); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + value = CompileRun("new obj3(22)"); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value1(try_catch.Exception()); + CHECK_EQ("22", *exception_value1); + try_catch.Reset(); + + Local args[] = { v8_num(23) }; + value = instance->CallAsConstructor(1, args); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value2(try_catch.Exception()); + CHECK_EQ("23", *exception_value2); + try_catch.Reset(); + } + + // Check whether constructor returns with an object or non-object. + { Local function_template = + FunctionTemplate::New(FakeConstructorCallback); + Local function = function_template->GetFunction(); + Local instance1 = function; + context->Global()->Set(v8_str("obj4"), instance1); + v8::TryCatch try_catch; + Local value; + CHECK(!try_catch.HasCaught()); + + CHECK(instance1->IsObject()); + CHECK(instance1->IsFunction()); + + value = CompileRun("new obj4(28)"); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsObject()); + + Local args1[] = { v8_num(28) }; + value = instance1->CallAsConstructor(1, args1); + CHECK(!try_catch.HasCaught()); + CHECK(value->IsObject()); + + Local instance_template = ObjectTemplate::New(); + instance_template->SetCallAsFunctionHandler(FakeConstructorCallback); + Local instance2 = instance_template->NewInstance(); + context->Global()->Set(v8_str("obj5"), instance2); + CHECK(!try_catch.HasCaught()); + + CHECK(instance2->IsObject()); + CHECK(!instance2->IsFunction()); + + value = CompileRun("new obj5(28)"); + CHECK(!try_catch.HasCaught()); + CHECK(!value->IsObject()); + + Local args2[] = { v8_num(28) }; + value = instance2->CallAsConstructor(1, args2); + CHECK(!try_catch.HasCaught()); + CHECK(!value->IsObject()); + } +} + + THREADED_TEST(FunctionDescriptorException) { v8::HandleScope handle_scope; LocalContext context; @@ -7028,9 +7222,8 @@ THREADED_TEST(CallAsFunction) { 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"); + CHECK_EQ("TypeError: Property 'obj2' of object # is not a function", + *exception_value1); try_catch.Reset(); // Call an object without call-as-function handler through the API @@ -7040,7 +7233,7 @@ THREADED_TEST(CallAsFunction) { 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"); + CHECK_EQ("TypeError: [object Object] is not a function", *exception_value2); try_catch.Reset(); } @@ -7057,14 +7250,14 @@ THREADED_TEST(CallAsFunction) { value = CompileRun("obj3(22)"); CHECK(try_catch.HasCaught()); String::AsciiValue exception_value1(try_catch.Exception()); - CHECK_EQ(*exception_value1, "22"); + CHECK_EQ("22", *exception_value1); 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"); + CHECK_EQ("23", *exception_value2); try_catch.Reset(); } } -- 1.7.6