368a20bcd7
... and add regression test for contextual stores to JSGlobalObject with interceptor in the prototype chain. Bug: chromium:1216437 Change-Id: Ibd344288c6327b35f3276f59517995d591acb967 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2944895 Commit-Queue: Igor Sheludko <ishell@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/master@{#75038}
5724 lines
200 KiB
C++
5724 lines
200 KiB
C++
// 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 "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::Context;
|
|
using ::v8::Function;
|
|
using ::v8::FunctionTemplate;
|
|
using ::v8::Local;
|
|
using ::v8::Name;
|
|
using ::v8::Object;
|
|
using ::v8::ObjectTemplate;
|
|
using ::v8::Script;
|
|
using ::v8::String;
|
|
using ::v8::Symbol;
|
|
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 = info.This().As<Object>();
|
|
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 = info.This().As<Object>();
|
|
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());
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Symbol> sym = name.As<Symbol>();
|
|
if (sym->Description(isolate)->IsUndefined()) return;
|
|
SimpleAccessorGetter(sym->Description(isolate).As<String>(), info);
|
|
}
|
|
|
|
void SymbolAccessorSetter(Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(name->IsSymbol());
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Symbol> sym = name.As<Symbol>();
|
|
if (sym->Description(isolate)->IsUndefined()) return;
|
|
SimpleAccessorSetter(sym->Description(isolate).As<String>(), value, info);
|
|
}
|
|
|
|
void InterceptorGetter(Local<Name> generic_name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
if (generic_name->IsSymbol()) return;
|
|
Local<String> name = generic_name.As<String>();
|
|
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 = info.This().As<Object>();
|
|
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 = generic_name.As<String>();
|
|
// 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 = info.This().As<Object>();
|
|
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) {
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<String> str;
|
|
if (generic_name->IsSymbol()) {
|
|
Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
|
|
if (name->IsUndefined()) return;
|
|
str = String::Concat(info.GetIsolate(), v8_str("_sym_"), name.As<String>());
|
|
} else {
|
|
Local<String> name = generic_name.As<String>();
|
|
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 = info.This().As<Object>();
|
|
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) {
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<String> str;
|
|
if (generic_name->IsSymbol()) {
|
|
Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
|
|
if (name->IsUndefined()) return;
|
|
str = String::Concat(info.GetIsolate(), v8_str("_sym_"), name.As<String>());
|
|
} else {
|
|
Local<String> name = generic_name.As<String>();
|
|
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 = info.This().As<Object>();
|
|
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));
|
|
}
|
|
|
|
namespace {
|
|
|
|
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());
|
|
}
|
|
|
|
void CheckInterceptorLoadIC(v8::GenericNamedPropertyGetterCallback getter,
|
|
const char* source, int expected) {
|
|
CheckInterceptorIC(getter, nullptr, source, expected);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// 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).
|
|
|
|
namespace {
|
|
|
|
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>());
|
|
}
|
|
|
|
void InterceptorLoadXICGetterWithSideEffects(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CompileRun("interceptor_getter_side_effect()");
|
|
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>());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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) {
|
|
info.This()
|
|
.As<Object>()
|
|
->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));
|
|
}
|
|
|
|
template <typename TKey, v8::internal::PropertyAttributes attribute>
|
|
void HasICQuerySideEffect(TKey name,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
CHECK_EQ(isolate, info.GetIsolate());
|
|
CompileRun("interceptor_query_side_effect()");
|
|
if (attribute != v8::internal::ABSENT) {
|
|
info.GetReturnValue().Set(v8::Integer::New(isolate, attribute));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
THREADED_TEST(InterceptorStoreICWithSideEffectfulCallbacks) {
|
|
CheckInterceptorIC(EmptyInterceptorGetter,
|
|
HasICQuerySideEffect<Local<Name>, v8::internal::ABSENT>,
|
|
"let r;"
|
|
"let inside_side_effect = false;"
|
|
"let interceptor_query_side_effect = function() {"
|
|
" if (!inside_side_effect) {"
|
|
" inside_side_effect = true;"
|
|
" r.x = 153;"
|
|
" inside_side_effect = false;"
|
|
" }"
|
|
"};"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" r = { __proto__: o };"
|
|
" r.x = i;"
|
|
"}",
|
|
19);
|
|
|
|
CheckInterceptorIC(InterceptorLoadXICGetterWithSideEffects,
|
|
nullptr, // query callback is not provided
|
|
"let r;"
|
|
"let inside_side_effect = false;"
|
|
"let interceptor_getter_side_effect = function() {"
|
|
" if (!inside_side_effect) {"
|
|
" inside_side_effect = true;"
|
|
" r.y = 153;"
|
|
" inside_side_effect = false;"
|
|
" }"
|
|
"};"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" r = { __proto__: o };"
|
|
" r.y = i;"
|
|
"}",
|
|
19);
|
|
}
|
|
|
|
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(EmptyInterceptorDoesNotShadowReadOnlyProperty) {
|
|
// Interceptor should not shadow readonly property 'x' on the prototype, and
|
|
// attempt to store to 'x' must throw.
|
|
CheckInterceptorIC(EmptyInterceptorGetter,
|
|
HasICQuery<Local<Name>, v8::internal::ABSENT>,
|
|
"'use strict';"
|
|
"let p = {};"
|
|
"Object.defineProperty(p, 'x', "
|
|
" {value: 153, writable: false});"
|
|
"o.__proto__ = p;"
|
|
"let result = 0;"
|
|
"let r;"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" r = { __proto__: o };"
|
|
" try {"
|
|
" r.x = i;"
|
|
" } catch (e) {"
|
|
" result++;"
|
|
" }"
|
|
"}"
|
|
"result",
|
|
20);
|
|
}
|
|
|
|
THREADED_TEST(InterceptorShadowsReadOnlyProperty) {
|
|
// Interceptor claims that it has a writable property 'x', so the existence
|
|
// of the readonly property 'x' on the prototype should not cause exceptions.
|
|
CheckInterceptorIC(InterceptorLoadXICGetter,
|
|
nullptr, // query callback
|
|
"'use strict';"
|
|
"let p = {};"
|
|
"Object.defineProperty(p, 'x', "
|
|
" {value: 153, writable: false});"
|
|
"o.__proto__ = p;"
|
|
"let result = 0;"
|
|
"let r;"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" r = { __proto__: o };"
|
|
" try {"
|
|
" r.x = i;"
|
|
" result++;"
|
|
" } catch (e) {}"
|
|
"}"
|
|
"result",
|
|
20);
|
|
}
|
|
|
|
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(EmptyInterceptorVsStoreGlobalICs) {
|
|
// In sloppy mode storing to global must succeed.
|
|
CheckInterceptorIC(EmptyInterceptorGetter,
|
|
HasICQuery<Local<Name>, v8::internal::ABSENT>,
|
|
"globalThis.__proto__ = o;"
|
|
"let result = 0;"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" try {"
|
|
" x = i;"
|
|
" result++;"
|
|
" } catch (e) {}"
|
|
"}"
|
|
"result + x",
|
|
20 + 19);
|
|
|
|
// In strict mode storing to global must throw.
|
|
CheckInterceptorIC(EmptyInterceptorGetter,
|
|
HasICQuery<Local<Name>, v8::internal::ABSENT>,
|
|
"'use strict';"
|
|
"globalThis.__proto__ = o;"
|
|
"let result = 0;"
|
|
"for (var i = 0; i < 20; i++) {"
|
|
" try {"
|
|
" x = i;"
|
|
" } catch (e) {"
|
|
" result++;"
|
|
" }"
|
|
"}"
|
|
"result + (typeof(x) === 'undefined' ? 100 : 0)",
|
|
120);
|
|
}
|
|
|
|
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> script = v8_compile("obj.x");
|
|
for (int i = 0; i < 10; i++) {
|
|
Local<Value> result = script->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(NamedInterceptorDictionaryIC) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter));
|
|
LocalContext context;
|
|
// Create an object with a named interceptor.
|
|
context->Global()
|
|
->Set(context.local(), v8_str("interceptor_obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
Local<Script> script = v8_compile("interceptor_obj.x");
|
|
for (int i = 0; i < 10; i++) {
|
|
Local<Value> result = script->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
|
|
}
|
|
// Create a slow case object and a function accessing a property in
|
|
// that slow case object (with dictionary probing in generated
|
|
// code). Then force object with a named interceptor into slow-case,
|
|
// pass it to the function, and check that the interceptor is called
|
|
// instead of accessing the local property.
|
|
Local<Value> result = CompileRun(
|
|
"function get_x(o) { return o.x; };"
|
|
"var obj = { x : 42, y : 0 };"
|
|
"delete obj.y;"
|
|
"for (var i = 0; i < 10; i++) get_x(obj);"
|
|
"interceptor_obj.x = 42;"
|
|
"interceptor_obj.y = 10;"
|
|
"delete interceptor_obj.y;"
|
|
"get_x(interceptor_obj)");
|
|
CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(NamedInterceptorDictionaryICMultipleContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
|
|
context1->Enter();
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter));
|
|
// Create an object with a named interceptor.
|
|
v8::Local<v8::Object> object = templ->NewInstance(context1).ToLocalChecked();
|
|
context1->Global()
|
|
->Set(context1, v8_str("interceptor_obj"), object)
|
|
.FromJust();
|
|
|
|
// Force the object into the slow case.
|
|
CompileRun(
|
|
"interceptor_obj.y = 0;"
|
|
"delete interceptor_obj.y;");
|
|
context1->Exit();
|
|
|
|
{
|
|
// Introduce the object into a different context.
|
|
// Repeat named loads to exercise ICs.
|
|
LocalContext context2;
|
|
context2->Global()
|
|
->Set(context2.local(), v8_str("interceptor_obj"), object)
|
|
.FromJust();
|
|
Local<Value> result = CompileRun(
|
|
"function get_x(o) { return o.x; }"
|
|
"interceptor_obj.x = 42;"
|
|
"for (var i=0; i != 10; i++) {"
|
|
" get_x(interceptor_obj);"
|
|
"}"
|
|
"get_x(interceptor_obj)");
|
|
// Check that the interceptor was actually invoked.
|
|
CHECK(result->Equals(context2.local(), v8_str("x")).FromJust());
|
|
}
|
|
|
|
// Return to the original context and force some object to the slow case
|
|
// to cause the NormalizedMapCache to verify.
|
|
context1->Enter();
|
|
CompileRun("var obj = { x : 0 }; delete obj.x;");
|
|
context1->Exit();
|
|
}
|
|
|
|
|
|
static void SetXOnPrototypeGetter(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
// Set x on the prototype object and do not handle the get request.
|
|
v8::Local<v8::Value> proto = info.Holder()->GetPrototype();
|
|
proto.As<v8::Object>()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("x"),
|
|
v8::Integer::New(info.GetIsolate(), 23))
|
|
.FromJust();
|
|
}
|
|
|
|
|
|
// This is a regression test for http://crbug.com/20104. Map
|
|
// transitions should not interfere with post interceptor lookup.
|
|
THREADED_TEST(NamedInterceptorMapTransitionRead) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::FunctionTemplate> function_template =
|
|
v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_template =
|
|
function_template->InstanceTemplate();
|
|
instance_template->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(SetXOnPrototypeGetter));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("F"),
|
|
function_template->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
// Create an instance of F and introduce a map transition for x.
|
|
CompileRun("var o = new F(); o.x = 23;");
|
|
// Create an instance of F and invoke the getter. The result should be 23.
|
|
Local<Value> result = CompileRun("o = new F(); o.x");
|
|
CHECK_EQ(23, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static void IndexedPropertyGetter(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index == 37) {
|
|
info.GetReturnValue().Set(v8_num(625));
|
|
}
|
|
}
|
|
|
|
|
|
static void IndexedPropertySetter(
|
|
uint32_t index, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index == 39) {
|
|
info.GetReturnValue().Set(value);
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithIndexedAccessor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
IndexedPropertyGetter, IndexedPropertySetter));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
Local<Script> getter_script =
|
|
v8_compile("obj.__defineGetter__(\"3\", function(){return 5;});obj[3];");
|
|
Local<Script> setter_script = v8_compile(
|
|
"obj.__defineSetter__(\"17\", function(val){this.foo = val;});"
|
|
"obj[17] = 23;"
|
|
"obj.foo;");
|
|
Local<Script> interceptor_setter_script = v8_compile(
|
|
"obj.__defineSetter__(\"39\", function(val){this.foo = \"hit\";});"
|
|
"obj[39] = 47;"
|
|
"obj.foo;"); // This setter should not run, due to the interceptor.
|
|
Local<Script> interceptor_getter_script = v8_compile("obj[37];");
|
|
Local<Value> result = getter_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(5)->Equals(context.local(), result).FromJust());
|
|
result = setter_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(23)->Equals(context.local(), result).FromJust());
|
|
result = interceptor_setter_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(23)->Equals(context.local(), result).FromJust());
|
|
result = interceptor_getter_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(625)->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
|
|
static void UnboxedDoubleIndexedPropertyGetter(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index < 25) {
|
|
info.GetReturnValue().Set(v8_num(index));
|
|
}
|
|
}
|
|
|
|
|
|
static void UnboxedDoubleIndexedPropertySetter(
|
|
uint32_t index, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index < 25) {
|
|
info.GetReturnValue().Set(v8_num(index));
|
|
}
|
|
}
|
|
|
|
|
|
void UnboxedDoubleIndexedPropertyEnumerator(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
// Force the list of returned keys to be stored in a FastDoubleArray.
|
|
Local<Script> indexed_property_names_script = v8_compile(
|
|
"keys = new Array(); keys[125000] = 1;"
|
|
"for(i = 0; i < 80000; i++) { keys[i] = i; };"
|
|
"keys.length = 25; keys;");
|
|
Local<Value> result =
|
|
indexed_property_names_script->Run(info.GetIsolate()->GetCurrentContext())
|
|
.ToLocalChecked();
|
|
info.GetReturnValue().Set(result.As<v8::Array>());
|
|
}
|
|
|
|
|
|
// Make sure that the the interceptor code in the runtime properly handles
|
|
// merging property name lists for double-array-backed arrays.
|
|
THREADED_TEST(IndexedInterceptorUnboxedDoubleWithIndexedAccessor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
UnboxedDoubleIndexedPropertyGetter, UnboxedDoubleIndexedPropertySetter,
|
|
nullptr, nullptr, UnboxedDoubleIndexedPropertyEnumerator));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
// When obj is created, force it to be Stored in a FastDoubleArray.
|
|
Local<Script> create_unboxed_double_script = v8_compile(
|
|
"obj[125000] = 1; for(i = 0; i < 80000; i+=2) { obj[i] = i; } "
|
|
"key_count = 0; "
|
|
"for (x in obj) {key_count++;};"
|
|
"obj;");
|
|
Local<Value> result =
|
|
create_unboxed_double_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->HasRealIndexedProperty(context.local(), 2000)
|
|
.FromJust());
|
|
Local<Script> key_count_check = v8_compile("key_count;");
|
|
result = key_count_check->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(40013)->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
|
|
void SloppyArgsIndexedPropertyEnumerator(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
// Force the list of returned keys to be stored in a Arguments object.
|
|
Local<Script> indexed_property_names_script = v8_compile(
|
|
"function f(w,x) {"
|
|
" return arguments;"
|
|
"}"
|
|
"keys = f(0, 1, 2, 3);"
|
|
"keys;");
|
|
Local<Object> result =
|
|
indexed_property_names_script->Run(info.GetIsolate()->GetCurrentContext())
|
|
.ToLocalChecked()
|
|
.As<Object>();
|
|
// Have to populate the handle manually, as it's not Cast-able.
|
|
i::Handle<i::JSReceiver> o =
|
|
v8::Utils::OpenHandle<Object, i::JSReceiver>(result);
|
|
i::Handle<i::JSArray> array(i::JSArray::unchecked_cast(*o), o->GetIsolate());
|
|
info.GetReturnValue().Set(v8::Utils::ToLocal(array));
|
|
}
|
|
|
|
|
|
static void SloppyIndexedPropertyGetter(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index < 4) {
|
|
info.GetReturnValue().Set(v8_num(index));
|
|
}
|
|
}
|
|
|
|
|
|
// Make sure that the the interceptor code in the runtime properly handles
|
|
// merging property name lists for non-string arguments arrays.
|
|
THREADED_TEST(IndexedInterceptorSloppyArgsWithIndexedAccessor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
SloppyIndexedPropertyGetter, nullptr, nullptr, nullptr,
|
|
SloppyArgsIndexedPropertyEnumerator));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
Local<Script> create_args_script = v8_compile(
|
|
"var key_count = 0;"
|
|
"for (x in obj) {key_count++;} key_count;");
|
|
Local<Value> result =
|
|
create_args_script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(4)->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
|
|
static void IdentityIndexedPropertyGetter(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(index);
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithGetOwnPropertyDescriptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
// Check fast object case.
|
|
const char* fast_case_code =
|
|
"Object.getOwnPropertyDescriptor(obj, 0).value.toString()";
|
|
ExpectString(fast_case_code, "0");
|
|
|
|
// Check slow case.
|
|
const char* slow_case_code =
|
|
"obj.x = 1; delete obj.x;"
|
|
"Object.getOwnPropertyDescriptor(obj, 1).value.toString()";
|
|
ExpectString(slow_case_code, "1");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithNoSetter) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
const char* code =
|
|
"try {"
|
|
" obj[0] = 239;"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var v = obj[0];"
|
|
" if (v != 0) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
static bool AccessAlwaysBlocked(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> accessed_object,
|
|
Local<v8::Value> data) {
|
|
return false;
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithAccessorCheck) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
templ->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"var result = 'PASSED';"
|
|
"for (var i = 0; i < 100; i++) {"
|
|
" try {"
|
|
" var v = obj[0];"
|
|
" result = 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" break;"
|
|
" } catch (e) {"
|
|
" /* pass */"
|
|
" }"
|
|
"}"
|
|
"result";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithDifferentIndices) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var v = obj[i];"
|
|
" if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithNegativeIndices) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var expected = i;"
|
|
" var key = i;"
|
|
" if (i == 25) {"
|
|
" key = -1;"
|
|
" expected = undefined;"
|
|
" }"
|
|
" if (i == 50) {"
|
|
" /* probe minimal Smi number on 32-bit platforms */"
|
|
" key = -(1 << 30);"
|
|
" expected = undefined;"
|
|
" }"
|
|
" if (i == 75) {"
|
|
" /* probe minimal Smi number on 64-bit platforms */"
|
|
" key = 1 << 31;"
|
|
" expected = undefined;"
|
|
" }"
|
|
" var v = obj[key];"
|
|
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorWithNotSmiLookup) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var expected = i;"
|
|
" var key = i;"
|
|
" if (i == 50) {"
|
|
" key = 'foobar';"
|
|
" expected = undefined;"
|
|
" }"
|
|
" var v = obj[key];"
|
|
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorGoingMegamorphic) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"var original = obj;"
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var expected = i;"
|
|
" if (i == 50) {"
|
|
" obj = {50: 'foobar'};"
|
|
" expected = 'foobar';"
|
|
" }"
|
|
" var v = obj[i];"
|
|
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" if (i == 50) obj = original;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorReceiverTurningSmi) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"var original = obj;"
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var expected = i;"
|
|
" if (i == 5) {"
|
|
" obj = 239;"
|
|
" expected = undefined;"
|
|
" }"
|
|
" var v = obj[i];"
|
|
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" if (i == 5) obj = original;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
|
|
THREADED_TEST(IndexedInterceptorOnProto) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
|
|
const char* code =
|
|
"var o = {__proto__: obj};"
|
|
"try {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" var v = o[i];"
|
|
" if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
|
|
" }"
|
|
" 'PASSED'"
|
|
"} catch(e) {"
|
|
" e"
|
|
"}";
|
|
ExpectString(code, "PASSED");
|
|
}
|
|
|
|
namespace {
|
|
|
|
void CheckIndexedInterceptorHasIC(v8::IndexedPropertyGetterCallback getter,
|
|
v8::IndexedPropertyQueryCallback 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::IndexedPropertyHandlerConfiguration(
|
|
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());
|
|
}
|
|
|
|
int indexed_query_counter = 0;
|
|
void IndexedQueryCallback(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
indexed_query_counter++;
|
|
}
|
|
|
|
void IndexHasICQueryAbsent(uint32_t index,
|
|
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, v8::internal::ABSENT));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
THREADED_TEST(IndexedInterceptorHasIC) {
|
|
indexed_query_counter = 0;
|
|
CheckIndexedInterceptorHasIC(nullptr, IndexedQueryCallback,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" i in o;"
|
|
"}",
|
|
0);
|
|
CHECK_EQ(1000, indexed_query_counter);
|
|
}
|
|
|
|
THREADED_TEST(IndexedInterceptorHasICQueryAbsent) {
|
|
CheckIndexedInterceptorHasIC(nullptr,
|
|
// HasICQuery<uint32_t, v8::internal::ABSENT>,
|
|
IndexHasICQueryAbsent,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" if (i in o) ++result;"
|
|
"}",
|
|
0);
|
|
}
|
|
|
|
THREADED_TEST(IndexedInterceptorHasICQueryNone) {
|
|
CheckIndexedInterceptorHasIC(nullptr,
|
|
HasICQuery<uint32_t, v8::internal::NONE>,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" if (i in o) ++result;"
|
|
"}",
|
|
1000);
|
|
}
|
|
|
|
THREADED_TEST(IndexedInterceptorHasICGetter) {
|
|
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, nullptr,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" if (i in o) ++result;"
|
|
"}",
|
|
1000);
|
|
}
|
|
|
|
THREADED_TEST(IndexedInterceptorHasICQueryGetter) {
|
|
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
|
|
HasICQuery<uint32_t, v8::internal::ABSENT>,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" if (i in o) ++result;"
|
|
"}",
|
|
0);
|
|
}
|
|
|
|
THREADED_TEST(IndexedInterceptorHasICQueryToggle) {
|
|
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
|
|
HasICQueryToggle<uint32_t>,
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" if (i in o) ++result;"
|
|
"}",
|
|
500);
|
|
}
|
|
|
|
static void NoBlockGetterX(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>&) {}
|
|
|
|
|
|
static void NoBlockGetterI(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Value>&) {}
|
|
|
|
|
|
static void PDeleter(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Boolean>& info) {
|
|
if (!name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust()) {
|
|
return; // not intercepted
|
|
}
|
|
|
|
info.GetReturnValue().Set(false); // intercepted, don't delete the property
|
|
}
|
|
|
|
|
|
static void IDeleter(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Boolean>& info) {
|
|
if (index != 2) {
|
|
return; // not intercepted
|
|
}
|
|
|
|
info.GetReturnValue().Set(false); // intercepted, don't delete the property
|
|
}
|
|
|
|
|
|
THREADED_TEST(Deleter) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
NoBlockGetterX, nullptr, nullptr, PDeleter, nullptr));
|
|
obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
NoBlockGetterI, nullptr, nullptr, IDeleter, nullptr));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("k"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"k.foo = 'foo';"
|
|
"k.bar = 'bar';"
|
|
"k[2] = 2;"
|
|
"k[4] = 4;");
|
|
CHECK(v8_compile("delete k.foo")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->IsFalse());
|
|
CHECK(v8_compile("delete k.bar")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->IsTrue());
|
|
|
|
CHECK(v8_compile("k.foo")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), v8_str("foo"))
|
|
.FromJust());
|
|
CHECK(v8_compile("k.bar")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
|
|
CHECK(v8_compile("delete k[2]")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->IsFalse());
|
|
CHECK(v8_compile("delete k[4]")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->IsTrue());
|
|
|
|
CHECK(v8_compile("k[2]")
|
|
->Run(context.local())
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), v8_num(2))
|
|
.FromJust());
|
|
CHECK(
|
|
v8_compile("k[4]")->Run(context.local()).ToLocalChecked()->IsUndefined());
|
|
}
|
|
|
|
|
|
static void GetK(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
if (name->Equals(context, v8_str("foo")).FromJust() ||
|
|
name->Equals(context, v8_str("bar")).FromJust() ||
|
|
name->Equals(context, v8_str("baz")).FromJust()) {
|
|
info.GetReturnValue().SetUndefined();
|
|
}
|
|
}
|
|
|
|
|
|
static void IndexedGetK(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (index == 0 || index == 1) info.GetReturnValue().SetUndefined();
|
|
}
|
|
|
|
|
|
static void NamedEnum(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 3);
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("foo"))
|
|
.FromJust());
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("bar"))
|
|
.FromJust());
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_str("baz"))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
static void IndexedEnum(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 2);
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
CHECK(
|
|
result->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("0"))
|
|
.FromJust());
|
|
CHECK(
|
|
result->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("1"))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
THREADED_TEST(Enumerators) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(GetK, nullptr, nullptr,
|
|
nullptr, NamedEnum));
|
|
obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
IndexedGetK, nullptr, nullptr, nullptr, IndexedEnum));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("k"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<v8::Array> result = CompileRun(
|
|
"k[10] = 0;"
|
|
"k.a = 0;"
|
|
"k[5] = 0;"
|
|
"k.b = 0;"
|
|
"k[4294967294] = 0;"
|
|
"k.c = 0;"
|
|
"k[4294967295] = 0;"
|
|
"k.d = 0;"
|
|
"k[140000] = 0;"
|
|
"k.e = 0;"
|
|
"k[30000000000] = 0;"
|
|
"k.f = 0;"
|
|
"var result = [];"
|
|
"for (var prop in k) {"
|
|
" result.push(prop);"
|
|
"}"
|
|
"result")
|
|
.As<v8::Array>();
|
|
// Check that we get all the property names returned including the
|
|
// ones from the enumerators in the right order: indexed properties
|
|
// in numerical order, indexed interceptor properties, named
|
|
// properties in insertion order, named interceptor properties.
|
|
// This order is not mandated by the spec, so this test is just
|
|
// documenting our behavior.
|
|
CHECK_EQ(17u, result->Length());
|
|
// Indexed properties.
|
|
CHECK(v8_str("5")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("10")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("140000")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("4294967294")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 3))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
// Indexed Interceptor properties
|
|
CHECK(v8_str("0")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 4))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("1")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 5))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
// Named properties in insertion order.
|
|
CHECK(v8_str("a")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 6))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("b")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 7))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("c")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 8))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("4294967295")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 9))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("d")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 10))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("e")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 11))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("30000000000")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 12))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("f")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 13))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
// Named interceptor properties.
|
|
CHECK(v8_str("foo")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 14))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("bar")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 15))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("baz")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), v8::Integer::New(isolate, 16))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
v8::Local<Value> call_ic_function;
|
|
v8::Local<Value> call_ic_function2;
|
|
v8::Local<Value> call_ic_function3;
|
|
|
|
static void InterceptorCallICGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK(v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust());
|
|
info.GetReturnValue().Set(call_ic_function);
|
|
}
|
|
|
|
|
|
// This test should hit the call IC for the interceptor case.
|
|
THREADED_TEST(InterceptorCallIC) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
call_ic_function = v8_compile("function f(x) { return x + 1; }; f")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" result = o.x(41);"
|
|
"}");
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// This test checks that if interceptor doesn't provide
|
|
// a value, we can fetch regular value.
|
|
THREADED_TEST(InterceptorCallICSeesOthers) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<Value> value = CompileRun(
|
|
"o.x = function f(x) { return x + 1; };"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" result = o.x(41);"
|
|
"}");
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static v8::Local<Value> call_ic_function4;
|
|
static void InterceptorCallICGetter4(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK(v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust());
|
|
info.GetReturnValue().Set(call_ic_function4);
|
|
}
|
|
|
|
|
|
// This test checks that if interceptor provides a function,
|
|
// even if we cached shadowed variant, interceptor's function
|
|
// is invoked
|
|
THREADED_TEST(InterceptorCallICCacheableNotNeeded) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter4));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
call_ic_function4 = v8_compile("function f(x) { return x - 1; }; f")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"Object.getPrototypeOf(o).x = function(x) { return x + 1; };"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" result = o.x(42);"
|
|
"}");
|
|
CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when we stored cacheable lookup into
|
|
// a stub, but it got invalidated later on
|
|
THREADED_TEST(InterceptorCallICInvalidatedCacheable) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<Value> value = CompileRun(
|
|
"proto1 = new Object();"
|
|
"proto2 = new Object();"
|
|
"o.__proto__ = proto1;"
|
|
"proto1.__proto__ = proto2;"
|
|
"proto2.y = function(x) { return x + 1; };"
|
|
// Invoke it many times to compile a stub
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" o.y(42);"
|
|
"}"
|
|
"proto1.y = function(x) { return x - 1; };"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" result += o.y(42);"
|
|
"}");
|
|
CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// This test checks that if interceptor doesn't provide a function,
|
|
// cached constant function is used
|
|
THREADED_TEST(InterceptorCallICConstantFunctionUsed) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"o.x = inc;"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" result = o.x(42);"
|
|
"}");
|
|
CHECK_EQ(43, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static v8::Local<Value> call_ic_function5;
|
|
static void InterceptorCallICGetter5(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust())
|
|
info.GetReturnValue().Set(call_ic_function5);
|
|
}
|
|
|
|
|
|
// This test checks that if interceptor provides a function,
|
|
// even if we cached constant function, interceptor's function
|
|
// is invoked
|
|
THREADED_TEST(InterceptorCallICConstantFunctionNotNeeded) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter5));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
call_ic_function5 = v8_compile("function f(x) { return x - 1; }; f")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"o.x = inc;"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 1000; i++) {"
|
|
" result = o.x(42);"
|
|
"}");
|
|
CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static v8::Local<Value> call_ic_function6;
|
|
static void InterceptorCallICGetter6(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust())
|
|
info.GetReturnValue().Set(call_ic_function6);
|
|
}
|
|
|
|
|
|
// Same test as above, except the code is wrapped in a function
|
|
// to test the optimized compiler.
|
|
THREADED_TEST(InterceptorCallICConstantFunctionNotNeededWrapped) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter6));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
call_ic_function6 = v8_compile("function f(x) { return x - 1; }; f")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"o.x = inc;"
|
|
"function test() {"
|
|
" var result = 0;"
|
|
" for (var i = 0; i < 1000; i++) {"
|
|
" result = o.x(42);"
|
|
" }"
|
|
" return result;"
|
|
"};"
|
|
"%PrepareFunctionForOptimization(test);"
|
|
"test();"
|
|
"test();"
|
|
"test();"
|
|
"%OptimizeFunctionOnNextCall(test);"
|
|
"test()");
|
|
CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when we stored constant function into
|
|
// a stub, but it got invalidated later on
|
|
THREADED_TEST(InterceptorCallICInvalidatedConstantFunction) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"proto1 = new Object();"
|
|
"proto2 = new Object();"
|
|
"o.__proto__ = proto1;"
|
|
"proto1.__proto__ = proto2;"
|
|
"proto2.y = inc;"
|
|
// Invoke it many times to compile a stub
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" o.y(42);"
|
|
"}"
|
|
"proto1.y = function(x) { return x - 1; };"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" result += o.y(42);"
|
|
"}");
|
|
CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when we stored constant function into
|
|
// a stub, but it got invalidated later on due to override on
|
|
// global object which is between interceptor and constant function' holders.
|
|
THREADED_TEST(InterceptorCallICInvalidatedConstantFunctionViaGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"o.__proto__ = this;"
|
|
"this.__proto__.y = inc;"
|
|
// Invoke it many times to compile a stub
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" if (o.y(42) != 43) throw 'oops: ' + o.y(42);"
|
|
"}"
|
|
"this.y = function(x) { return x - 1; };"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 7; i++) {"
|
|
" result += o.y(42);"
|
|
"}");
|
|
CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when actual function to call sits on global object.
|
|
THREADED_TEST(InterceptorCallICCachedFromGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
|
|
templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ_o->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
v8::Local<Value> value = CompileRun(
|
|
"try {"
|
|
" o.__proto__ = this;"
|
|
" for (var i = 0; i < 10; i++) {"
|
|
" var v = o.parseFloat('239');"
|
|
" if (v != 239) throw v;"
|
|
// Now it should be ICed and keep a reference to parseFloat.
|
|
" }"
|
|
" var result = 0;"
|
|
" for (var i = 0; i < 10; i++) {"
|
|
" result += o.parseFloat('239');"
|
|
" }"
|
|
" result"
|
|
"} catch(e) {"
|
|
" e"
|
|
"};");
|
|
CHECK_EQ(239 * 10, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
v8::Local<Value> keyed_call_ic_function;
|
|
|
|
static void InterceptorKeyedCallICGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust()) {
|
|
info.GetReturnValue().Set(keyed_call_ic_function);
|
|
}
|
|
}
|
|
|
|
|
|
// Test the case when we stored cacheable lookup into
|
|
// a stub, but the function name changed (to another cacheable function).
|
|
THREADED_TEST(InterceptorKeyedCallICKeyChange1) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"proto = new Object();"
|
|
"proto.y = function(x) { return x + 1; };"
|
|
"proto.z = function(x) { return x - 1; };"
|
|
"o.__proto__ = proto;"
|
|
"var result = 0;"
|
|
"var method = 'y';"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) { method = 'z'; };"
|
|
" result += o[method](41);"
|
|
"}");
|
|
CHECK_EQ(42 * 5 + 40 * 5, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when we stored cacheable lookup into
|
|
// a stub, but the function name changed (and the new function is present
|
|
// both before and after the interceptor in the prototype chain).
|
|
THREADED_TEST(InterceptorKeyedCallICKeyChange2) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorKeyedCallICGetter));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("proto1"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
keyed_call_ic_function = v8_compile("function f(x) { return x - 1; }; f")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
CompileRun(
|
|
"o = new Object();"
|
|
"proto2 = new Object();"
|
|
"o.y = function(x) { return x + 1; };"
|
|
"proto2.y = function(x) { return x + 2; };"
|
|
"o.__proto__ = proto1;"
|
|
"proto1.__proto__ = proto2;"
|
|
"var result = 0;"
|
|
"var method = 'x';"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) { method = 'y'; };"
|
|
" result += o[method](41);"
|
|
"}");
|
|
CHECK_EQ(42 * 5 + 40 * 5, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// Same as InterceptorKeyedCallICKeyChange1 only the cacheable function sit
|
|
// on the global object.
|
|
THREADED_TEST(InterceptorKeyedCallICKeyChangeOnGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"function inc(x) { return x + 1; };"
|
|
"inc(1);"
|
|
"function dec(x) { return x - 1; };"
|
|
"dec(1);"
|
|
"o.__proto__ = this;"
|
|
"this.__proto__.x = inc;"
|
|
"this.__proto__.y = dec;"
|
|
"var result = 0;"
|
|
"var method = 'x';"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) { method = 'y'; };"
|
|
" result += o[method](41);"
|
|
"}");
|
|
CHECK_EQ(42 * 5 + 40 * 5, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// Test the case when actual function to call sits on global object.
|
|
THREADED_TEST(InterceptorKeyedCallICFromGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
|
|
templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ_o->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
CompileRun(
|
|
"function len(x) { return x.length; };"
|
|
"o.__proto__ = this;"
|
|
"var m = 'parseFloat';"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) {"
|
|
" m = 'len';"
|
|
" saved_result = result;"
|
|
" };"
|
|
" result = o[m]('239');"
|
|
"}");
|
|
CHECK_EQ(3, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(239, context->Global()
|
|
->Get(context.local(), v8_str("saved_result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// Test the map transition before the interceptor.
|
|
THREADED_TEST(InterceptorKeyedCallICMapChangeBefore) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
|
|
templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("proto"),
|
|
templ_o->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
CompileRun(
|
|
"var o = new Object();"
|
|
"o.__proto__ = proto;"
|
|
"o.method = function(x) { return x + 1; };"
|
|
"var m = 'method';"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) { o.method = function(x) { return x - 1; }; };"
|
|
" result += o[m](41);"
|
|
"}");
|
|
CHECK_EQ(42 * 5 + 40 * 5, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// Test the map transition after the interceptor.
|
|
THREADED_TEST(InterceptorKeyedCallICMapChangeAfter) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
|
|
templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ_o->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
CompileRun(
|
|
"var proto = new Object();"
|
|
"o.__proto__ = proto;"
|
|
"proto.method = function(x) { return x + 1; };"
|
|
"var m = 'method';"
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" if (i == 5) { proto.method = function(x) { return x - 1; }; };"
|
|
" result += o[m](41);"
|
|
"}");
|
|
CHECK_EQ(42 * 5 + 40 * 5, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static int interceptor_call_count = 0;
|
|
|
|
static void InterceptorICRefErrorGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (!is_bootstrapping &&
|
|
v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust() &&
|
|
interceptor_call_count++ < 20) {
|
|
info.GetReturnValue().Set(call_ic_function2);
|
|
}
|
|
}
|
|
|
|
|
|
// This test should hit load and call ICs for the interceptor case.
|
|
// Once in a while, the interceptor will reply that a property was not
|
|
// found in which case we should get a reference error.
|
|
THREADED_TEST(InterceptorICReferenceErrors) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorICRefErrorGetter));
|
|
is_bootstrapping = true;
|
|
LocalContext context(nullptr, templ, v8::Local<Value>());
|
|
is_bootstrapping = false;
|
|
call_ic_function2 = v8_compile("function h(x) { return x; }; h")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function f() {"
|
|
" for (var i = 0; i < 1000; i++) {"
|
|
" try { x; } catch(e) { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"};"
|
|
"f();");
|
|
CHECK(value->BooleanValue(isolate));
|
|
interceptor_call_count = 0;
|
|
value = CompileRun(
|
|
"function g() {"
|
|
" for (var i = 0; i < 1000; i++) {"
|
|
" try { x(42); } catch(e) { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"};"
|
|
"g();");
|
|
CHECK(value->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
static int interceptor_ic_exception_get_count = 0;
|
|
|
|
static void InterceptorICExceptionGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (is_bootstrapping) return;
|
|
if (v8_str("x")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), name)
|
|
.FromJust() &&
|
|
++interceptor_ic_exception_get_count < 20) {
|
|
info.GetReturnValue().Set(call_ic_function3);
|
|
}
|
|
if (interceptor_ic_exception_get_count == 20) {
|
|
info.GetIsolate()->ThrowException(v8_num(42));
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// Test interceptor load/call IC where the interceptor throws an
|
|
// exception once in a while.
|
|
THREADED_TEST(InterceptorICGetterExceptions) {
|
|
interceptor_ic_exception_get_count = 0;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorICExceptionGetter));
|
|
is_bootstrapping = true;
|
|
LocalContext context(nullptr, templ, v8::Local<Value>());
|
|
is_bootstrapping = false;
|
|
call_ic_function3 = v8_compile("function h(x) { return x; }; h")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
v8::Local<Value> value = CompileRun(
|
|
"function f() {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" try { x; } catch(e) { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"};"
|
|
"f();");
|
|
CHECK(value->BooleanValue(isolate));
|
|
interceptor_ic_exception_get_count = 0;
|
|
value = CompileRun(
|
|
"function f() {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" try { x(42); } catch(e) { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"};"
|
|
"f();");
|
|
CHECK(value->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
static int interceptor_ic_exception_set_count = 0;
|
|
|
|
static void InterceptorICExceptionSetter(
|
|
Local<Name> key, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (++interceptor_ic_exception_set_count > 20) {
|
|
info.GetIsolate()->ThrowException(v8_num(42));
|
|
}
|
|
}
|
|
|
|
|
|
// Test interceptor store IC where the interceptor throws an exception
|
|
// once in a while.
|
|
THREADED_TEST(InterceptorICSetterExceptions) {
|
|
interceptor_ic_exception_set_count = 0;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
nullptr, InterceptorICExceptionSetter));
|
|
LocalContext context(nullptr, templ, v8::Local<Value>());
|
|
v8::Local<Value> value = CompileRun(
|
|
"function f() {"
|
|
" for (var i = 0; i < 100; i++) {"
|
|
" try { x = 42; } catch(e) { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"};"
|
|
"f();");
|
|
CHECK(value->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
// Test that we ignore null interceptors.
|
|
THREADED_TEST(NullNamedInterceptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
static_cast<v8::GenericNamedPropertyGetterCallback>(nullptr)));
|
|
LocalContext context;
|
|
templ->Set(CcTest::isolate(), "x", v8_num(42));
|
|
v8::Local<v8::Object> obj =
|
|
templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
v8::Local<Value> value = CompileRun("obj.x");
|
|
CHECK(value->IsInt32());
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// Test that we ignore null interceptors.
|
|
THREADED_TEST(NullIndexedInterceptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
static_cast<v8::IndexedPropertyGetterCallback>(nullptr)));
|
|
LocalContext context;
|
|
templ->Set(CcTest::isolate(), "42", v8_num(42));
|
|
v8::Local<v8::Object> obj =
|
|
templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
v8::Local<Value> value = CompileRun("obj[42]");
|
|
CHECK(value->IsInt32());
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(NamedPropertyHandlerGetterAttributes) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
|
|
LocalContext env;
|
|
env->Global()
|
|
->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust();
|
|
ExpectTrue("obj.x === 42");
|
|
ExpectTrue("!obj.propertyIsEnumerable('x')");
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress256330) {
|
|
if (!i::FLAG_opt) return;
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
|
|
context->Global()
|
|
->Set(context.local(), v8_str("Bug"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"\"use strict\"; var o = new Bug;"
|
|
"function f(o) { o.x = 10; };"
|
|
"%PrepareFunctionForOptimization(f);"
|
|
"f(o); f(o); f(o);"
|
|
"%OptimizeFunctionOnNextCall(f);"
|
|
"f(o);");
|
|
int status = v8_run_int32value(v8_compile("%GetOptimizationStatus(f)"));
|
|
int mask = static_cast<int>(i::OptimizationStatus::kIsFunction) |
|
|
static_cast<int>(i::OptimizationStatus::kOptimized);
|
|
CHECK_EQ(mask, status & mask);
|
|
}
|
|
|
|
THREADED_TEST(OptimizedInterceptorSetter) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
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;"
|
|
// Initialize fields to avoid transitions later.
|
|
"obj.age = 0;"
|
|
"obj.accessor_age = 42;"
|
|
"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 });"
|
|
"%PrepareFunctionForOptimization(setAge);"
|
|
"setAge(1);"
|
|
"setAge(2);"
|
|
"setAge(3);"
|
|
"%OptimizeFunctionOnNextCall(setAge);"
|
|
"setAge(4);");
|
|
// All stores went through the interceptor.
|
|
ExpectInt32("obj.interceptor_age", 4);
|
|
ExpectInt32("obj.accessor_age", 42);
|
|
}
|
|
|
|
THREADED_TEST(OptimizedInterceptorGetter) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
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;"
|
|
// Initialize fields to avoid transitions later.
|
|
"obj.age = 1;"
|
|
"obj.accessor_age = 42;"
|
|
"function getter() { return this.accessor_age; };"
|
|
"function getAge() { return obj.interceptor_age; };"
|
|
"Object.defineProperty(obj, 'interceptor_age', { get:getter });"
|
|
"%PrepareFunctionForOptimization(getAge);"
|
|
"getAge();"
|
|
"getAge();"
|
|
"getAge();"
|
|
"%OptimizeFunctionOnNextCall(getAge);");
|
|
// Access through interceptor.
|
|
ExpectInt32("getAge()", 1);
|
|
}
|
|
|
|
THREADED_TEST(OptimizedInterceptorFieldRead) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
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;"
|
|
"obj.__proto__.interceptor_age = 42;"
|
|
"obj.age = 100;"
|
|
"function getAge() { return obj.interceptor_age; };"
|
|
"%PrepareFunctionForOptimization(getAge);");
|
|
ExpectInt32("getAge();", 100);
|
|
ExpectInt32("getAge();", 100);
|
|
ExpectInt32("getAge();", 100);
|
|
CompileRun("%OptimizeFunctionOnNextCall(getAge);");
|
|
// Access through interceptor.
|
|
ExpectInt32("getAge();", 100);
|
|
}
|
|
|
|
THREADED_TEST(OptimizedInterceptorFieldWrite) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
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;"
|
|
"obj.age = 100000;"
|
|
"function setAge(i) { obj.age = i };"
|
|
"%PrepareFunctionForOptimization(setAge);"
|
|
"setAge(100);"
|
|
"setAge(101);"
|
|
"setAge(102);"
|
|
"%OptimizeFunctionOnNextCall(setAge);"
|
|
"setAge(103);");
|
|
ExpectInt32("obj.age", 100000);
|
|
ExpectInt32("obj.interceptor_age", 103);
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress149912) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
|
|
context->Global()
|
|
->Set(context.local(), v8_str("Bug"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun("Number.prototype.__proto__ = new Bug; var x = 0; x.foo();");
|
|
}
|
|
|
|
THREADED_TEST(Regress625155) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
|
|
context->Global()
|
|
->Set(context.local(), v8_str("Bug"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"Number.prototype.__proto__ = new Bug;"
|
|
"var x;"
|
|
"x = 0xDEAD;"
|
|
"x.boom = 0;"
|
|
"x = 's';"
|
|
"x.boom = 0;"
|
|
"x = 1.5;"
|
|
"x.boom = 0;");
|
|
}
|
|
|
|
THREADED_TEST(Regress125988) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Local<FunctionTemplate> intercept = FunctionTemplate::New(CcTest::isolate());
|
|
AddInterceptor(intercept, EmptyInterceptorGetter, EmptyInterceptorSetter);
|
|
LocalContext env;
|
|
env->Global()
|
|
->Set(env.local(), v8_str("Intercept"),
|
|
intercept->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun(
|
|
"var a = new Object();"
|
|
"var b = new Intercept();"
|
|
"var c = new Object();"
|
|
"c.__proto__ = b;"
|
|
"b.__proto__ = a;"
|
|
"a.x = 23;"
|
|
"for (var i = 0; i < 3; i++) c.x;");
|
|
ExpectBoolean("c.hasOwnProperty('x')", false);
|
|
ExpectInt32("c.x", 23);
|
|
CompileRun(
|
|
"a.y = 42;"
|
|
"for (var i = 0; i < 3; i++) c.x;");
|
|
ExpectBoolean("c.hasOwnProperty('x')", false);
|
|
ExpectInt32("c.x", 23);
|
|
ExpectBoolean("c.hasOwnProperty('y')", false);
|
|
ExpectInt32("c.y", 42);
|
|
}
|
|
|
|
|
|
static void IndexedPropertyEnumerator(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 1);
|
|
result->Set(info.GetIsolate()->GetCurrentContext(), 0,
|
|
v8::Integer::New(info.GetIsolate(), 7))
|
|
.FromJust();
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
static void NamedPropertyEnumerator(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 2);
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
result->Set(context, 0, v8_str("x")).FromJust();
|
|
result->Set(context, 1, v8::Symbol::GetIterator(info.GetIsolate()))
|
|
.FromJust();
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
THREADED_TEST(GetOwnPropertyNamesWithInterceptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
|
|
obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
|
|
obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, nullptr, nullptr, IndexedPropertyEnumerator));
|
|
obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, nullptr, nullptr, NamedPropertyEnumerator));
|
|
|
|
LocalContext context;
|
|
v8::Local<v8::Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("object"),
|
|
obj_template->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
v8::Local<v8::Value> result =
|
|
CompileRun("Object.getOwnPropertyNames(object)");
|
|
CHECK(result->IsArray());
|
|
v8::Local<v8::Array> result_array = result.As<v8::Array>();
|
|
CHECK_EQ(2u, result_array->Length());
|
|
CHECK(result_array->Get(context.local(), 0).ToLocalChecked()->IsString());
|
|
CHECK(result_array->Get(context.local(), 1).ToLocalChecked()->IsString());
|
|
CHECK(v8_str("7")
|
|
->Equals(context.local(),
|
|
result_array->Get(context.local(), 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("x")
|
|
->Equals(context.local(),
|
|
result_array->Get(context.local(), 1).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
result = CompileRun("var ret = []; for (var k in object) ret.push(k); ret");
|
|
CHECK(result->IsArray());
|
|
result_array = result.As<v8::Array>();
|
|
CHECK_EQ(2u, result_array->Length());
|
|
CHECK(result_array->Get(context.local(), 0).ToLocalChecked()->IsString());
|
|
CHECK(result_array->Get(context.local(), 1).ToLocalChecked()->IsString());
|
|
CHECK(v8_str("7")
|
|
->Equals(context.local(),
|
|
result_array->Get(context.local(), 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("x")
|
|
->Equals(context.local(),
|
|
result_array->Get(context.local(), 1).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
result = CompileRun("Object.getOwnPropertySymbols(object)");
|
|
CHECK(result->IsArray());
|
|
result_array = result.As<v8::Array>();
|
|
CHECK_EQ(1u, result_array->Length());
|
|
CHECK(result_array->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), v8::Symbol::GetIterator(isolate))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static void IndexedPropertyEnumeratorException(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
info.GetIsolate()->ThrowException(v8_num(42));
|
|
}
|
|
|
|
|
|
THREADED_TEST(GetOwnPropertyNamesWithIndexedInterceptorExceptions_regress4026) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
|
|
obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
|
|
obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
// First just try a failing indexed interceptor.
|
|
obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, nullptr, nullptr, IndexedPropertyEnumeratorException));
|
|
|
|
LocalContext context;
|
|
v8::Local<v8::Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("object"),
|
|
obj_template->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var result = []; "
|
|
"try { "
|
|
" for (var k in object) result .push(k);"
|
|
"} catch (e) {"
|
|
" result = e"
|
|
"}"
|
|
"result ");
|
|
CHECK(!result->IsArray());
|
|
CHECK(v8_num(42)->Equals(context.local(), result).FromJust());
|
|
|
|
result = CompileRun(
|
|
"var result = [];"
|
|
"try { "
|
|
" result = Object.keys(object);"
|
|
"} catch (e) {"
|
|
" result = e;"
|
|
"}"
|
|
"result");
|
|
CHECK(!result->IsArray());
|
|
CHECK(v8_num(42)->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
|
|
static void NamedPropertyEnumeratorException(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
info.GetIsolate()->ThrowException(v8_num(43));
|
|
}
|
|
|
|
|
|
THREADED_TEST(GetOwnPropertyNamesWithNamedInterceptorExceptions_regress4026) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
|
|
obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
|
|
obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
// First just try a failing indexed interceptor.
|
|
obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, nullptr, nullptr, NamedPropertyEnumeratorException));
|
|
|
|
LocalContext context;
|
|
v8::Local<v8::Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("object"),
|
|
obj_template->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var result = []; "
|
|
"try { "
|
|
" for (var k in object) result.push(k);"
|
|
"} catch (e) {"
|
|
" result = e"
|
|
"}"
|
|
"result");
|
|
CHECK(!result->IsArray());
|
|
CHECK(v8_num(43)->Equals(context.local(), result).FromJust());
|
|
|
|
result = CompileRun(
|
|
"var result = [];"
|
|
"try { "
|
|
" result = Object.keys(object);"
|
|
"} catch (e) {"
|
|
" result = e;"
|
|
"}"
|
|
"result");
|
|
CHECK(!result->IsArray());
|
|
CHECK(v8_num(43)->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
Local<Object> BuildWrappedObject(v8::Isolate* isolate, T* data) {
|
|
auto templ = v8::ObjectTemplate::New(isolate);
|
|
templ->SetInternalFieldCount(1);
|
|
auto instance =
|
|
templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
|
instance->SetAlignedPointerInInternalField(0, data);
|
|
return instance;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
T* GetWrappedObject(Local<Value> data) {
|
|
return reinterpret_cast<T*>(
|
|
Object::Cast(*data)->GetAlignedPointerFromInternalField(0));
|
|
}
|
|
|
|
|
|
struct AccessCheckData {
|
|
int count;
|
|
bool result;
|
|
};
|
|
|
|
AccessCheckData* g_access_check_data = nullptr;
|
|
|
|
bool SimpleAccessChecker(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> access_object,
|
|
Local<v8::Value> data) {
|
|
g_access_check_data->count++;
|
|
return g_access_check_data->result;
|
|
}
|
|
|
|
|
|
struct ShouldInterceptData {
|
|
int value;
|
|
bool should_intercept;
|
|
};
|
|
|
|
|
|
void ShouldNamedInterceptor(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(ShouldNamedInterceptor));
|
|
auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
|
|
if (!data->should_intercept) return;
|
|
info.GetReturnValue().Set(v8_num(data->value));
|
|
}
|
|
|
|
|
|
void ShouldIndexedInterceptor(uint32_t,
|
|
const v8::PropertyCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(ShouldIndexedInterceptor));
|
|
auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
|
|
if (!data->should_intercept) return;
|
|
info.GetReturnValue().Set(v8_num(data->value));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
TEST(NamedAllCanReadInterceptor) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
AccessCheckData access_check_data;
|
|
access_check_data.result = true;
|
|
access_check_data.count = 0;
|
|
|
|
g_access_check_data = &access_check_data;
|
|
|
|
ShouldInterceptData intercept_data_0;
|
|
intercept_data_0.value = 239;
|
|
intercept_data_0.should_intercept = true;
|
|
|
|
ShouldInterceptData intercept_data_1;
|
|
intercept_data_1.value = 165;
|
|
intercept_data_1.should_intercept = false;
|
|
|
|
auto intercepted_0 = v8::ObjectTemplate::New(isolate);
|
|
{
|
|
v8::NamedPropertyHandlerConfiguration conf(ShouldNamedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kAllCanRead;
|
|
conf.data =
|
|
BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data_0);
|
|
intercepted_0->SetHandler(conf);
|
|
}
|
|
|
|
auto intercepted_1 = v8::ObjectTemplate::New(isolate);
|
|
{
|
|
v8::NamedPropertyHandlerConfiguration conf(ShouldNamedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kAllCanRead;
|
|
conf.data =
|
|
BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data_1);
|
|
intercepted_1->SetHandler(conf);
|
|
}
|
|
|
|
auto checked = v8::ObjectTemplate::New(isolate);
|
|
checked->SetAccessCheckCallback(SimpleAccessChecker);
|
|
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_0"),
|
|
intercepted_0->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_1"),
|
|
intercepted_1->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
auto checked_instance =
|
|
checked->NewInstance(context.local()).ToLocalChecked();
|
|
checked_instance->Set(context.local(), v8_str("whatever"), v8_num(17))
|
|
.FromJust();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("checked"), checked_instance)
|
|
.FromJust();
|
|
CompileRun(
|
|
"checked.__proto__ = intercepted_1;"
|
|
"intercepted_1.__proto__ = intercepted_0;");
|
|
|
|
CHECK_EQ(3, access_check_data.count);
|
|
|
|
ExpectInt32("checked.whatever", 17);
|
|
CHECK(!CompileRun("Object.getOwnPropertyDescriptor(checked, 'whatever')")
|
|
->IsUndefined());
|
|
CHECK_EQ(5, access_check_data.count);
|
|
|
|
access_check_data.result = false;
|
|
ExpectInt32("checked.whatever", intercept_data_0.value);
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("Object.getOwnPropertyDescriptor(checked, 'whatever')");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
CHECK_EQ(8, access_check_data.count);
|
|
|
|
intercept_data_1.should_intercept = true;
|
|
ExpectInt32("checked.whatever", intercept_data_1.value);
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("Object.getOwnPropertyDescriptor(checked, 'whatever')");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
CHECK_EQ(11, access_check_data.count);
|
|
g_access_check_data = nullptr;
|
|
}
|
|
|
|
|
|
TEST(IndexedAllCanReadInterceptor) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
AccessCheckData access_check_data;
|
|
access_check_data.result = true;
|
|
access_check_data.count = 0;
|
|
|
|
g_access_check_data = &access_check_data;
|
|
|
|
ShouldInterceptData intercept_data_0;
|
|
intercept_data_0.value = 239;
|
|
intercept_data_0.should_intercept = true;
|
|
|
|
ShouldInterceptData intercept_data_1;
|
|
intercept_data_1.value = 165;
|
|
intercept_data_1.should_intercept = false;
|
|
|
|
auto intercepted_0 = v8::ObjectTemplate::New(isolate);
|
|
{
|
|
v8::IndexedPropertyHandlerConfiguration conf(ShouldIndexedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kAllCanRead;
|
|
conf.data =
|
|
BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data_0);
|
|
intercepted_0->SetHandler(conf);
|
|
}
|
|
|
|
auto intercepted_1 = v8::ObjectTemplate::New(isolate);
|
|
{
|
|
v8::IndexedPropertyHandlerConfiguration conf(ShouldIndexedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kAllCanRead;
|
|
conf.data =
|
|
BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data_1);
|
|
intercepted_1->SetHandler(conf);
|
|
}
|
|
|
|
auto checked = v8::ObjectTemplate::New(isolate);
|
|
checked->SetAccessCheckCallback(SimpleAccessChecker);
|
|
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_0"),
|
|
intercepted_0->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_1"),
|
|
intercepted_1->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
auto checked_instance =
|
|
checked->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("checked"), checked_instance)
|
|
.FromJust();
|
|
checked_instance->Set(context.local(), 15, v8_num(17)).FromJust();
|
|
CompileRun(
|
|
"checked.__proto__ = intercepted_1;"
|
|
"intercepted_1.__proto__ = intercepted_0;");
|
|
|
|
CHECK_EQ(3, access_check_data.count);
|
|
|
|
access_check_data.result = true;
|
|
ExpectInt32("checked[15]", 17);
|
|
CHECK(!CompileRun("Object.getOwnPropertyDescriptor(checked, '15')")
|
|
->IsUndefined());
|
|
CHECK_EQ(5, access_check_data.count);
|
|
|
|
access_check_data.result = false;
|
|
ExpectInt32("checked[15]", intercept_data_0.value);
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("Object.getOwnPropertyDescriptor(checked, '15')");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
CHECK_EQ(8, access_check_data.count);
|
|
|
|
intercept_data_1.should_intercept = true;
|
|
ExpectInt32("checked[15]", intercept_data_1.value);
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("Object.getOwnPropertyDescriptor(checked, '15')");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
CHECK_EQ(11, access_check_data.count);
|
|
|
|
g_access_check_data = nullptr;
|
|
}
|
|
|
|
|
|
THREADED_TEST(NonMaskingInterceptorOwnProperty) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
ShouldInterceptData intercept_data;
|
|
intercept_data.value = 239;
|
|
intercept_data.should_intercept = true;
|
|
|
|
auto interceptor_templ = v8::ObjectTemplate::New(isolate);
|
|
v8::NamedPropertyHandlerConfiguration conf(ShouldNamedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kNonMasking;
|
|
conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
|
|
interceptor_templ->SetHandler(conf);
|
|
|
|
auto interceptor =
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"), interceptor)
|
|
.FromJust();
|
|
|
|
ExpectInt32("obj.whatever", 239);
|
|
|
|
CompileRun("obj.whatever = 4;");
|
|
|
|
// obj.whatever exists, thus it is not affected by the non-masking
|
|
// interceptor.
|
|
ExpectInt32("obj.whatever", 4);
|
|
|
|
CompileRun("delete obj.whatever;");
|
|
ExpectInt32("obj.whatever", 239);
|
|
}
|
|
|
|
|
|
THREADED_TEST(NonMaskingInterceptorPrototypeProperty) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
ShouldInterceptData intercept_data;
|
|
intercept_data.value = 239;
|
|
intercept_data.should_intercept = true;
|
|
|
|
auto interceptor_templ = v8::ObjectTemplate::New(isolate);
|
|
v8::NamedPropertyHandlerConfiguration conf(ShouldNamedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kNonMasking;
|
|
conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
|
|
interceptor_templ->SetHandler(conf);
|
|
|
|
auto interceptor =
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"), interceptor)
|
|
.FromJust();
|
|
|
|
ExpectInt32("obj.whatever", 239);
|
|
|
|
CompileRun("obj.__proto__ = {'whatever': 4};");
|
|
ExpectInt32("obj.whatever", 4);
|
|
|
|
CompileRun("delete obj.__proto__.whatever;");
|
|
ExpectInt32("obj.whatever", 239);
|
|
}
|
|
|
|
|
|
THREADED_TEST(NonMaskingInterceptorPrototypePropertyIC) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
ShouldInterceptData intercept_data;
|
|
intercept_data.value = 239;
|
|
intercept_data.should_intercept = true;
|
|
|
|
auto interceptor_templ = v8::ObjectTemplate::New(isolate);
|
|
v8::NamedPropertyHandlerConfiguration conf(ShouldNamedInterceptor);
|
|
conf.flags = v8::PropertyHandlerFlags::kNonMasking;
|
|
conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
|
|
interceptor_templ->SetHandler(conf);
|
|
|
|
auto interceptor =
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"), interceptor)
|
|
.FromJust();
|
|
|
|
CompileRun(
|
|
"outer = {};"
|
|
"outer.__proto__ = obj;"
|
|
"function f(obj) {"
|
|
" var x;"
|
|
" for (var i = 0; i < 4; i++) {"
|
|
" x = obj.whatever;"
|
|
" }"
|
|
" return x;"
|
|
"}");
|
|
|
|
// Receiver == holder.
|
|
CompileRun("obj.__proto__ = null;");
|
|
ExpectInt32("f(obj)", 239);
|
|
ExpectInt32("f(outer)", 239);
|
|
|
|
// Receiver != holder.
|
|
CompileRun("Object.setPrototypeOf(obj, {});");
|
|
ExpectInt32("f(obj)", 239);
|
|
ExpectInt32("f(outer)", 239);
|
|
|
|
// Masked value on prototype.
|
|
CompileRun("obj.__proto__.whatever = 4;");
|
|
CompileRun("obj.__proto__.__proto__ = { 'whatever' : 5 };");
|
|
ExpectInt32("f(obj)", 4);
|
|
ExpectInt32("f(outer)", 4);
|
|
|
|
// Masked value on prototype prototype.
|
|
CompileRun("delete obj.__proto__.whatever;");
|
|
ExpectInt32("f(obj)", 5);
|
|
ExpectInt32("f(outer)", 5);
|
|
|
|
// Reset.
|
|
CompileRun("delete obj.__proto__.__proto__.whatever;");
|
|
ExpectInt32("f(obj)", 239);
|
|
ExpectInt32("f(outer)", 239);
|
|
|
|
// Masked value on self.
|
|
CompileRun("obj.whatever = 4;");
|
|
ExpectInt32("f(obj)", 4);
|
|
ExpectInt32("f(outer)", 4);
|
|
|
|
// Reset.
|
|
CompileRun("delete obj.whatever;");
|
|
ExpectInt32("f(obj)", 239);
|
|
ExpectInt32("f(outer)", 239);
|
|
|
|
CompileRun("outer.whatever = 4;");
|
|
ExpectInt32("f(obj)", 239);
|
|
ExpectInt32("f(outer)", 4);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void ConcatNamedPropertyGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(
|
|
// Return the property name concatenated with itself.
|
|
String::Concat(info.GetIsolate(), name.As<String>(), name.As<String>()));
|
|
}
|
|
|
|
void ConcatIndexedPropertyGetter(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(
|
|
// Return the double value of the index.
|
|
v8_num(index + index));
|
|
}
|
|
|
|
void EnumCallbackWithNames(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 4);
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("foo"))
|
|
.FromJust());
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("bar"))
|
|
.FromJust());
|
|
CHECK(
|
|
result
|
|
->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_str("baz"))
|
|
.FromJust());
|
|
CHECK(
|
|
result->Set(context, v8::Integer::New(info.GetIsolate(), 3), v8_str("10"))
|
|
.FromJust());
|
|
|
|
// Create a holey array.
|
|
CHECK(result->Delete(context, v8::Integer::New(info.GetIsolate(), 1))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
void EnumCallbackWithIndices(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 4);
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
|
|
CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_num(10))
|
|
.FromJust());
|
|
CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_num(11))
|
|
.FromJust());
|
|
CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_num(12))
|
|
.FromJust());
|
|
CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 3), v8_num(14))
|
|
.FromJust());
|
|
|
|
// Create a holey array.
|
|
CHECK(result->Delete(context, v8::Integer::New(info.GetIsolate(), 1))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
void RestrictiveNamedQuery(Local<Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
// Only "foo" is enumerable.
|
|
if (v8_str("foo")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), property)
|
|
.FromJust()) {
|
|
info.GetReturnValue().Set(v8::PropertyAttribute::None);
|
|
return;
|
|
}
|
|
info.GetReturnValue().Set(v8::PropertyAttribute::DontEnum);
|
|
}
|
|
|
|
void RestrictiveIndexedQuery(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
// Only index 2 and 12 are enumerable.
|
|
if (index == 2 || index == 12) {
|
|
info.GetReturnValue().Set(v8::PropertyAttribute::None);
|
|
return;
|
|
}
|
|
info.GetReturnValue().Set(v8::PropertyAttribute::DontEnum);
|
|
}
|
|
} // namespace
|
|
|
|
// Regression test for V8 bug 6627.
|
|
// Object.keys() must return enumerable keys only.
|
|
THREADED_TEST(EnumeratorsAndUnenumerableNamedProperties) {
|
|
// The enumerator interceptor returns a list
|
|
// of items which are filtered according to the
|
|
// properties defined in the query interceptor.
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
ConcatNamedPropertyGetter, nullptr, RestrictiveNamedQuery, nullptr,
|
|
EnumCallbackWithNames));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
|
|
ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[1]", "baz");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[2]", "10");
|
|
|
|
ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");
|
|
ExpectFalse("Object.getOwnPropertyDescriptor(obj, 'baz').enumerable");
|
|
|
|
ExpectInt32("Object.entries(obj).length", 1);
|
|
ExpectString("Object.entries(obj)[0][0]", "foo");
|
|
ExpectString("Object.entries(obj)[0][1]", "foofoo");
|
|
|
|
ExpectInt32("Object.keys(obj).length", 1);
|
|
ExpectString("Object.keys(obj)[0]", "foo");
|
|
|
|
ExpectInt32("Object.values(obj).length", 1);
|
|
ExpectString("Object.values(obj)[0]", "foofoo");
|
|
}
|
|
|
|
namespace {
|
|
void QueryInterceptorForFoo(Local<Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
// Don't intercept anything except "foo."
|
|
if (!v8_str("foo")
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), property)
|
|
.FromJust()) {
|
|
return;
|
|
}
|
|
// "foo" is enumerable.
|
|
info.GetReturnValue().Set(v8::PropertyAttribute::None);
|
|
}
|
|
} // namespace
|
|
|
|
// Test that calls to the query interceptor are independent of each
|
|
// other.
|
|
THREADED_TEST(EnumeratorsAndUnenumerableNamedPropertiesWithoutSet) {
|
|
// The enumerator interceptor returns a list
|
|
// of items which are filtered according to the
|
|
// properties defined in the query interceptor.
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
ConcatNamedPropertyGetter, nullptr, QueryInterceptorForFoo, nullptr,
|
|
EnumCallbackWithNames));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
|
|
ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[1]", "baz");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[2]", "10");
|
|
|
|
ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");
|
|
ExpectInt32("Object.keys(obj).length", 1);
|
|
}
|
|
|
|
THREADED_TEST(EnumeratorsAndUnenumerableIndexedPropertiesArgumentsElements) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
ConcatIndexedPropertyGetter, nullptr, RestrictiveIndexedQuery, nullptr,
|
|
SloppyArgsIndexedPropertyEnumerator));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
ExpectInt32("Object.getOwnPropertyNames(obj).length", 4);
|
|
ExpectString("Object.getOwnPropertyNames(obj)[0]", "0");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[1]", "1");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[2]", "2");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[3]", "3");
|
|
|
|
ExpectTrue("Object.getOwnPropertyDescriptor(obj, '2').enumerable");
|
|
|
|
ExpectInt32("Object.entries(obj).length", 1);
|
|
ExpectString("Object.entries(obj)[0][0]", "2");
|
|
ExpectInt32("Object.entries(obj)[0][1]", 4);
|
|
|
|
ExpectInt32("Object.keys(obj).length", 1);
|
|
ExpectString("Object.keys(obj)[0]", "2");
|
|
|
|
ExpectInt32("Object.values(obj).length", 1);
|
|
ExpectInt32("Object.values(obj)[0]", 4);
|
|
}
|
|
|
|
THREADED_TEST(EnumeratorsAndUnenumerableIndexedProperties) {
|
|
// The enumerator interceptor returns a list
|
|
// of items which are filtered according to the
|
|
// properties defined in the query interceptor.
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
ConcatIndexedPropertyGetter, nullptr, RestrictiveIndexedQuery, nullptr,
|
|
EnumCallbackWithIndices));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
|
|
ExpectString("Object.getOwnPropertyNames(obj)[0]", "10");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[1]", "12");
|
|
ExpectString("Object.getOwnPropertyNames(obj)[2]", "14");
|
|
|
|
ExpectFalse("Object.getOwnPropertyDescriptor(obj, '10').enumerable");
|
|
ExpectTrue("Object.getOwnPropertyDescriptor(obj, '12').enumerable");
|
|
|
|
ExpectInt32("Object.entries(obj).length", 1);
|
|
ExpectString("Object.entries(obj)[0][0]", "12");
|
|
ExpectInt32("Object.entries(obj)[0][1]", 24);
|
|
|
|
ExpectInt32("Object.keys(obj).length", 1);
|
|
ExpectString("Object.keys(obj)[0]", "12");
|
|
|
|
ExpectInt32("Object.values(obj).length", 1);
|
|
ExpectInt32("Object.values(obj)[0]", 24);
|
|
}
|
|
|
|
THREADED_TEST(EnumeratorsAndForIn) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
ConcatNamedPropertyGetter, nullptr, RestrictiveNamedQuery, nullptr,
|
|
NamedEnum));
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
|
|
ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");
|
|
|
|
ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");
|
|
|
|
CompileRun(
|
|
"let concat = '';"
|
|
"for(var prop in obj) {"
|
|
" concat += `key:${prop}:value:${obj[prop]}`;"
|
|
"}");
|
|
|
|
// Check that for...in only iterates over enumerable properties.
|
|
ExpectString("concat", "key:foo:value:foofoo");
|
|
}
|
|
|
|
namespace {
|
|
|
|
void DatabaseGetter(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
auto context = info.GetIsolate()->GetCurrentContext();
|
|
Local<v8::Object> db = info.Holder()
|
|
->GetRealNamedProperty(context, v8_str("db"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
if (!db->Has(context, name).FromJust()) return;
|
|
info.GetReturnValue().Set(db->Get(context, name).ToLocalChecked());
|
|
}
|
|
|
|
|
|
void DatabaseSetter(Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
auto context = info.GetIsolate()->GetCurrentContext();
|
|
if (name->Equals(context, v8_str("db")).FromJust()) return;
|
|
Local<v8::Object> db = info.Holder()
|
|
->GetRealNamedProperty(context, v8_str("db"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
db->Set(context, name, value).FromJust();
|
|
info.GetReturnValue().Set(value);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
THREADED_TEST(NonMaskingInterceptorGlobalEvalRegression) {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
auto interceptor_templ = v8::ObjectTemplate::New(isolate);
|
|
v8::NamedPropertyHandlerConfiguration conf(DatabaseGetter, DatabaseSetter);
|
|
conf.flags = v8::PropertyHandlerFlags::kNonMasking;
|
|
interceptor_templ->SetHandler(conf);
|
|
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_1"),
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
context->Global()
|
|
->Set(context.local(), v8_str("intercepted_2"),
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
|
|
// Init dbs.
|
|
CompileRun(
|
|
"intercepted_1.db = {};"
|
|
"intercepted_2.db = {};");
|
|
|
|
ExpectInt32(
|
|
"var obj = intercepted_1;"
|
|
"obj.x = 4;"
|
|
"eval('obj.x');"
|
|
"eval('obj.x');"
|
|
"eval('obj.x');"
|
|
"obj = intercepted_2;"
|
|
"obj.x = 9;"
|
|
"eval('obj.x');",
|
|
9);
|
|
}
|
|
|
|
static void CheckReceiver(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(info.This()->IsObject());
|
|
}
|
|
|
|
TEST(Regress609134Interceptor) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto fun_templ = v8::FunctionTemplate::New(isolate);
|
|
fun_templ->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(CheckReceiver));
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("Fun"),
|
|
fun_templ->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun(
|
|
"var f = new Fun();"
|
|
"Number.prototype.__proto__ = f;"
|
|
"var a = 42;"
|
|
"for (var i = 0; i<3; i++) { a.foo; }");
|
|
}
|