blob: e331d1a26ad84f1419864063a29208636d294a9b [file] [log] [blame]
// 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->Name()->IsUndefined()) return;
SimpleAccessorGetter(Local<String>::Cast(sym->Name()), 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->Name()->IsUndefined()) return;
SimpleAccessorSetter(Local<String>::Cast(sym->Name()), 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)->Name();
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)->Name();
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())