[api] Add interceptor for getOwnPropertyDescriptor().

The existing PropertyQueryCallback intercepts getOwnPropertyDescriptor, but
it returns only value and attributes, not the accessors. This
PropertyDescriptorCallback returns a descriptor similar to Ecma-262 6.2.4.

You can either set a PropertyQueryCallback or a PropertyDescriptorCallback,
but not both. When you set a callback for DefineProperty(), you can set a
PropertyDescriptorCallback but not a PropertyQueryCallback.

BUG=v8:5359

Review-Url: https://codereview.chromium.org/2311873002
Cr-Commit-Position: refs/heads/master@{#39279}
This commit is contained in:
franzih 2016-09-08 05:51:37 -07:00 committed by Commit bot
parent 212624b757
commit b0a7738a5f
7 changed files with 272 additions and 32 deletions

View File

@ -4740,6 +4740,28 @@ typedef void (*GenericNamedPropertyDefinerCallback)(
Local<Name> property, const PropertyDescriptor& desc,
const PropertyCallbackInfo<Value>& info);
/**
* Interceptor for getOwnPropertyDescriptor requests on an object.
*
* Use `info.GetReturnValue().Set()` to set the return value of the
* intercepted request. The return value must be an object that
* can be converted to a PropertyDescriptor, e.g., a `v8::value` returned from
* `v8::Object::getOwnPropertyDescriptor`.
*
* \param property The name of the property for which the request was
* intercepted.
* \info Information about the intercepted request, such as
* isolate, receiver, return value, or whether running in `'use strict'` mode.
* See `PropertyCallbackInfo`.
*
* \note If GetOwnPropertyDescriptor is intercepted, it will
* always return true, i.e., indicate that the property was found.
*
* See also `ObjectTemplate::SetNamedPropertyHandler`.
*/
typedef void (*GenericNamedPropertyDescriptorCallback)(
Local<Name> property, const PropertyCallbackInfo<Value>& info);
/**
* Returns the value of the property if the getter intercepts the
* request. Otherwise, returns an empty handle.
@ -4789,6 +4811,9 @@ typedef void (*IndexedPropertyDefinerCallback)(
uint32_t index, const PropertyDescriptor& desc,
const PropertyCallbackInfo<Value>& info);
typedef void (*IndexedPropertyDescriptorCallback)(
uint32_t index, const PropertyCallbackInfo<Value>& info);
/**
* Access type specification.
*/
@ -5050,13 +5075,14 @@ struct NamedPropertyHandlerConfiguration {
deleter(deleter),
enumerator(enumerator),
definer(0),
descriptor(0),
data(data),
flags(flags) {}
NamedPropertyHandlerConfiguration(
GenericNamedPropertyGetterCallback getter,
GenericNamedPropertySetterCallback setter,
GenericNamedPropertyQueryCallback query,
GenericNamedPropertyDescriptorCallback descriptor,
GenericNamedPropertyDeleterCallback deleter,
GenericNamedPropertyEnumeratorCallback enumerator,
GenericNamedPropertyDefinerCallback definer,
@ -5064,10 +5090,11 @@ struct NamedPropertyHandlerConfiguration {
PropertyHandlerFlags flags = PropertyHandlerFlags::kNone)
: getter(getter),
setter(setter),
query(query),
query(0),
deleter(deleter),
enumerator(enumerator),
definer(definer),
descriptor(descriptor),
data(data),
flags(flags) {}
@ -5077,6 +5104,7 @@ struct NamedPropertyHandlerConfiguration {
GenericNamedPropertyDeleterCallback deleter;
GenericNamedPropertyEnumeratorCallback enumerator;
GenericNamedPropertyDefinerCallback definer;
GenericNamedPropertyDescriptorCallback descriptor;
Local<Value> data;
PropertyHandlerFlags flags;
};
@ -5098,12 +5126,14 @@ struct IndexedPropertyHandlerConfiguration {
deleter(deleter),
enumerator(enumerator),
definer(0),
descriptor(0),
data(data),
flags(flags) {}
IndexedPropertyHandlerConfiguration(
IndexedPropertyGetterCallback getter,
IndexedPropertySetterCallback setter, IndexedPropertyQueryCallback query,
IndexedPropertySetterCallback setter,
IndexedPropertyDescriptorCallback descriptor,
IndexedPropertyDeleterCallback deleter,
IndexedPropertyEnumeratorCallback enumerator,
IndexedPropertyDefinerCallback definer,
@ -5111,10 +5141,11 @@ struct IndexedPropertyHandlerConfiguration {
PropertyHandlerFlags flags = PropertyHandlerFlags::kNone)
: getter(getter),
setter(setter),
query(query),
query(0),
deleter(deleter),
enumerator(enumerator),
definer(definer),
descriptor(descriptor),
data(data),
flags(flags) {}
@ -5124,6 +5155,7 @@ struct IndexedPropertyHandlerConfiguration {
IndexedPropertyDeleterCallback deleter;
IndexedPropertyEnumeratorCallback enumerator;
IndexedPropertyDefinerCallback definer;
IndexedPropertyDescriptorCallback descriptor;
Local<Value> data;
PropertyHandlerFlags flags;
};

View File

@ -1503,12 +1503,17 @@ void ObjectTemplate::SetAccessor(v8::Local<Name> name,
signature, i::FLAG_disable_old_api_accessors);
}
template <typename Getter, typename Setter, typename Query, typename Deleter,
typename Enumerator, typename Definer>
template <typename Getter, typename Setter, typename Query, typename Descriptor,
typename Deleter, typename Enumerator, typename Definer>
static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
i::Isolate* isolate, Getter getter, Setter setter, Query query,
Deleter remover, Enumerator enumerator, Definer definer, Local<Value> data,
PropertyHandlerFlags flags) {
Descriptor descriptor, Deleter remover, Enumerator enumerator,
Definer definer, Local<Value> data, PropertyHandlerFlags flags) {
DCHECK(query == nullptr ||
descriptor == nullptr); // Either intercept attributes or descriptor.
DCHECK(query == nullptr ||
definer ==
nullptr); // Only use descriptor callback with definer callback.
auto obj = i::Handle<i::InterceptorInfo>::cast(
isolate->factory()->NewStruct(i::INTERCEPTOR_INFO_TYPE));
obj->set_flags(0);
@ -1516,6 +1521,7 @@ static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
if (getter != 0) SET_FIELD_WRAPPED(obj, set_getter, getter);
if (setter != 0) SET_FIELD_WRAPPED(obj, set_setter, setter);
if (query != 0) SET_FIELD_WRAPPED(obj, set_query, query);
if (descriptor != 0) SET_FIELD_WRAPPED(obj, set_descriptor, descriptor);
if (remover != 0) SET_FIELD_WRAPPED(obj, set_deleter, remover);
if (enumerator != 0) SET_FIELD_WRAPPED(obj, set_enumerator, enumerator);
if (definer != 0) SET_FIELD_WRAPPED(obj, set_definer, definer);
@ -1534,19 +1540,19 @@ static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
return obj;
}
template <typename Getter, typename Setter, typename Query, typename Deleter,
typename Enumerator, typename Definer>
template <typename Getter, typename Setter, typename Query, typename Descriptor,
typename Deleter, typename Enumerator, typename Definer>
static void ObjectTemplateSetNamedPropertyHandler(
ObjectTemplate* templ, Getter getter, Setter setter, Query query,
Deleter remover, Enumerator enumerator, Definer definer, Local<Value> data,
PropertyHandlerFlags flags) {
Descriptor descriptor, Deleter remover, Enumerator enumerator,
Definer definer, Local<Value> data, PropertyHandlerFlags flags) {
i::Isolate* isolate = Utils::OpenHandle(templ)->GetIsolate();
ENTER_V8(isolate);
i::HandleScope scope(isolate);
auto cons = EnsureConstructor(isolate, templ);
EnsureNotInstantiated(cons, "ObjectTemplateSetNamedPropertyHandler");
auto obj = CreateInterceptorInfo(isolate, getter, setter, query, remover,
enumerator, definer, data, flags);
auto obj = CreateInterceptorInfo(isolate, getter, setter, query, descriptor,
remover, enumerator, definer, data, flags);
cons->set_named_property_handler(*obj);
}
@ -1555,15 +1561,16 @@ void ObjectTemplate::SetNamedPropertyHandler(
NamedPropertyQueryCallback query, NamedPropertyDeleterCallback remover,
NamedPropertyEnumeratorCallback enumerator, Local<Value> data) {
ObjectTemplateSetNamedPropertyHandler(
this, getter, setter, query, remover, enumerator, nullptr, data,
this, getter, setter, query, nullptr, remover, enumerator, nullptr, data,
PropertyHandlerFlags::kOnlyInterceptStrings);
}
void ObjectTemplate::SetHandler(
const NamedPropertyHandlerConfiguration& config) {
ObjectTemplateSetNamedPropertyHandler(
this, config.getter, config.setter, config.query, config.deleter,
config.enumerator, config.definer, config.data, config.flags);
this, config.getter, config.setter, config.query, config.descriptor,
config.deleter, config.enumerator, config.definer, config.data,
config.flags);
}
@ -1623,14 +1630,14 @@ void ObjectTemplate::SetAccessCheckCallbackAndHandler(
SET_FIELD_WRAPPED(info, set_callback, callback);
auto named_interceptor = CreateInterceptorInfo(
isolate, named_handler.getter, named_handler.setter, named_handler.query,
named_handler.deleter, named_handler.enumerator, named_handler.definer,
named_handler.data, named_handler.flags);
named_handler.descriptor, named_handler.deleter, named_handler.enumerator,
named_handler.definer, named_handler.data, named_handler.flags);
info->set_named_interceptor(*named_interceptor);
auto indexed_interceptor = CreateInterceptorInfo(
isolate, indexed_handler.getter, indexed_handler.setter,
indexed_handler.query, indexed_handler.deleter,
indexed_handler.enumerator, indexed_handler.definer, indexed_handler.data,
indexed_handler.flags);
indexed_handler.query, indexed_handler.descriptor,
indexed_handler.deleter, indexed_handler.enumerator,
indexed_handler.definer, indexed_handler.data, indexed_handler.flags);
info->set_indexed_interceptor(*indexed_interceptor);
if (data.IsEmpty()) {
@ -1649,9 +1656,10 @@ void ObjectTemplate::SetHandler(
i::HandleScope scope(isolate);
auto cons = EnsureConstructor(isolate, this);
EnsureNotInstantiated(cons, "v8::ObjectTemplate::SetHandler");
auto obj = CreateInterceptorInfo(
isolate, config.getter, config.setter, config.query, config.deleter,
config.enumerator, config.definer, config.data, config.flags);
auto obj = CreateInterceptorInfo(isolate, config.getter, config.setter,
config.query, config.descriptor,
config.deleter, config.enumerator,
config.definer, config.data, config.flags);
cons->set_indexed_property_handler(*obj);
}

View File

@ -682,10 +682,12 @@ class RuntimeCallTimer {
V(GC) \
V(GenericNamedPropertyDefinerCallback) \
V(GenericNamedPropertyDeleterCallback) \
V(GenericNamedPropertyDescriptorCallback) \
V(GenericNamedPropertyQueryCallback) \
V(GenericNamedPropertySetterCallback) \
V(IndexedPropertyDefinerCallback) \
V(IndexedPropertyDeleterCallback) \
V(IndexedPropertyDescriptorCallback) \
V(IndexedPropertyGetterCallback) \
V(IndexedPropertyQueryCallback) \
V(IndexedPropertySetterCallback) \

View File

@ -5708,6 +5708,7 @@ ACCESSORS(AccessCheckInfo, data, Object, kDataOffset)
ACCESSORS(InterceptorInfo, getter, Object, kGetterOffset)
ACCESSORS(InterceptorInfo, setter, Object, kSetterOffset)
ACCESSORS(InterceptorInfo, query, Object, kQueryOffset)
ACCESSORS(InterceptorInfo, descriptor, Object, kDescriptorOffset)
ACCESSORS(InterceptorInfo, deleter, Object, kDeleterOffset)
ACCESSORS(InterceptorInfo, enumerator, Object, kEnumeratorOffset)
ACCESSORS(InterceptorInfo, definer, Object, kDefinerOffset)

View File

@ -7336,6 +7336,57 @@ Maybe<bool> JSReceiver::GetOwnPropertyDescriptor(Isolate* isolate,
return GetOwnPropertyDescriptor(&it, desc);
}
namespace {
Maybe<bool> GetPropertyDescriptorWithInterceptor(LookupIterator* it,
PropertyDescriptor* desc) {
if (it->state() == LookupIterator::INTERCEPTOR) {
Isolate* isolate = it->isolate();
Handle<InterceptorInfo> interceptor = it->GetInterceptor();
if (!interceptor->descriptor()->IsUndefined(isolate)) {
Handle<Object> result;
Handle<JSObject> holder = it->GetHolder<JSObject>();
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, receiver, Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, Object::DONT_THROW);
if (it->IsElement()) {
uint32_t index = it->index();
v8::IndexedPropertyDescriptorCallback descriptorCallback =
v8::ToCData<v8::IndexedPropertyDescriptorCallback>(
interceptor->descriptor());
result = args.Call(descriptorCallback, index);
} else {
Handle<Name> name = it->name();
DCHECK(!name->IsPrivate());
v8::GenericNamedPropertyDescriptorCallback descriptorCallback =
v8::ToCData<v8::GenericNamedPropertyDescriptorCallback>(
interceptor->descriptor());
result = args.Call(descriptorCallback, name);
}
if (!result.is_null()) {
// Request successfully intercepted, try to set the property
// descriptor.
Utils::ApiCheck(
PropertyDescriptor::ToPropertyDescriptor(isolate, result, desc),
it->IsElement() ? "v8::IndexedPropertyDescriptorCallback"
: "v8::NamedPropertyDescriptorCallback",
"Invalid property descriptor.");
return Just(true);
}
}
}
return Just(false);
}
} // namespace
// ES6 9.1.5.1
// Returns true on success, false if the property didn't exist, nothing if
@ -7350,6 +7401,13 @@ Maybe<bool> JSReceiver::GetOwnPropertyDescriptor(LookupIterator* it,
it->GetName(), desc);
}
Maybe<bool> intercepted = GetPropertyDescriptorWithInterceptor(it, desc);
MAYBE_RETURN(intercepted, Nothing<bool>());
if (intercepted.FromJust()) {
return Just(true);
}
// Request was not intercepted, continue as normal.
// 1. (Assert)
// 2. If O does not have an own property with key P, return undefined.
Maybe<PropertyAttributes> maybe = JSObject::GetPropertyAttributes(it);

View File

@ -2062,7 +2062,7 @@ class JSObject: public JSReceiver {
// [elements]: The elements (properties with names that are integers).
//
// Elements can be in two general modes: fast and slow. Each mode
// corrensponds to a set of object representations of elements that
// corresponds to a set of object representations of elements that
// have something in common.
//
// In the fast mode elements is a FixedArray and so each element can
@ -10810,6 +10810,7 @@ class InterceptorInfo: public Struct {
DECL_ACCESSORS(getter, Object)
DECL_ACCESSORS(setter, Object)
DECL_ACCESSORS(query, Object)
DECL_ACCESSORS(descriptor, Object)
DECL_ACCESSORS(deleter, Object)
DECL_ACCESSORS(enumerator, Object)
DECL_ACCESSORS(definer, Object)
@ -10830,7 +10831,8 @@ class InterceptorInfo: public Struct {
static const int kGetterOffset = HeapObject::kHeaderSize;
static const int kSetterOffset = kGetterOffset + kPointerSize;
static const int kQueryOffset = kSetterOffset + kPointerSize;
static const int kDeleterOffset = kQueryOffset + kPointerSize;
static const int kDescriptorOffset = kQueryOffset + kPointerSize;
static const int kDeleterOffset = kDescriptorOffset + kPointerSize;
static const int kEnumeratorOffset = kDeleterOffset + kPointerSize;
static const int kDefinerOffset = kEnumeratorOffset + kPointerSize;
static const int kDataOffset = kDefinerOffset + kPointerSize;

View File

@ -268,6 +268,23 @@ void CheckThisIndexedPropertySetter(
.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,
@ -1309,13 +1326,13 @@ 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()
// 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
// Intercept the callback by setting a non-empty handle
info.GetReturnValue().Set(name);
}
@ -1735,6 +1752,74 @@ THREADED_TEST(PropertyDefinerCallbackWithSetter) {
.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(
0, 0, EmptyPropertyDescriptorCallback, 0, 0, 0));
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(
0, 0, InterceptingPropertyDescriptorCallback, 0, 0, 0));
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());
}
}
int echo_indexed_call_count = 0;
@ -1776,17 +1861,16 @@ THREADED_TEST(PropertyHandlerInPrototype) {
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Set up a prototype chain with three interceptors.
v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration(
CheckThisIndexedPropertyHandler, CheckThisIndexedPropertySetter,
CheckThisIndexedPropertyQuery, CheckThisIndexedPropertyDeleter,
CheckThisIndexedPropertyEnumerator, CheckThisIndexedPropertyDefiner));
CheckThisIndexedPropertyEnumerator));
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
CheckThisNamedPropertyHandler, CheckThisNamedPropertySetter,
CheckThisNamedPropertyQuery, CheckThisNamedPropertyDeleter,
CheckThisNamedPropertyEnumerator, CheckThisNamedPropertyDefiner));
CheckThisNamedPropertyEnumerator));
bottom = templ->GetFunction(env.local())
.ToLocalChecked()
@ -1823,10 +1907,63 @@ THREADED_TEST(PropertyHandlerInPrototype) {
// 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');");
}