// 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. #include #include "test/cctest/test-api.h" #include "include/v8-util.h" #include "src/api/api-inl.h" #include "src/base/platform/platform.h" #include "src/codegen/compilation-cache.h" #include "src/execution/arguments.h" #include "src/execution/execution.h" #include "src/objects/objects-inl.h" #include "src/objects/objects.h" #include "src/runtime/runtime.h" #include "src/strings/unicode-inl.h" #include "src/utils/utils.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::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 EmptyInterceptorQuery(Local name, const v8::PropertyCallbackInfo& info) {} void EmptyInterceptorDeleter( Local name, const v8::PropertyCallbackInfo& info) {} void EmptyInterceptorEnumerator( 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(info.GetIsolate(), 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(info.GetIsolate(), 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 InterceptorGetter(Local generic_name, const v8::PropertyCallbackInfo& info) { if (generic_name->IsSymbol()) return; Local name = Local::Cast(generic_name); String::Utf8Value utf8(info.GetIsolate(), 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 InterceptorSetter(Local generic_name, Local value, const v8::PropertyCallbackInfo& info) { if (generic_name->IsSymbol()) return; Local name = Local::Cast(generic_name); // Intercept accesses that set certain integer values, for which the name does // not start with 'accessor_'. String::Utf8Value utf8(info.GetIsolate(), 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 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(info.GetIsolate(), v8_str("_sym_"), Local::Cast(name)); } else { Local name = Local::Cast(generic_name); String::Utf8Value utf8(info.GetIsolate(), name); char* name_str = *utf8; if (*name_str == '_') return; str = String::Concat(info.GetIsolate(), 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(info.GetIsolate(), v8_str("_sym_"), Local::Cast(name)); } else { Local name = Local::Cast(generic_name); String::Utf8Value utf8(info.GetIsolate(), name); char* name_str = *utf8; if (*name_str == '_') return; str = String::Concat(info.GetIsolate(), 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 AddAccessor(Local templ, Local name, v8::AccessorNameGetterCallback getter, v8::AccessorNameSetterCallback setter) { templ->PrototypeTemplate()->SetAccessor(name, getter, setter); } void AddStringOnlyInterceptor(Local templ, v8::GenericNamedPropertyGetterCallback getter, v8::GenericNamedPropertySetterCallback setter) { templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( getter, setter, nullptr, nullptr, nullptr, Local(), v8::PropertyHandlerFlags::kOnlyInterceptStrings)); } 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 CheckThisIndexedPropertyDefiner( uint32_t index, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDefiner)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyDefiner( Local property, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDefiner)); 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 CheckThisIndexedPropertyDescriptor( uint32_t index, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDescriptor)); ApiTestFuzzer::Fuzz(); CHECK(info.This() ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) .FromJust()); } void CheckThisNamedPropertyDescriptor( Local property, const v8::PropertyCallbackInfo& info) { CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDescriptor)); 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::CollectAllGarbage(); } int query_counter_int = 0; void QueryCallback(Local property, const v8::PropertyCallbackInfo& info) { query_counter_int++; } } // namespace // Examples that show when the query callback is triggered. THREADED_TEST(QueryInterceptor) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::FunctionTemplate::New(isolate); templ->InstanceTemplate()->SetHandler( v8::NamedPropertyHandlerConfiguration(nullptr, nullptr, QueryCallback)); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); CHECK_EQ(0, query_counter_int); v8::Local result = v8_compile("Object.getOwnPropertyDescriptor(obj, 'x');") ->Run(env.local()) .ToLocalChecked(); CHECK_EQ(1, query_counter_int); CHECK_EQ(v8::PropertyAttribute::None, static_cast( result->Int32Value(env.local()).FromJust())); v8_compile("Object.defineProperty(obj, 'not_enum', {value: 17});") ->Run(env.local()) .ToLocalChecked(); CHECK_EQ(2, query_counter_int); v8_compile( "Object.defineProperty(obj, 'enum', {value: 17, enumerable: true, " "writable: true});") ->Run(env.local()) .ToLocalChecked(); CHECK_EQ(3, query_counter_int); CHECK(v8_compile("obj.propertyIsEnumerable('enum');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(4, query_counter_int); CHECK(!v8_compile("obj.propertyIsEnumerable('not_enum');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(5, query_counter_int); CHECK(v8_compile("obj.hasOwnProperty('enum');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(5, query_counter_int); CHECK(v8_compile("obj.hasOwnProperty('not_enum');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(5, query_counter_int); CHECK(!v8_compile("obj.hasOwnProperty('x');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(6, query_counter_int); CHECK(!v8_compile("obj.propertyIsEnumerable('undef');") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); CHECK_EQ(7, query_counter_int); v8_compile("Object.defineProperty(obj, 'enum', {value: 42});") ->Run(env.local()) .ToLocalChecked(); CHECK_EQ(8, query_counter_int); v8_compile("Object.isFrozen('obj.x');")->Run(env.local()).ToLocalChecked(); CHECK_EQ(8, query_counter_int); v8_compile("'x' in obj;")->Run(env.local()).ToLocalChecked(); CHECK_EQ(9, query_counter_int); } namespace { bool get_was_called = false; bool set_was_called = false; int set_was_called_counter = 0; void GetterCallback(Local property, const v8::PropertyCallbackInfo& info) { get_was_called = true; } void SetterCallback(Local property, Local value, const v8::PropertyCallbackInfo& info) { set_was_called = true; set_was_called_counter++; } void InterceptingSetterCallback( Local property, Local value, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set(value); } } // namespace // Check that get callback is called in defineProperty with accessor descriptor. THREADED_TEST(DefinerCallbackAccessorInterceptor) { v8::HandleScope scope(CcTest::isolate()); v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler( v8::NamedPropertyHandlerConfiguration(GetterCallback, SetterCallback)); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); get_was_called = false; set_was_called = false; v8_compile("Object.defineProperty(obj, 'x', {set: function() {return 17;}});") ->Run(env.local()) .ToLocalChecked(); CHECK(get_was_called); CHECK(!set_was_called); } // Check that set callback is called for function declarations. THREADED_TEST(SetterCallbackFunctionDeclarationInterceptor) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler( v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); set_was_called_counter = 0; // Declare function. v8::Local code = v8_str("function x() {return 42;}; x();"); CHECK_EQ(42, v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->Int32Value(ctx) .FromJust()); CHECK_EQ(1, set_was_called_counter); // Redeclare function. code = v8_str("function x() {return 43;}; x();"); CHECK_EQ(43, v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->Int32Value(ctx) .FromJust()); CHECK_EQ(2, set_was_called_counter); // Redefine function. code = v8_str("x = function() {return 44;}; x();"); CHECK_EQ(44, v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->Int32Value(ctx) .FromJust()); CHECK_EQ(3, set_was_called_counter); } namespace { int descriptor_was_called; void PropertyDescriptorCallback( Local name, const v8::PropertyCallbackInfo& info) { // Intercept the callback by setting a different descriptor. descriptor_was_called++; const char* code = "var desc = {value: 5};" "desc;"; Local descriptor = v8_compile(code) ->Run(info.GetIsolate()->GetCurrentContext()) .ToLocalChecked(); info.GetReturnValue().Set(descriptor); } } // namespace // Check that the descriptor callback is called on the global object. THREADED_TEST(DescriptorCallbackOnGlobalObject) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, PropertyDescriptorCallback, nullptr, nullptr, nullptr)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); descriptor_was_called = 0; // Declare function. v8::Local code = v8_str( "var x = 42; var desc = Object.getOwnPropertyDescriptor(this, 'x'); " "desc.value;"); CHECK_EQ(5, v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->Int32Value(ctx) .FromJust()); CHECK_EQ(1, descriptor_was_called); } namespace { void QueryCallbackSetDontDelete( Local property, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set(v8::PropertyAttribute::DontDelete); } } // namespace // Regression for a Node.js test that fails in debug mode. THREADED_TEST(InterceptorFunctionRedeclareWithQueryCallback) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, QueryCallbackSetDontDelete)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); // Declare and redeclare function. v8::Local code = v8_str( "function x() {return 42;};" "function x() {return 43;};"); v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).ToLocalChecked(); } // Regression test for chromium bug 656648. // Do not crash on non-masking, intercepting setter callbacks. THREADED_TEST(NonMaskingInterceptor) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, InterceptingSetterCallback, nullptr, nullptr, nullptr, Local(), v8::PropertyHandlerFlags::kNonMasking)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); v8::Local code = v8_str("function x() {return 43;};"); v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).ToLocalChecked(); } // Check that function re-declarations throw if they are read-only. THREADED_TEST(SetterCallbackFunctionDeclarationInterceptorThrow) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler( v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); set_was_called = false; v8::Local code = v8_str( "function x() {return 42;};" "Object.defineProperty(this, 'x', {" "configurable: false, " "writable: false});" "x();"); CHECK_EQ(42, v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->Int32Value(ctx) .FromJust()); CHECK(set_was_called); v8::TryCatch try_catch(CcTest::isolate()); set_was_called = false; // Redeclare function that is read-only. code = v8_str("function x() {return 43;};"); CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty()); CHECK(try_catch.HasCaught()); CHECK(!set_was_called); } namespace { bool get_was_called_in_order = false; bool define_was_called_in_order = false; void GetterCallbackOrder(Local property, const v8::PropertyCallbackInfo& info) { get_was_called_in_order = true; CHECK(!define_was_called_in_order); info.GetReturnValue().Set(property); } void DefinerCallbackOrder(Local property, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { // Get called before DefineProperty because we query the descriptor first. CHECK(get_was_called_in_order); define_was_called_in_order = true; } } // namespace // Check that getter callback is called before definer callback. THREADED_TEST(DefinerCallbackGetAndDefine) { v8::HandleScope scope(CcTest::isolate()); v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( GetterCallbackOrder, SetterCallback, nullptr, nullptr, nullptr, DefinerCallbackOrder)); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); CHECK(!get_was_called_in_order); CHECK(!define_was_called_in_order); v8_compile("Object.defineProperty(obj, 'x', {set: function() {return 17;}});") ->Run(env.local()) .ToLocalChecked(); CHECK(get_was_called_in_order); CHECK(define_was_called_in_order); } namespace { // namespace for InObjectLiteralDefinitionWithInterceptor // Workaround for no-snapshot builds: only intercept once Context::New() is // done, otherwise we'll intercept // bootstrapping like defining array on the global object. bool context_is_done = false; bool getter_callback_was_called = false; void ReturnUndefinedGetterCallback( Local property, const v8::PropertyCallbackInfo& info) { if (context_is_done) { getter_callback_was_called = true; info.GetReturnValue().SetUndefined(); } } } // namespace // Check that an interceptor is not invoked during ES6 style definitions inside // an object literal. THREADED_TEST(InObjectLiteralDefinitionWithInterceptor) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; // Set up a context in which all global object definitions are intercepted. v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetHandler( v8::NamedPropertyHandlerConfiguration(ReturnUndefinedGetterCallback)); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); context_is_done = true; // The interceptor returns undefined for any global object, // so setting a property on an object should throw. v8::Local code = v8_str("var o = {}; o.x = 5"); { getter_callback_was_called = false; v8::TryCatch try_catch(CcTest::isolate()); CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty()); CHECK(try_catch.HasCaught()); CHECK(getter_callback_was_called); } // Defining a property in the object literal should not throw // because the interceptor is not invoked. { getter_callback_was_called = false; v8::TryCatch try_catch(CcTest::isolate()); code = v8_str("var l = {x: 5};"); CHECK(v8::Script::Compile(ctx, code) .ToLocalChecked() ->Run(ctx) .ToLocalChecked() ->IsUndefined()); CHECK(!try_catch.HasCaught()); CHECK(!getter_callback_was_called); } } 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(!value->BooleanValue(isolate)); value = CompileRun( "o.ostehaps = 42;" "o.hasOwnProperty('ostehaps');"); CHECK(value->BooleanValue(isolate)); value = CompileRun( "var p = new constructor();" "p.hasOwnProperty('ostehaps');"); CHECK(!value->BooleanValue(isolate)); } 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(!value->BooleanValue(isolate)); } static void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter, v8::GenericNamedPropertyQueryCallback query, 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, nullptr, query, nullptr, nullptr, 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 CheckInterceptorLoadIC( v8::GenericNamedPropertyGetterCallback getter, const char* source, int expected) { CheckInterceptorIC(getter, nullptr, source, expected); } 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()); } // Test load of a non-existing global when a global object has an interceptor. THREADED_TEST(InterceptorLoadGlobalICGlobalWithInterceptor) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_global = v8::ObjectTemplate::New(isolate); templ_global->SetHandler(v8::NamedPropertyHandlerConfiguration( EmptyInterceptorGetter, EmptyInterceptorSetter)); LocalContext context(nullptr, templ_global); i::Handle global_proxy = v8::Utils::OpenHandle(context->Global()); CHECK(global_proxy->IsJSGlobalProxy()); i::Handle global( i::JSGlobalObject::cast(global_proxy->map().prototype()), global_proxy->GetIsolate()); CHECK(global->map().has_named_interceptor()); v8::Local value = CompileRun( "var f = function() { " " try {" " x1;" " } catch(e) {" " }" " return typeof x1 === 'undefined';" "};" "for (var i = 0; i < 10; i++) {" " f();" "};" "f();"); CHECK(value->BooleanValue(isolate)); value = CompileRun( "var f = function() { " " try {" " x2;" " return false;" " } catch(e) {" " return true;" " }" "};" "for (var i = 0; i < 10; i++) {" " f();" "};" "f();"); CHECK(value->BooleanValue(isolate)); value = CompileRun( "var f = function() { " " try {" " typeof(x3);" " return true;" " } catch(e) {" " return false;" " }" "};" "for (var i = 0; i < 10; i++) {" " f();" "};" "f();"); CHECK(value->BooleanValue(isolate)); } // Test load of a non-existing global through prototype chain when a global // object has an interceptor. THREADED_TEST(InterceptorLoadICGlobalWithInterceptor) { i::FLAG_allow_natives_syntax = true; v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ_global = v8::ObjectTemplate::New(isolate); templ_global->SetHandler(v8::NamedPropertyHandlerConfiguration( GenericInterceptorGetter, GenericInterceptorSetter)); LocalContext context(nullptr, templ_global); i::Handle global_proxy = v8::Utils::OpenHandle(context->Global()); CHECK(global_proxy->IsJSGlobalProxy()); i::Handle global( i::JSGlobalObject::cast(global_proxy->map().prototype()), global_proxy->GetIsolate()); CHECK(global->map().has_named_interceptor()); ExpectInt32( "(function() {" " var f = function(obj) { " " return obj.foo;" " };" " var obj = { __proto__: this, _str_foo: 42 };" " for (var i = 0; i < 1500; i++) obj['p' + i] = 0;" " /* Ensure that |obj| is in dictionary mode. */" " if (%HasFastProperties(obj)) return -1;" " for (var i = 0; i < 3; i++) {" " f(obj);" " };" " return f(obj);" "})();", 42); } 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); } namespace { template void HasICQuery(TKey name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); v8::Isolate* isolate = CcTest::isolate(); CHECK_EQ(isolate, info.GetIsolate()); info.GetReturnValue().Set(v8::Integer::New(isolate, attribute)); } template void HasICQueryToggle(TKey name, const v8::PropertyCallbackInfo& info) { ApiTestFuzzer::Fuzz(); static bool toggle = false; toggle = !toggle; v8::Isolate* isolate = CcTest::isolate(); CHECK_EQ(isolate, info.GetIsolate()); info.GetReturnValue().Set(v8::Integer::New( isolate, toggle ? v8::internal::ABSENT : v8::internal::NONE)); } int named_query_counter = 0; void NamedQueryCallback(Local name, const v8::PropertyCallbackInfo& info) { named_query_counter++; } } // namespace THREADED_TEST(InterceptorHasIC) { named_query_counter = 0; CheckInterceptorIC(nullptr, NamedQueryCallback, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " 'x' in o;" "}", 0); CHECK_EQ(1000, named_query_counter); } THREADED_TEST(InterceptorHasICQueryAbsent) { CheckInterceptorIC(nullptr, HasICQuery, v8::internal::ABSENT>, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " if ('x' in o) ++result;" "}", 0); } THREADED_TEST(InterceptorHasICQueryNone) { CheckInterceptorIC(nullptr, HasICQuery, v8::internal::NONE>, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " if ('x' in o) ++result;" "}", 1000); } THREADED_TEST(InterceptorHasICGetter) { CheckInterceptorIC(InterceptorLoadICGetter, nullptr, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " if ('x' in o) ++result;" "}", 1000); } THREADED_TEST(InterceptorHasICQueryGetter) { CheckInterceptorIC(InterceptorLoadICGetter, HasICQuery, v8::internal::ABSENT>, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " if ('x' in o) ++result;" "}", 0); } THREADED_TEST(InterceptorHasICQueryToggle) { CheckInterceptorIC(InterceptorLoadICGetter, HasICQueryToggle>, "var result = 0;" "for (var i = 0; i < 1000; i++) {" " if ('x' in o) ++result;" "}", 500); } 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, nullptr, nullptr, nullptr, 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); AddStringOnlyInterceptor(child, InterceptorGetter, InterceptorSetter); 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::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::FunctionTemplate::New(isolate); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( EchoNamedProperty, nullptr, nullptr, nullptr, nullptr, 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(0, echo_named_call_count); v8_compile("obj.x")->Run(env.local()).ToLocalChecked(); CHECK_EQ(1, echo_named_call_count); const char* code = "var str = 'oddle'; obj[str] + obj.poddle;"; v8::Local str = CompileRun(code); String::Utf8Value value(isolate, 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(isolate)); CHECK(v8_compile("delete obj.myProperty") ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); } namespace { void NotInterceptingPropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { // Do not intercept by not calling info.GetReturnValue().Set(). } void InterceptingPropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { // Intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } void CheckDescriptorInDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(!desc.has_writable()); CHECK(!desc.has_value()); CHECK(!desc.has_enumerable()); CHECK(desc.has_configurable()); CHECK(!desc.configurable()); CHECK(desc.has_get()); CHECK(desc.get()->IsFunction()); CHECK(desc.has_set()); CHECK(desc.set()->IsUndefined()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallback) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; { // Intercept defineProperty() v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, NotInterceptingPropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {value: 42});" "obj.x;"; CHECK_EQ(42, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } { // Intercept defineProperty() for correct accessor descriptor v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckDescriptorInDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {" "get: function(){ return 42; }, " "set: undefined," "configurable: 0" "});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } { // Do not intercept defineProperty() v8::Local templ2 = v8::FunctionTemplate::New(CcTest::isolate()); templ2->InstanceTemplate()->SetHandler( v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, InterceptingPropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ2->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {value: 42});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } } namespace { void NotInterceptingPropertyDefineCallbackIndexed( uint32_t index, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { // Do not intercept by not calling info.GetReturnValue().Set() } void InterceptingPropertyDefineCallbackIndexed( uint32_t index, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(index); } void CheckDescriptorInDefineCallbackIndexed( uint32_t index, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(!desc.has_writable()); CHECK(!desc.has_value()); CHECK(desc.has_enumerable()); CHECK(desc.enumerable()); CHECK(!desc.has_configurable()); CHECK(desc.has_get()); CHECK(desc.get()->IsFunction()); CHECK(desc.has_set()); CHECK(desc.set()->IsUndefined()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(index); } } // namespace THREADED_TEST(PropertyDefinerCallbackIndexed) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; { // Intercept defineProperty() v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler( v8::IndexedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, NotInterceptingPropertyDefineCallbackIndexed)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj[2] = 17; " "Object.defineProperty(obj, 2, {value: 42});" "obj[2];"; CHECK_EQ(42, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } { // Intercept defineProperty() for correct accessor descriptor v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler( v8::IndexedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckDescriptorInDefineCallbackIndexed)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj[2] = 17; " "Object.defineProperty(obj, 2, {" "get: function(){ return 42; }, " "set: undefined," "enumerable: true" "});" "obj[2];"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } { // Do not intercept defineProperty() v8::Local templ2 = v8::FunctionTemplate::New(CcTest::isolate()); templ2->InstanceTemplate()->SetHandler( v8::IndexedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, InterceptingPropertyDefineCallbackIndexed)); env->Global() ->Set(env.local(), v8_str("obj"), templ2->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj[2] = 17; " "Object.defineProperty(obj, 2, {value: 42});" "obj[2];"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } } // Test that freeze() is intercepted. THREADED_TEST(PropertyDefinerCallbackForFreeze) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(isolate); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, InterceptingPropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.freeze(obj.x); " "Object.isFrozen(obj.x);"; CHECK(v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->BooleanValue(isolate)); } // Check that the descriptor passed to the callback is enumerable. namespace { void CheckEnumerablePropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(desc.has_value()); CHECK_EQ(42, desc.value() ->Int32Value(info.GetIsolate()->GetCurrentContext()) .FromJust()); CHECK(desc.has_enumerable()); CHECK(desc.enumerable()); CHECK(!desc.has_writable()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallbackEnumerable) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckEnumerablePropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {value: 42, enumerable: true});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } // Check that the descriptor passed to the callback is configurable. namespace { void CheckConfigurablePropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(desc.has_value()); CHECK_EQ(42, desc.value() ->Int32Value(info.GetIsolate()->GetCurrentContext()) .FromJust()); CHECK(desc.has_configurable()); CHECK(desc.configurable()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallbackConfigurable) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckConfigurablePropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {value: 42, configurable: true});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } // Check that the descriptor passed to the callback is writable. namespace { void CheckWritablePropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(desc.has_writable()); CHECK(desc.writable()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallbackWritable) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckWritablePropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "Object.defineProperty(obj, 'x', {value: 42, writable: true});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } // Check that the descriptor passed to the callback has a getter. namespace { void CheckGetterPropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(desc.has_get()); CHECK(!desc.has_set()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallbackWithGetter) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckGetterPropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17;" "Object.defineProperty(obj, 'x', {get: function() {return 42;}});" "obj.x;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } // Check that the descriptor passed to the callback has a setter. namespace { void CheckSetterPropertyDefineCallback( Local name, const v8::PropertyDescriptor& desc, const v8::PropertyCallbackInfo& info) { CHECK(desc.has_set()); CHECK(!desc.has_get()); // intercept the callback by setting a non-empty handle info.GetReturnValue().Set(name); } } // namespace THREADED_TEST(PropertyDefinerCallbackWithSetter) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, nullptr, nullptr, nullptr, CheckSetterPropertyDefineCallback)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "Object.defineProperty(obj, 'x', {set: function() {return 42;}});" "obj.x = 17;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } namespace { void EmptyPropertyDescriptorCallback( Local name, const v8::PropertyCallbackInfo& info) { // Do not intercept by not calling info.GetReturnValue().Set(). } void InterceptingPropertyDescriptorCallback( Local name, const v8::PropertyCallbackInfo& info) { // Intercept the callback by setting a different descriptor. const char* code = "var desc = {value: 42};" "desc;"; Local descriptor = v8_compile(code) ->Run(info.GetIsolate()->GetCurrentContext()) .ToLocalChecked(); info.GetReturnValue().Set(descriptor); } } // namespace THREADED_TEST(PropertyDescriptorCallback) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; { // Normal behavior of getOwnPropertyDescriptor() with empty callback. v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, EmptyPropertyDescriptorCallback, nullptr, nullptr, nullptr)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "var desc = Object.getOwnPropertyDescriptor(obj, 'x');" "desc.value;"; CHECK_EQ(17, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } { // Intercept getOwnPropertyDescriptor(). v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( nullptr, nullptr, InterceptingPropertyDescriptorCallback, nullptr, nullptr, nullptr)); env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); const char* code = "obj.x = 17; " "var desc = Object.getOwnPropertyDescriptor(obj, 'x');" "desc.value;"; CHECK_EQ(42, v8_compile(code) ->Run(env.local()) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } } namespace { int echo_indexed_call_count = 0; } // namespace 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, nullptr, nullptr, nullptr, nullptr, v8_num(637))); LocalContext env; env->Global() ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) .ToLocalChecked() ->NewInstance(env.local()) .ToLocalChecked()) .FromJust(); Local