// 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. // TODO(jochen): Remove this after the setting is turned on globally. #define V8_IMMINENT_DEPRECATION_WARNINGS #include #include "test/cctest/test-api.h" #include "include/v8-util.h" #include "src/api.h" #include "src/arguments.h" #include "src/base/platform/platform.h" #include "src/compilation-cache.h" #include "src/execution.h" #include "src/objects.h" #include "src/parsing/parser.h" #include "src/unicode-inl.h" #include "src/utils.h" #include "src/vm-state.h" using ::v8::Boolean; using ::v8::BooleanObject; using ::v8::Context; using ::v8::Extension; using ::v8::Function; using ::v8::FunctionTemplate; using ::v8::HandleScope; using ::v8::Local; using ::v8::Name; using ::v8::Message; using ::v8::MessageCallback; using ::v8::Object; using ::v8::ObjectTemplate; using ::v8::Persistent; using ::v8::Script; using ::v8::StackTrace; using ::v8::String; using ::v8::Symbol; using ::v8::TryCatch; using ::v8::Undefined; using ::v8::UniqueId; using ::v8::V8; using ::v8::Value; namespace { void Returns42(const v8::FunctionCallbackInfo& info) { info.GetReturnValue().Set(42); } void Return239Callback(Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); CheckReturnValue(info, FUNCTION_ADDR(Return239Callback)); info.GetReturnValue().Set(v8_str("bad value")); info.GetReturnValue().Set(v8_num(239)); } void EmptyInterceptorGetter(Local name, const v8::PropertyCallbackInfo& info) {} void EmptyInterceptorSetter(Local name, Local value, const v8::PropertyCallbackInfo& info) {} void SimpleAccessorGetter(Local name, const v8::PropertyCallbackInfo& info) { Local self = Local::Cast(info.This()); info.GetReturnValue().Set(self->Get(info.GetIsolate()->GetCurrentContext(), String::Concat(v8_str("accessor_"), name)) .ToLocalChecked()); } void SimpleAccessorSetter(Local name, Local value, const v8::PropertyCallbackInfo& info) { Local self = Local::Cast(info.This()); self->Set(info.GetIsolate()->GetCurrentContext(), String::Concat(v8_str("accessor_"), name), value) .FromJust(); } void SymbolAccessorGetter(Local name, const v8::PropertyCallbackInfo& info) { CHECK(name->IsSymbol()); Local sym = Local::Cast(name); if (sym->Name()->IsUndefined()) return; SimpleAccessorGetter(Local::Cast(sym->Name()), info); } void SymbolAccessorSetter(Local name, Local value, const v8::PropertyCallbackInfo& info) { CHECK(name->IsSymbol()); Local sym = Local::Cast(name); if (sym->Name()->IsUndefined()) return; SimpleAccessorSetter(Local::Cast(sym->Name()), value, info); } void StringInterceptorGetter( Local name, const v8::PropertyCallbackInfo& info) { // Intercept names that start with 'interceptor_'. String::Utf8Value utf8(name); char* name_str = *utf8; char prefix[] = "interceptor_"; int i; for (i = 0; name_str[i] && prefix[i]; ++i) { if (name_str[i] != prefix[i]) return; } Local self = Local::Cast(info.This()); info.GetReturnValue().Set( self->GetPrivate( info.GetIsolate()->GetCurrentContext(), v8::Private::ForApi(info.GetIsolate(), v8_str(name_str + i))) .ToLocalChecked()); } void StringInterceptorSetter(Local name, Local value, const v8::PropertyCallbackInfo& info) { // Intercept accesses that set certain integer values, for which the name does // not start with 'accessor_'. String::Utf8Value utf8(name); char* name_str = *utf8; char prefix[] = "accessor_"; int i; for (i = 0; name_str[i] && prefix[i]; ++i) { if (name_str[i] != prefix[i]) break; } if (!prefix[i]) return; Local context = info.GetIsolate()->GetCurrentContext(); if (value->IsInt32() && value->Int32Value(context).FromJust() < 10000) { Local self = Local::Cast(info.This()); Local symbol = v8::Private::ForApi(info.GetIsolate(), name); self->SetPrivate(context, symbol, value).FromJust(); info.GetReturnValue().Set(value); } } void InterceptorGetter(Local generic_name, const v8::PropertyCallbackInfo& info) { if (generic_name->IsSymbol()) return; StringInterceptorGetter(Local::Cast(generic_name), info); } void InterceptorSetter(Local generic_name, Local value, const v8::PropertyCallbackInfo& info) { if (generic_name->IsSymbol()) return; StringInterceptorSetter(Local::Cast(generic_name), value, info); } void GenericInterceptorGetter(Local generic_name, const v8::PropertyCallbackInfo& info) { Local str; if (generic_name->IsSymbol()) { Local name = Local::Cast(generic_name)->Name(); if (name->IsUndefined()) return; str = String::Concat(v8_str("_sym_"), Local::Cast(name)); } else { Local name = Local::Cast(generic_name); String::Utf8Value utf8(name); char* name_str = *utf8; if (*name_str == '_') return; str = String::Concat(v8_str("_str_"), name); } Local self = Local::Cast(info.This()); info.GetReturnValue().Set( self->Get(info.GetIsolate()->GetCurrentContext(), str).ToLocalChecked()); } void GenericInterceptorSetter(Local generic_name, Local value, const v8::PropertyCallbackInfo& info) { Local str; if (generic_name->IsSymbol()) { Local name = Local::Cast(generic_name)->Name(); if (name->IsUndefined()) return; str = String::Concat(v8_str("_sym_"), Local::Cast(name)); } else { Local name = Local::Cast(generic_name); String::Utf8Value utf8(name); char* name_str = *utf8; if (*name_str == '_') return; str = String::Concat(v8_str("_str_"), name); } Local self = Local::Cast(info.This()); self->Set(info.GetIsolate()->GetCurrentContext(), str, value).FromJust(); info.GetReturnValue().Set(value); } void AddAccessor(Local templ, Local name, v8::AccessorGetterCallback getter, v8::AccessorSetterCallback setter) { templ->PrototypeTemplate()->SetAccessor(name, getter, setter); } void AddInterceptor(Local templ, v8::NamedPropertyGetterCallback getter, v8::NamedPropertySetterCallback setter) { templ->InstanceTemplate()->SetNamedPropertyHandler(getter, setter); } void AddAccessor(Local templ, Local name, v8::AccessorNameGetterCallback getter, v8::AccessorNameSetterCallback setter) { templ->PrototypeTemplate()->SetAccessor(name, getter, setter); } void AddInterceptor(Local templ, v8::GenericNamedPropertyGetterCallback getter, v8::GenericNamedPropertySetterCallback setter) { templ->InstanceTemplate()->SetHandler( v8::NamedPropertyHandlerConfiguration(getter, setter)); } v8::Local bottom; void CheckThisIndexedPropertyHandler( uint32_t index, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyHandler)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyHandler( Local name, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyHandler)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisIndexedPropertySetter( uint32_t index, Local value, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertySetter)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertySetter( Local property, Local value, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertySetter)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisIndexedPropertyQuery( uint32_t index, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyQuery)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyQuery( Local property, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyQuery)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisIndexedPropertyDeleter( uint32_t index, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDeleter)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyDeleter( Local property, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDeleter)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisIndexedPropertyEnumerator( const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyEnumerator)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyEnumerator( const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyEnumerator)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } int echo_named_call_count; void EchoNamedProperty(Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); CHECK(v8_str("data") ->Equals(info.GetIsolate()->GetCurrentContext(), info.Data()) .FromJust()); echo_named_call_count++; info.GetReturnValue().Set(name); } void InterceptorHasOwnPropertyGetter( Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); } void InterceptorHasOwnPropertyGetterGC( Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); CcTest::heap()->CollectAllGarbage(); } } // namespace THREADED_TEST(InterceptorHasOwnProperty) { LocalContext context; v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope scope(isolate); Local fun_templ = v8::FunctionTemplate::New(isolate); Local instance_templ = fun_templ->InstanceTemplate(); instance_templ->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetter)); Local function = fun_templ->GetFunction(context.local()).ToLocalChecked(); context->Global() ->Set(context.local(), v8_str("constructor"), function) .FromJust(); v8::Local value = CompileRun( "var o = new constructor();" "o.hasOwnProperty('ostehaps');"); CHECK_EQ(false, value->BooleanValue(context.local()).FromJust()); value = CompileRun( "o.ostehaps = 42;" "o.hasOwnProperty('ostehaps');"); CHECK_EQ(true, value->BooleanValue(context.local()).FromJust()); value = CompileRun( "var p = new constructor();" "p.hasOwnProperty('ostehaps');"); CHECK_EQ(false, value->BooleanValue(context.local()).FromJust()); } THREADED_TEST(InterceptorHasOwnPropertyCausingGC) { LocalContext context; v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope scope(isolate); Local fun_templ = v8::FunctionTemplate::New(isolate); Local instance_templ = fun_templ->InstanceTemplate(); instance_templ->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetterGC)); Local function = fun_templ->GetFunction(context.local()).ToLocalChecked(); context->Global() ->Set(context.local(), v8_str("constructor"), function) .FromJust(); // Let's first make some stuff so we can be sure to get a good GC. CompileRun( "function makestr(size) {" " switch (size) {" " case 1: return 'f';" " case 2: return 'fo';" " case 3: return 'foo';" " }" " return makestr(size >> 1) + makestr((size + 1) >> 1);" "}" "var x = makestr(12345);" "x = makestr(31415);" "x = makestr(23456);"); v8::Local value = CompileRun( "var o = new constructor();" "o.__proto__ = new String(x);" "o.hasOwnProperty('ostehaps');"); CHECK_EQ(false, value->BooleanValue(context.local()).FromJust()); } static void CheckInterceptorLoadIC( v8::GenericNamedPropertyGetterCallback getter, const char* source, int expected) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler(v8::NamedPropertyHandlerConfiguration(getter, 0, 0, 0, 0, v8_str("data"))); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun(source); CHECK_EQ(expected, value->Int32Value(context.local()).FromJust()); } static void InterceptorLoadICGetter( Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); v8::Isolate* isolate = CcTest::isolate(); CHECK_EQ(isolate, info.GetIsolate()); v8::Local context = isolate->GetCurrentContext(); CHECK(v8_str("data")->Equals(context, info.Data()).FromJust()); CHECK(v8_str("x")->Equals(context, name).FromJust()); info.GetReturnValue().Set(v8::Integer::New(isolate, 42)); } // This test should hit the load IC for the interceptor case. THREADED_TEST(InterceptorLoadIC) { CheckInterceptorLoadIC(InterceptorLoadICGetter, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result = o.x;" "}", 42); } // Below go several tests which verify that JITing for various // configurations of interceptor and explicit fields works fine // (those cases are special cased to get better performance). static void InterceptorLoadXICGetter( Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); info.GetReturnValue().Set( v8_str("x") ->Equals(info.GetIsolate()->GetCurrentContext(), name) .FromJust() ? v8::Local(v8::Integer::New(info.GetIsolate(), 42)) : v8::Local()); } THREADED_TEST(InterceptorLoadICWithFieldOnHolder) { CheckInterceptorLoadIC(InterceptorLoadXICGetter, "var result = 0;" "o.y = 239;" "for (var i = 0; i < 1000; i++) {" " result = o.y;" "}", 239); } THREADED_TEST(InterceptorLoadICWithSubstitutedProto) { CheckInterceptorLoadIC(InterceptorLoadXICGetter, "var result = 0;" "o.__proto__ = { 'y': 239 };" "for (var i = 0; i < 1000; i++) {" " result = o.y + o.x;" "}", 239 + 42); } THREADED_TEST(InterceptorLoadICWithPropertyOnProto) { CheckInterceptorLoadIC(InterceptorLoadXICGetter, "var result = 0;" "o.__proto__.y = 239;" "for (var i = 0; i < 1000; i++) {" " result = o.y + o.x;" "}", 239 + 42); } THREADED_TEST(InterceptorLoadICUndefined) { CheckInterceptorLoadIC(InterceptorLoadXICGetter, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result = (o.y == undefined) ? 239 : 42;" "}", 239); } THREADED_TEST(InterceptorLoadICWithOverride) { CheckInterceptorLoadIC(InterceptorLoadXICGetter, "fst = new Object(); fst.__proto__ = o;" "snd = new Object(); snd.__proto__ = fst;" "var result1 = 0;" "for (var i = 0; i < 1000; i++) {" " result1 = snd.x;" "}" "fst.x = 239;" "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result = snd.x;" "}" "result + result1", 239 + 42); } // Test the case when we stored field into // a stub, but interceptor produced value on its own. THREADED_TEST(InterceptorLoadICFieldNotNeeded) { CheckInterceptorLoadIC( InterceptorLoadXICGetter, "proto = new Object();" "o.__proto__ = proto;" "proto.x = 239;" "for (var i = 0; i < 1000; i++) {" " o.x;" // Now it should be ICed and keep a reference to x defined on proto "}" "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result += o.x;" "}" "result;", 42 * 1000); } // Test the case when we stored field into // a stub, but it got invalidated later on. THREADED_TEST(InterceptorLoadICInvalidatedField) { CheckInterceptorLoadIC( InterceptorLoadXICGetter, "proto1 = new Object();" "proto2 = new Object();" "o.__proto__ = proto1;" "proto1.__proto__ = proto2;" "proto2.y = 239;" "for (var i = 0; i < 1000; i++) {" " o.y;" // Now it should be ICed and keep a reference to y defined on proto2 "}" "proto1.y = 42;" "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result += o.y;" "}" "result;", 42 * 1000); } static int interceptor_load_not_handled_calls = 0; static void InterceptorLoadNotHandled( Local name, const v8::PropertyCallbackInfo& info) { ++interceptor_load_not_handled_calls; } // Test how post-interceptor lookups are done in the non-cacheable // case: the interceptor should not be invoked during this lookup. THREADED_TEST(InterceptorLoadICPostInterceptor) { interceptor_load_not_handled_calls = 0; CheckInterceptorLoadIC(InterceptorLoadNotHandled, "receiver = new Object();" "receiver.__proto__ = o;" "proto = new Object();" "/* Make proto a slow-case object. */" "for (var i = 0; i < 1000; i++) {" " proto[\"xxxxxxxx\" + i] = [];" "}" "proto.x = 17;" "o.__proto__ = proto;" "var result = 0;" "for (var i = 0; i < 1000; i++) {" " result += receiver.x;" "}" "result;", 17 * 1000); CHECK_EQ(1000, interceptor_load_not_handled_calls); } // Test the case when we stored field into // a stub, but it got invalidated later on due to override on // global object which is between interceptor and fields' holders. THREADED_TEST(InterceptorLoadICInvalidatedFieldViaGlobal) { CheckInterceptorLoadIC( InterceptorLoadXICGetter, "o.__proto__ = this;" // set a global to be a proto of o. "this.__proto__.y = 239;" "for (var i = 0; i < 10; i++) {" " if (o.y != 239) throw 'oops: ' + o.y;" // Now it should be ICed and keep a reference to y defined on // field_holder. "}" "this.y = 42;" // Assign on a global. "var result = 0;" "for (var i = 0; i < 10; i++) {" " result += o.y;" "}" "result;", 42 * 10); } static void SetOnThis(Local name, Local value, const v8::PropertyCallbackInfo& info) { Local::Cast(info.This()) ->CreateDataProperty(info.GetIsolate()->GetCurrentContext(), name, value) .FromJust(); } THREADED_TEST(InterceptorLoadICWithCallbackOnHolder) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); templ->SetAccessor(v8_str("y"), Return239Callback); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ->NewInstance(context.local()).ToLocalChecked()) .FromJust(); // Check the case when receiver and interceptor's holder // are the same objects. v8::Local value = CompileRun( "var result = 0;" "for (var i = 0; i < 7; i++) {" " result = o.y;" "}"); CHECK_EQ(239, value->Int32Value(context.local()).FromJust()); // Check the case when interceptor's holder is in proto chain // of receiver. value = CompileRun( "r = { __proto__: o };" "var result = 0;" "for (var i = 0; i < 7; i++) {" " result = r.y;" "}"); CHECK_EQ(239, value->Int32Value(context.local()).FromJust()); } THREADED_TEST(InterceptorLoadICWithCallbackOnProto) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_o = ObjectTemplate::New(isolate); templ_o->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); v8::Local templ_p = ObjectTemplate::New(isolate); templ_p->SetAccessor(v8_str("y"), Return239Callback); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ_o->NewInstance(context.local()).ToLocalChecked()) .FromJust(); context->Global() ->Set(context.local(), v8_str("p"), templ_p->NewInstance(context.local()).ToLocalChecked()) .FromJust(); // Check the case when receiver and interceptor's holder // are the same objects. v8::Local value = CompileRun( "o.__proto__ = p;" "var result = 0;" "for (var i = 0; i < 7; i++) {" " result = o.x + o.y;" "}"); CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust()); // Check the case when interceptor's holder is in proto chain // of receiver. value = CompileRun( "r = { __proto__: o };" "var result = 0;" "for (var i = 0; i < 7; i++) {" " result = r.x + r.y;" "}"); CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust()); } THREADED_TEST(InterceptorLoadICForCallbackWithOverride) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); templ->SetAccessor(v8_str("y"), Return239Callback); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun( "fst = new Object(); fst.__proto__ = o;" "snd = new Object(); snd.__proto__ = fst;" "var result1 = 0;" "for (var i = 0; i < 7; i++) {" " result1 = snd.x;" "}" "fst.x = 239;" "var result = 0;" "for (var i = 0; i < 7; i++) {" " result = snd.x;" "}" "result + result1"); CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust()); } // Test the case when we stored callback into // a stub, but interceptor produced value on its own. THREADED_TEST(InterceptorLoadICCallbackNotNeeded) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_o = ObjectTemplate::New(isolate); templ_o->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); v8::Local templ_p = ObjectTemplate::New(isolate); templ_p->SetAccessor(v8_str("y"), Return239Callback); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ_o->NewInstance(context.local()).ToLocalChecked()) .FromJust(); context->Global() ->Set(context.local(), v8_str("p"), templ_p->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun( "o.__proto__ = p;" "for (var i = 0; i < 7; i++) {" " o.x;" // Now it should be ICed and keep a reference to x defined on p "}" "var result = 0;" "for (var i = 0; i < 7; i++) {" " result += o.x;" "}" "result"); CHECK_EQ(42 * 7, value->Int32Value(context.local()).FromJust()); } // Test the case when we stored callback into // a stub, but it got invalidated later on. THREADED_TEST(InterceptorLoadICInvalidatedCallback) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_o = ObjectTemplate::New(isolate); templ_o->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); v8::Local templ_p = ObjectTemplate::New(isolate); templ_p->SetAccessor(v8_str("y"), Return239Callback, SetOnThis); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ_o->NewInstance(context.local()).ToLocalChecked()) .FromJust(); context->Global() ->Set(context.local(), v8_str("p"), templ_p->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun( "inbetween = new Object();" "o.__proto__ = inbetween;" "inbetween.__proto__ = p;" "for (var i = 0; i < 10; i++) {" " o.y;" // Now it should be ICed and keep a reference to y defined on p "}" "inbetween.y = 42;" "var result = 0;" "for (var i = 0; i < 10; i++) {" " result += o.y;" "}" "result"); CHECK_EQ(42 * 10, value->Int32Value(context.local()).FromJust()); } // Test the case when we stored callback into // a stub, but it got invalidated later on due to override on // global object which is between interceptor and callbacks' holders. THREADED_TEST(InterceptorLoadICInvalidatedCallbackViaGlobal) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_o = ObjectTemplate::New(isolate); templ_o->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); v8::Local templ_p = ObjectTemplate::New(isolate); templ_p->SetAccessor(v8_str("y"), Return239Callback, SetOnThis); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ_o->NewInstance(context.local()).ToLocalChecked()) .FromJust(); context->Global() ->Set(context.local(), v8_str("p"), templ_p->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun( "o.__proto__ = this;" "this.__proto__ = p;" "for (var i = 0; i < 10; i++) {" " if (o.y != 239) throw 'oops: ' + o.y;" // Now it should be ICed and keep a reference to y defined on p "}" "this.y = 42;" "var result = 0;" "for (var i = 0; i < 10; i++) {" " result += o.y;" "}" "result"); CHECK_EQ(42 * 10, value->Int32Value(context.local()).FromJust()); } static void InterceptorLoadICGetter0( Local name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); CHECK(v8_str("x") ->Equals(info.GetIsolate()->GetCurrentContext(), name) .FromJust()); info.GetReturnValue().Set(v8::Integer::New(info.GetIsolate(), 0)); } THREADED_TEST(InterceptorReturningZero) { CheckInterceptorLoadIC(InterceptorLoadICGetter0, "o.x == undefined ? 1 : 0", 0); } static void InterceptorStoreICSetter( Local key, Local value, const v8::PropertyCallbackInfo& info) { v8::Local context = info.GetIsolate()->GetCurrentContext(); CHECK(v8_str("x")->Equals(context, key).FromJust()); CHECK_EQ(42, value->Int32Value(context).FromJust()); info.GetReturnValue().Set(value); } // This test should hit the store IC for the interceptor case. THREADED_TEST(InterceptorStoreIC) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler(v8::NamedPropertyHandlerConfiguration( InterceptorLoadICGetter, InterceptorStoreICSetter, 0, 0, 0, v8_str("data"))); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ->NewInstance(context.local()).ToLocalChecked()) .FromJust(); CompileRun( "for (var i = 0; i < 1000; i++) {" " o.x = 42;" "}"); } THREADED_TEST(InterceptorStoreICWithNoSetter) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler( v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), templ->NewInstance(context.local()).ToLocalChecked()) .FromJust(); v8::Local value = CompileRun( "for (var i = 0; i < 1000; i++) {" " o.y = 239;" "}" "42 + o.y"); CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust()); } THREADED_TEST(EmptyInterceptorDoesNotShadowAccessors) { v8::HandleScope scope(CcTest::isolate()); Local parent = FunctionTemplate::New(CcTest::isolate()); Local child = FunctionTemplate::New(CcTest::isolate()); child->Inherit(parent); AddAccessor(parent, v8_str("age"), SimpleAccessorGetter, SimpleAccessorSetter); AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter); LocalContext env; env->Global() ->Set(env.local(), v8_str("Child"), child->GetFunction(env.local()).ToLocalChecked()) .FromJust(); CompileRun( "var child = new Child;" "child.age = 10;"); ExpectBoolean("child.hasOwnProperty('age')", false); ExpectInt32("child.age", 10); ExpectInt32("child.accessor_age", 10); } THREADED_TEST(LegacyInterceptorDoesNotSeeSymbols) { LocalContext env; v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); Local parent = FunctionTemplate::New(isolate); Local child = FunctionTemplate::New(isolate); v8::Local age = v8::Symbol::New(isolate, v8_str("age")); child->Inherit(parent); AddAccessor(parent, age, SymbolAccessorGetter, SymbolAccessorSetter); AddInterceptor(child, StringInterceptorGetter, StringInterceptorSetter); env->Global() ->Set(env.local(), v8_str("Child"), child->GetFunction(env.local()).ToLocalChecked()) .FromJust(); env->Global()->Set(env.local(), v8_str("age"), age).FromJust(); CompileRun( "var child = new Child;" "child[age] = 10;"); ExpectInt32("child[age]", 10); ExpectBoolean("child.hasOwnProperty('age')", false); ExpectBoolean("child.hasOwnProperty('accessor_age')", true); } THREADED_TEST(GenericInterceptorDoesSeeSymbols) { LocalContext env; v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); Local parent = FunctionTemplate::New(isolate); Local child = FunctionTemplate::New(isolate); v8::Local age = v8::Symbol::New(isolate, v8_str("age")); v8::Local anon = v8::Symbol::New(isolate); child->Inherit(parent); AddAccessor(parent, age, SymbolAccessorGetter, SymbolAccessorSetter); AddInterceptor(child, GenericInterceptorGetter, GenericInterceptorSetter); env->Global() ->Set(env.local(), v8_str("Child"), child->GetFunction(env.local()).ToLocalChecked()) .FromJust(); env->Global()->Set(env.local(), v8_str("age"), age).FromJust(); env->Global()->Set(env.local(), v8_str("anon"), anon).FromJust(); CompileRun( "var child = new Child;" "child[age] = 10;"); ExpectInt32("child[age]", 10); ExpectInt32("child._sym_age", 10); // Check that it also sees strings. CompileRun("child.foo = 47"); ExpectInt32("child.foo", 47); ExpectInt32("child._str_foo", 47); // Check that the interceptor can punt (in this case, on anonymous symbols). CompileRun("child[anon] = 31337"); ExpectInt32("child[anon]", 31337); } THREADED_TEST(NamedPropertyHandlerGetter) { echo_named_call_count = 0; v8::HandleScope scope(CcTest::isolate()); v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( EchoNamedProperty, 0, 0, 0, 0, v8_str("data"))); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); CHECK_EQ(echo_named_call_count, 0); v8_compile("obj.x")->Run(env.local()).ToLocalChecked(); CHECK_EQ(echo_named_call_count, 1); const char* code = "var str = 'oddle'; obj[str] + obj.poddle;"; v8::Local str = CompileRun(code); String::Utf8Value value(str); CHECK_EQ(0, strcmp(*value, "oddlepoddle")); // Check default behavior CHECK_EQ(10, v8_compile("obj.flob = 10;") ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(v8_compile("'myProperty' in obj") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(env.local()) .FromJust()); CHECK(v8_compile("delete obj.myProperty") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(env.local()) .FromJust()); } int echo_indexed_call_count = 0; static void EchoIndexedProperty( uint32_t index, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); CHECK(v8_num(637) ->Equals(info.GetIsolate()->GetCurrentContext(), info.Data()) .FromJust()); echo_indexed_call_count++; info.GetReturnValue().Set(v8_num(index)); } THREADED_TEST(IndexedPropertyHandlerGetter) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::FunctionTemplate::New(isolate); templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration( EchoIndexedProperty, 0, 0, 0, 0, v8_num(637))); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); Local