| // 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 <stdlib.h> |
| |
| #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<v8::Value>& info) { |
| info.GetReturnValue().Set(42); |
| } |
| |
| void Return239Callback(Local<String> name, |
| const v8::PropertyCallbackInfo<Value>& 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> name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) {} |
| |
| |
| void EmptyInterceptorSetter(Local<Name> name, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) {} |
| |
| void EmptyInterceptorQuery(Local<Name> name, |
| const v8::PropertyCallbackInfo<v8::Integer>& info) {} |
| |
| void EmptyInterceptorDeleter( |
| Local<Name> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) {} |
| |
| void EmptyInterceptorEnumerator( |
| const v8::PropertyCallbackInfo<v8::Array>& info) {} |
| |
| void SimpleAccessorGetter(Local<String> name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| Local<Object> self = Local<Object>::Cast(info.This()); |
| info.GetReturnValue().Set( |
| self->Get(info.GetIsolate()->GetCurrentContext(), |
| String::Concat(info.GetIsolate(), v8_str("accessor_"), name)) |
| .ToLocalChecked()); |
| } |
| |
| void SimpleAccessorSetter(Local<String> name, Local<Value> value, |
| const v8::PropertyCallbackInfo<void>& info) { |
| Local<Object> self = Local<Object>::Cast(info.This()); |
| self->Set(info.GetIsolate()->GetCurrentContext(), |
| String::Concat(info.GetIsolate(), v8_str("accessor_"), name), value) |
| .FromJust(); |
| } |
| |
| |
| void SymbolAccessorGetter(Local<Name> name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| CHECK(name->IsSymbol()); |
| Local<Symbol> sym = Local<Symbol>::Cast(name); |
| if (sym->Description()->IsUndefined()) return; |
| SimpleAccessorGetter(Local<String>::Cast(sym->Description()), info); |
| } |
| |
| void SymbolAccessorSetter(Local<Name> name, Local<Value> value, |
| const v8::PropertyCallbackInfo<void>& info) { |
| CHECK(name->IsSymbol()); |
| Local<Symbol> sym = Local<Symbol>::Cast(name); |
| if (sym->Description()->IsUndefined()) return; |
| SimpleAccessorSetter(Local<String>::Cast(sym->Description()), value, info); |
| } |
| |
| void InterceptorGetter(Local<Name> generic_name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| if (generic_name->IsSymbol()) return; |
| Local<String> name = Local<String>::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<Object> self = Local<Object>::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<Name> generic_name, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| if (generic_name->IsSymbol()) return; |
| Local<String> name = Local<String>::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> context = info.GetIsolate()->GetCurrentContext(); |
| if (value->IsInt32() && value->Int32Value(context).FromJust() < 10000) { |
| Local<Object> self = Local<Object>::Cast(info.This()); |
| Local<v8::Private> symbol = v8::Private::ForApi(info.GetIsolate(), name); |
| self->SetPrivate(context, symbol, value).FromJust(); |
| info.GetReturnValue().Set(value); |
| } |
| } |
| |
| void GenericInterceptorGetter(Local<Name> generic_name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| Local<String> str; |
| if (generic_name->IsSymbol()) { |
| Local<Value> name = Local<Symbol>::Cast(generic_name)->Description(); |
| if (name->IsUndefined()) return; |
| str = String::Concat(info.GetIsolate(), v8_str("_sym_"), |
| Local<String>::Cast(name)); |
| } else { |
| Local<String> name = Local<String>::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<Object> self = Local<Object>::Cast(info.This()); |
| info.GetReturnValue().Set( |
| self->Get(info.GetIsolate()->GetCurrentContext(), str).ToLocalChecked()); |
| } |
| |
| void GenericInterceptorSetter(Local<Name> generic_name, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| Local<String> str; |
| if (generic_name->IsSymbol()) { |
| Local<Value> name = Local<Symbol>::Cast(generic_name)->Description(); |
| if (name->IsUndefined()) return; |
| str = String::Concat(info.GetIsolate(), v8_str("_sym_"), |
| Local<String>::Cast(name)); |
| } else { |
| Local<String> name = Local<String>::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<Object> self = Local<Object>::Cast(info.This()); |
| self->Set(info.GetIsolate()->GetCurrentContext(), str, value).FromJust(); |
| info.GetReturnValue().Set(value); |
| } |
| |
| void AddAccessor(Local<FunctionTemplate> templ, Local<String> name, |
| v8::AccessorGetterCallback getter, |
| v8::AccessorSetterCallback setter) { |
| templ->PrototypeTemplate()->SetAccessor(name, getter, setter); |
| } |
| |
| void AddAccessor(Local<FunctionTemplate> templ, Local<Name> name, |
| v8::AccessorNameGetterCallback getter, |
| v8::AccessorNameSetterCallback setter) { |
| templ->PrototypeTemplate()->SetAccessor(name, getter, setter); |
| } |
| |
| void AddStringOnlyInterceptor(Local<FunctionTemplate> templ, |
| v8::GenericNamedPropertyGetterCallback getter, |
| v8::GenericNamedPropertySetterCallback setter) { |
| templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| getter, setter, nullptr, nullptr, nullptr, Local<v8::Value>(), |
| v8::PropertyHandlerFlags::kOnlyInterceptStrings)); |
| } |
| |
| void AddInterceptor(Local<FunctionTemplate> templ, |
| v8::GenericNamedPropertyGetterCallback getter, |
| v8::GenericNamedPropertySetterCallback setter) { |
| templ->InstanceTemplate()->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(getter, setter)); |
| } |
| |
| |
| v8::Local<v8::Object> bottom; |
| |
| void CheckThisIndexedPropertyHandler( |
| uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyHandler)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| void CheckThisNamedPropertyHandler( |
| Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& 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<v8::Value>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDefiner)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| void CheckThisNamedPropertyDefiner( |
| Local<Name> property, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDefiner)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| void CheckThisIndexedPropertySetter( |
| uint32_t index, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::Value>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDescriptor)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| void CheckThisNamedPropertyDescriptor( |
| Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDescriptor)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| void CheckThisNamedPropertySetter( |
| Local<Name> property, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::Integer>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyQuery)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| |
| void CheckThisNamedPropertyQuery( |
| Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& 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<v8::Boolean>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDeleter)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| |
| void CheckThisNamedPropertyDeleter( |
| Local<Name> property, const v8::PropertyCallbackInfo<v8::Boolean>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDeleter)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| |
| void CheckThisIndexedPropertyEnumerator( |
| const v8::PropertyCallbackInfo<v8::Array>& info) { |
| CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyEnumerator)); |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.This() |
| ->Equals(info.GetIsolate()->GetCurrentContext(), bottom) |
| .FromJust()); |
| } |
| |
| |
| void CheckThisNamedPropertyEnumerator( |
| const v8::PropertyCallbackInfo<v8::Array>& 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> name, |
| const v8::PropertyCallbackInfo<v8::Value>& 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> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| } |
| |
| void InterceptorHasOwnPropertyGetterGC( |
| Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| CcTest::CollectAllGarbage(); |
| } |
| |
| int query_counter_int = 0; |
| |
| void QueryCallback(Local<Name> property, |
| const v8::PropertyCallbackInfo<v8::Integer>& 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<v8::FunctionTemplate> 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<Value> result = |
| v8_compile("Object.getOwnPropertyDescriptor(obj, 'x');") |
| ->Run(env.local()) |
| .ToLocalChecked(); |
| CHECK_EQ(1, query_counter_int); |
| CHECK_EQ(v8::PropertyAttribute::None, |
| static_cast<v8::PropertyAttribute>( |
| 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<Name> property, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| get_was_called = true; |
| } |
| |
| void SetterCallback(Local<Name> property, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| set_was_called = true; |
| set_was_called_counter++; |
| } |
| |
| void InterceptingSetterCallback( |
| Local<Name> property, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback)); |
| v8::Local<v8::Context> ctx = |
| v8::Context::New(CcTest::isolate(), nullptr, object_template); |
| |
| set_was_called_counter = 0; |
| |
| // Declare function. |
| v8::Local<v8::String> 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> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Intercept the callback by setting a different descriptor. |
| descriptor_was_called++; |
| const char* code = |
| "var desc = {value: 5};" |
| "desc;"; |
| Local<Value> 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| nullptr, nullptr, PropertyDescriptorCallback, nullptr, nullptr, nullptr)); |
| v8::Local<v8::Context> ctx = |
| v8::Context::New(CcTest::isolate(), nullptr, object_template); |
| |
| descriptor_was_called = 0; |
| |
| // Declare function. |
| v8::Local<v8::String> 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<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| nullptr, nullptr, QueryCallbackSetDontDelete)); |
| v8::Local<v8::Context> ctx = |
| v8::Context::New(CcTest::isolate(), nullptr, object_template); |
| |
| // Declare and redeclare function. |
| v8::Local<v8::String> 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| nullptr, InterceptingSetterCallback, nullptr, nullptr, nullptr, |
| Local<Value>(), v8::PropertyHandlerFlags::kNonMasking)); |
| v8::Local<v8::Context> ctx = |
| v8::Context::New(CcTest::isolate(), nullptr, object_template); |
| |
| v8::Local<v8::String> 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback)); |
| v8::Local<v8::Context> ctx = |
| v8::Context::New(CcTest::isolate(), nullptr, object_template); |
| |
| set_was_called = false; |
| |
| v8::Local<v8::String> 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<Name> property, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| get_was_called_in_order = true; |
| CHECK(!define_was_called_in_order); |
| info.GetReturnValue().Set(property); |
| } |
| |
| void DefinerCallbackOrder(Local<Name> property, |
| const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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<Name> property, const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> templ = |
| v8::FunctionTemplate::New(CcTest::isolate()); |
| v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate(); |
| object_template->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(ReturnUndefinedGetterCallback)); |
| v8::Local<v8::Context> 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<v8::String> 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<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate); |
| Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate(); |
| instance_templ->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetter)); |
| Local<Function> function = |
| fun_templ->GetFunction(context.local()).ToLocalChecked(); |
| context->Global() |
| ->Set(context.local(), v8_str("constructor"), function) |
| .FromJust(); |
| v8::Local<Value> 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<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate); |
| Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate(); |
| instance_templ->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetterGC)); |
| Local<Function> 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> 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<v8::ObjectTemplate> 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> 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> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| CHECK_EQ(isolate, info.GetIsolate()); |
| v8::Local<v8::Context> 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> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| info.GetReturnValue().Set( |
| v8_str("x") |
| ->Equals(info.GetIsolate()->GetCurrentContext(), name) |
| .FromJust() |
| ? v8::Local<v8::Value>(v8::Integer::New(info.GetIsolate(), 42)) |
| : v8::Local<v8::Value>()); |
| } |
| |
| |
| 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> name, const v8::PropertyCallbackInfo<v8::Value>& 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<String> name, Local<Value> value, |
| const v8::PropertyCallbackInfo<void>& info) { |
| Local<Object>::Cast(info.This()) |
| ->CreateDataProperty(info.GetIsolate()->GetCurrentContext(), name, value) |
| .FromJust(); |
| } |
| |
| |
| THREADED_TEST(InterceptorLoadICWithCallbackOnHolder) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate); |
| templ_o->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); |
| v8::Local<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate); |
| templ_o->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); |
| v8::Local<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate); |
| templ_o->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); |
| v8::Local<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate); |
| templ_o->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter)); |
| v8::Local<v8::ObjectTemplate> 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> 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<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate); |
| templ_global->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| EmptyInterceptorGetter, EmptyInterceptorSetter)); |
| |
| LocalContext context(nullptr, templ_global); |
| i::Handle<i::JSReceiver> global_proxy = |
| v8::Utils::OpenHandle<Object, i::JSReceiver>(context->Global()); |
| CHECK(global_proxy->IsJSGlobalProxy()); |
| i::Handle<i::JSGlobalObject> global( |
| i::JSGlobalObject::cast(global_proxy->map().prototype()), |
| global_proxy->GetIsolate()); |
| CHECK(global->map().has_named_interceptor()); |
| |
| v8::Local<Value> 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<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate); |
| templ_global->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| GenericInterceptorGetter, GenericInterceptorSetter)); |
| |
| LocalContext context(nullptr, templ_global); |
| i::Handle<i::JSReceiver> global_proxy = |
| v8::Utils::OpenHandle<Object, i::JSReceiver>(context->Global()); |
| CHECK(global_proxy->IsJSGlobalProxy()); |
| i::Handle<i::JSGlobalObject> 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> name, const v8::PropertyCallbackInfo<v8::Value>& 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 <typename TKey, v8::internal::PropertyAttributes attribute> |
| void HasICQuery(TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) { |
| ApiTestFuzzer::Fuzz(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| CHECK_EQ(isolate, info.GetIsolate()); |
| info.GetReturnValue().Set(v8::Integer::New(isolate, attribute)); |
| } |
| |
| template <typename TKey> |
| void HasICQueryToggle(TKey name, |
| const v8::PropertyCallbackInfo<v8::Integer>& 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> name, |
| const v8::PropertyCallbackInfo<v8::Integer>& 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<Local<Name>, 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<Local<Name>, 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<Local<Name>, 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<Local<Name>>, |
| "var result = 0;" |
| "for (var i = 0; i < 1000; i++) {" |
| " if ('x' in o) ++result;" |
| "}", |
| 500); |
| } |
| |
| static void InterceptorStoreICSetter( |
| Local<Name> key, Local<Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| v8::Local<v8::Context> 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<v8::ObjectTemplate> 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<v8::ObjectTemplate> 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> 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<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate()); |
| Local<FunctionTemplate> 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<FunctionTemplate> parent = FunctionTemplate::New(isolate); |
| Local<FunctionTemplate> child = FunctionTemplate::New(isolate); |
| v8::Local<v8::Symbol> 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<FunctionTemplate> parent = FunctionTemplate::New(isolate); |
| Local<FunctionTemplate> child = FunctionTemplate::New(isolate); |
| v8::Local<v8::Symbol> age = v8::Symbol::New(isolate, v8_str("age")); |
| v8::Local<v8::Symbol> 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<v8::FunctionTemplate> 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<Value> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Do not intercept by not calling info.GetReturnValue().Set(). |
| } |
| |
| void InterceptingPropertyDefineCallback( |
| Local<Name> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Intercept the callback by setting a non-empty handle |
| info.GetReturnValue().Set(name); |
| } |
| |
| void CheckDescriptorInDefineCallback( |
| Local<Name> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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<v8::Value>& info) { |
| // Do not intercept by not calling info.GetReturnValue().Set() |
| } |
| |
| void InterceptingPropertyDefineCallbackIndexed( |
| uint32_t index, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::Value>& 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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> name, const v8::PropertyDescriptor& desc, |
| const v8::PropertyCallbackInfo<v8::Value>& 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<v8::FunctionTemplate> 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> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Do not intercept by not calling info.GetReturnValue().Set(). |
| } |
| |
| void InterceptingPropertyDescriptorCallback( |
| Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Intercept the callback by setting a different descriptor. |
| const char* code = |
| "var desc = {value: 42};" |
| "desc;"; |
| Local<Value> 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<v8::FunctionTemplate> 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<v8::FunctionTemplate> 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<v8::Value>& 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<v8::FunctionTemplate> 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<Script> script = v8_compile("obj[900]"); |
| CHECK_EQ(900, script->Run(env.local()) |
| .ToLocalChecked() |
| ->Int32Value(env.local()) |
| .FromJust()); |
| } |
| |
| |
| THREADED_TEST(PropertyHandlerInPrototype) { |
| LocalContext env; |
| v8::Isolate* isolate = env->GetIsolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate); |
| templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration( |
| CheckThisIndexedPropertyHandler, CheckThisIndexedPropertySetter, |
| CheckThisIndexedPropertyQuery, CheckThisIndexedPropertyDeleter, |
| CheckThisIndexedPropertyEnumerator)); |
| |
| templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| CheckThisNamedPropertyHandler, CheckThisNamedPropertySetter, |
| CheckThisNamedPropertyQuery, CheckThisNamedPropertyDeleter, |
| CheckThisNamedPropertyEnumerator)); |
| |
| bottom = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| Local<v8::Object> top = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| Local<v8::Object> middle = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| |
| bottom->SetPrototype(env.local(), middle).FromJust(); |
| middle->SetPrototype(env.local(), top).FromJust(); |
| env->Global()->Set(env.local(), v8_str("obj"), bottom).FromJust(); |
| |
| // Indexed and named get. |
| CompileRun("obj[0]"); |
| CompileRun("obj.x"); |
| |
| // Indexed and named set. |
| CompileRun("obj[1] = 42"); |
| CompileRun("obj.y = 42"); |
| |
| // Indexed and named query. |
| CompileRun("0 in obj"); |
| CompileRun("'x' in obj"); |
| |
| // Indexed and named deleter. |
| CompileRun("delete obj[0]"); |
| CompileRun("delete obj.x"); |
| |
| // Enumerators. |
| CompileRun("for (var p in obj) ;"); |
| } |
| |
| TEST(PropertyHandlerInPrototypeWithDefine) { |
| LocalContext env; |
| v8::Isolate* isolate = env->GetIsolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate); |
| templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration( |
| CheckThisIndexedPropertyHandler, CheckThisIndexedPropertySetter, |
| CheckThisIndexedPropertyDescriptor, CheckThisIndexedPropertyDeleter, |
| CheckThisIndexedPropertyEnumerator, CheckThisIndexedPropertyDefiner)); |
| |
| templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| CheckThisNamedPropertyHandler, CheckThisNamedPropertySetter, |
| CheckThisNamedPropertyDescriptor, CheckThisNamedPropertyDeleter, |
| CheckThisNamedPropertyEnumerator, CheckThisNamedPropertyDefiner)); |
| |
| bottom = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| Local<v8::Object> top = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| Local<v8::Object> middle = templ->GetFunction(env.local()) |
| .ToLocalChecked() |
| ->NewInstance(env.local()) |
| .ToLocalChecked(); |
| |
| bottom->SetPrototype(env.local(), middle).FromJust(); |
| middle->SetPrototype(env.local(), top).FromJust(); |
| env->Global()->Set(env.local(), v8_str("obj"), bottom).FromJust(); |
| |
| // Indexed and named get. |
| CompileRun("obj[0]"); |
| CompileRun("obj.x"); |
| |
| // Indexed and named set. |
| CompileRun("obj[1] = 42"); |
| CompileRun("obj.y = 42"); |
| |
| // Indexed and named deleter. |
| CompileRun("delete obj[0]"); |
| CompileRun("delete obj.x"); |
| |
| // Enumerators. |
| CompileRun("for (var p in obj) ;"); |
| |
| // Indexed and named definer. |
| CompileRun("Object.defineProperty(obj, 2, {});"); |
| CompileRun("Object.defineProperty(obj, 'z', {});"); |
| |
| // Indexed and named propertyDescriptor. |
| CompileRun("Object.getOwnPropertyDescriptor(obj, 2);"); |
| CompileRun("Object.getOwnPropertyDescriptor(obj, 'z');"); |
| } |
| |
| |
| bool is_bootstrapping = false; |
| static void PrePropertyHandlerGet( |
| Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| if (!is_bootstrapping && |
| v8_str("pre") |
| ->Equals(info.GetIsolate()->GetCurrentContext(), key) |
| .FromJust()) { |
| info.GetReturnValue().Set(v8_str("PrePropertyHandler: pre")); |
| } |
| } |
| |
| |
| static void PrePropertyHandlerQuery( |
| Local<Name> key, const v8::PropertyCallbackInfo<v8::Integer>& info) { |
| if (!is_bootstrapping && |
| v8_str("pre") |
| ->Equals(info.GetIsolate()->GetCurrentContext(), key) |
| .FromJust()) { |
| info.GetReturnValue().Set(static_cast<int32_t>(v8::None)); |
| } |
| } |
| |
| |
| THREADED_TEST(PrePropertyHandler) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate); |
| desc->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| PrePropertyHandlerGet, nullptr, PrePropertyHandlerQuery)); |
| is_bootstrapping = true; |
| LocalContext env(nullptr, desc->InstanceTemplate()); |
| is_bootstrapping = false; |
| CompileRun("var pre = 'Object: pre'; var on = 'Object: on';"); |
| v8::Local<Value> result_pre = CompileRun("pre"); |
| CHECK(v8_str("PrePropertyHandler: pre") |
| ->Equals(env.local(), result_pre) |
| .FromJust()); |
| v8::Local<Value> result_on = CompileRun("on"); |
| CHECK(v8_str("Object: on")->Equals(env.local(), result_on).FromJust()); |
| v8::Local<Value> result_post = CompileRun("post"); |
| CHECK(result_post.IsEmpty()); |
| } |
| |
| |
| THREADED_TEST(EmptyInterceptorBreakTransitions) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate()); |
| AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Constructor"), |
| templ->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var o1 = new Constructor;" |
| "o1.a = 1;" // Ensure a and x share the descriptor array. |
| "Object.defineProperty(o1, 'x', {value: 10});"); |
| CompileRun( |
| "var o2 = new Constructor;" |
| "o2.a = 1;" |
| "Object.defineProperty(o2, 'x', {value: 10});"); |
| } |
| |
| |
| THREADED_TEST(EmptyInterceptorDoesNotShadowJSAccessors) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(isolate); |
| Local<FunctionTemplate> child = FunctionTemplate::New(isolate); |
| child->Inherit(parent); |
| 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;" |
| "var parent = child.__proto__;" |
| "Object.defineProperty(parent, 'age', " |
| " {get: function(){ return this.accessor_age; }, " |
| " set: function(v){ this.accessor_age = v; }, " |
| " enumerable: true, configurable: true});" |
| "child.age = 10;"); |
| ExpectBoolean("child.hasOwnProperty('age')", false); |
| ExpectInt32("child.age", 10); |
| ExpectInt32("child.accessor_age", 10); |
| } |
| |
| |
| THREADED_TEST(EmptyInterceptorDoesNotShadowApiAccessors) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(isolate); |
| auto returns_42 = FunctionTemplate::New(isolate, Returns42); |
| parent->PrototypeTemplate()->SetAccessorProperty(v8_str("age"), returns_42); |
| Local<FunctionTemplate> child = FunctionTemplate::New(isolate); |
| child->Inherit(parent); |
| 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;" |
| "var parent = child.__proto__;"); |
| ExpectBoolean("child.hasOwnProperty('age')", false); |
| ExpectInt32("child.age", 42); |
| // Check interceptor followup. |
| ExpectInt32( |
| "var result;" |
| "for (var i = 0; i < 4; ++i) {" |
| " result = child.age;" |
| "}" |
| "result", |
| 42); |
| } |
| |
| |
| THREADED_TEST(EmptyInterceptorDoesNotAffectJSProperties) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(isolate); |
| Local<FunctionTemplate> child = FunctionTemplate::New(isolate); |
| child->Inherit(parent); |
| 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;" |
| "var parent = child.__proto__;" |
| "parent.name = 'Alice';"); |
| ExpectBoolean("child.hasOwnProperty('name')", false); |
| ExpectString("child.name", "Alice"); |
| CompileRun("child.name = 'Bob';"); |
| ExpectString("child.name", "Bob"); |
| ExpectBoolean("child.hasOwnProperty('name')", true); |
| ExpectString("parent.name", "Alice"); |
| } |
| |
| |
| THREADED_TEST(SwitchFromInterceptorToAccessor) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate()); |
| AddAccessor(templ, v8_str("age"), SimpleAccessorGetter, SimpleAccessorSetter); |
| AddInterceptor(templ, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Obj"), |
| templ->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var obj = new Obj;" |
| "function setAge(i){ obj.age = i; };" |
| "for(var i = 0; i <= 10000; i++) setAge(i);"); |
| // All i < 10000 go to the interceptor. |
| ExpectInt32("obj.interceptor_age", 9999); |
| // The last i goes to the accessor. |
| ExpectInt32("obj.accessor_age", 10000); |
| } |
| |
| |
| THREADED_TEST(SwitchFromAccessorToInterceptor) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate()); |
| AddAccessor(templ, v8_str("age"), SimpleAccessorGetter, SimpleAccessorSetter); |
| AddInterceptor(templ, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Obj"), |
| templ->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var obj = new Obj;" |
| "function setAge(i){ obj.age = i; };" |
| "for(var i = 20000; i >= 9999; i--) setAge(i);"); |
| // All i >= 10000 go to the accessor. |
| ExpectInt32("obj.accessor_age", 10000); |
| // The last i goes to the interceptor. |
| ExpectInt32("obj.interceptor_age", 9999); |
| } |
| |
| |
| THREADED_TEST(SwitchFromInterceptorToAccessorWithInheritance) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate()); |
| Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate()); |
| child->Inherit(parent); |
| AddAccessor(parent, v8_str("age"), SimpleAccessorGetter, |
| SimpleAccessorSetter); |
| AddInterceptor(child, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Child"), |
| child->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var child = new Child;" |
| "function setAge(i){ child.age = i; };" |
| "for(var i = 0; i <= 10000; i++) setAge(i);"); |
| // All i < 10000 go to the interceptor. |
| ExpectInt32("child.interceptor_age", 9999); |
| // The last i goes to the accessor. |
| ExpectInt32("child.accessor_age", 10000); |
| } |
| |
| |
| THREADED_TEST(SwitchFromAccessorToInterceptorWithInheritance) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate()); |
| Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate()); |
| child->Inherit(parent); |
| AddAccessor(parent, v8_str("age"), SimpleAccessorGetter, |
| SimpleAccessorSetter); |
| AddInterceptor(child, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Child"), |
| child->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var child = new Child;" |
| "function setAge(i){ child.age = i; };" |
| "for(var i = 20000; i >= 9999; i--) setAge(i);"); |
| // All i >= 10000 go to the accessor. |
| ExpectInt32("child.accessor_age", 10000); |
| // The last i goes to the interceptor. |
| ExpectInt32("child.interceptor_age", 9999); |
| } |
| |
| |
| THREADED_TEST(SwitchFromInterceptorToJSAccessor) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate()); |
| AddInterceptor(templ, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Obj"), |
| templ->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var obj = new Obj;" |
| "function setter(i) { this.accessor_age = i; };" |
| "function getter() { return this.accessor_age; };" |
| "function setAge(i) { obj.age = i; };" |
| "Object.defineProperty(obj, 'age', { get:getter, set:setter });" |
| "for(var i = 0; i <= 10000; i++) setAge(i);"); |
| // All i < 10000 go to the interceptor. |
| ExpectInt32("obj.interceptor_age", 9999); |
| // The last i goes to the JavaScript accessor. |
| ExpectInt32("obj.accessor_age", 10000); |
| // The installed JavaScript getter is still intact. |
| // This last part is a regression test for issue 1651 and relies on the fact |
| // that both interceptor and accessor are being installed on the same object. |
| ExpectInt32("obj.age", 10000); |
| ExpectBoolean("obj.hasOwnProperty('age')", true); |
| ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value"); |
| } |
| |
| |
| THREADED_TEST(SwitchFromJSAccessorToInterceptor) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate()); |
| AddInterceptor(templ, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Obj"), |
| templ->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var obj = new Obj;" |
| "function setter(i) { this.accessor_age = i; };" |
| "function getter() { return this.accessor_age; };" |
| "function setAge(i) { obj.age = i; };" |
| "Object.defineProperty(obj, 'age', { get:getter, set:setter });" |
| "for(var i = 20000; i >= 9999; i--) setAge(i);"); |
| // All i >= 10000 go to the accessor. |
| ExpectInt32("obj.accessor_age", 10000); |
| // The last i goes to the interceptor. |
| ExpectInt32("obj.interceptor_age", 9999); |
| // The installed JavaScript getter is still intact. |
| // This last part is a regression test for issue 1651 and relies on the fact |
| // that both interceptor and accessor are being installed on the same object. |
| ExpectInt32("obj.age", 10000); |
| ExpectBoolean("obj.hasOwnProperty('age')", true); |
| ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value"); |
| } |
| |
| |
| THREADED_TEST(SwitchFromInterceptorToProperty) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate()); |
| Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate()); |
| child->Inherit(parent); |
| AddInterceptor(child, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Child"), |
| child->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var child = new Child;" |
| "function setAge(i){ child.age = i; };" |
| "for(var i = 0; i <= 10000; i++) setAge(i);"); |
| // All i < 10000 go to the interceptor. |
| ExpectInt32("child.interceptor_age", 9999); |
| // The last i goes to child's own property. |
| ExpectInt32("child.age", 10000); |
| } |
| |
| |
| THREADED_TEST(SwitchFromPropertyToInterceptor) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate()); |
| Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate()); |
| child->Inherit(parent); |
| AddInterceptor(child, InterceptorGetter, InterceptorSetter); |
| LocalContext env; |
| env->Global() |
| ->Set(env.local(), v8_str("Child"), |
| child->GetFunction(env.local()).ToLocalChecked()) |
| .FromJust(); |
| CompileRun( |
| "var child = new Child;" |
| "function setAge(i){ child.age = i; };" |
| "for(var i = 20000; i >= 9999; i--) setAge(i);"); |
| // All i >= 10000 go to child's own property. |
| ExpectInt32("child.age", 10000); |
| // The last i goes to the interceptor. |
| ExpectInt32("child.interceptor_age", 9999); |
| } |
| |
| |
| static bool interceptor_for_hidden_properties_called; |
| static void InterceptorForHiddenProperties( |
| Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { |
| interceptor_for_hidden_properties_called = true; |
| } |
| |
| THREADED_TEST(NoSideEffectPropertyHandler) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| LocalContext context; |
| |
| Local<ObjectTemplate> templ = ObjectTemplate::New(isolate); |
| templ->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| EmptyInterceptorGetter, EmptyInterceptorSetter, EmptyInterceptorQuery, |
| EmptyInterceptorDeleter, EmptyInterceptorEnumerator)); |
| v8::Local<v8::Object> object = |
| templ->NewInstance(context.local()).ToLocalChecked(); |
| context->Global()->Set(context.local(), v8_str("obj"), object).FromJust(); |
| |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("obj.x"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("obj.x = 1"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("'x' in obj"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("delete obj.x"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| // Wrap the variable declaration since declaring globals is a side effect. |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("(function() { for (var p in obj) ; })()"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| |
| // Side-effect-free version. |
| Local<ObjectTemplate> templ2 = ObjectTemplate::New(isolate); |
| templ2->SetHandler(v8::NamedPropertyHandlerConfiguration( |
| EmptyInterceptorGetter, EmptyInterceptorSetter, EmptyInterceptorQuery, |
| EmptyInterceptorDeleter, EmptyInterceptorEnumerator, |
| v8::Local<v8::Value>(), v8::PropertyHandlerFlags::kHasNoSideEffect)); |
| v8::Local<v8::Object> object2 = |
| templ2->NewInstance(context.local()).ToLocalChecked(); |
| context->Global()->Set(context.local(), v8_str("obj2"), object2).FromJust(); |
| |
| v8::debug::EvaluateGlobal( |
| isolate, v8_str("obj2.x"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .ToLocalChecked(); |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("obj2.x = 1"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| v8::debug::EvaluateGlobal( |
| isolate, v8_str("'x' in obj2"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .ToLocalChecked(); |
| CHECK(v8::debug::EvaluateGlobal( |
| isolate, v8_str("delete obj2.x"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .IsEmpty()); |
| v8::debug::EvaluateGlobal( |
| isolate, v8_str("(function() { for (var p in obj2) ; })()"), |
| v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) |
| .ToLocalChecked(); |
| } |
| |
| THREADED_TEST(HiddenPropertiesWithInterceptors) { |
| LocalContext context; |
| v8::Isolate* isolate = context->GetIsolate(); |
| v8::HandleScope scope(isolate); |
| |
| interceptor_for_hidden_properties_called = false; |
| |
| v8::Local<v8::Private> key = |
| v8::Private::New(isolate, v8_str("api-test::hidden-key")); |
| |
| // Associate an interceptor with an object and start setting hidden values. |
| Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate); |
| Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate(); |
| instance_templ->SetHandler( |
| v8::NamedPropertyHandlerConfiguration(InterceptorForHiddenProperties)); |
| Local<v8::Function> function = |
| fun_templ->GetFunction(context.local()).ToLocalChecked(); |
| Local<v8::Object> obj = |
| function->NewInstance(context.local()).ToLocalChecked(); |
| CHECK(obj->SetPrivate(context.local(), key, v8::Integer::New(isolate, 2302)) |
| .FromJust()); |
| CHECK_EQ(2302, obj->GetPrivate(context.local(), key) |
| .ToLocalChecked() |
| ->Int32Value(context.local()) |
| .FromJust()); |
| CHECK(!interceptor_for_hidden_properties_called); |
| } |
| |
| |
| static void XPropertyGetter(Local<Name> property, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| ApiTestFuzzer::Fuzz(); |
| CHECK(info.Data()->IsUndefined()); |
| info.GetReturnValue().Set(property); |
| } |
| |
| |
| THREADED_TEST(NamedInterceptorPropertyRead) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| Local<ObjectTemplate> templ = ObjectTemplate::New(isolate); |
| templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter)); |
| LocalContext context; |
| context->Global() |
| ->Set(context.local(), v8_str("obj"), |
| templ->NewInstance(context.local()).ToLocalChecked()) |
| .FromJust(); |
| Local<Script |