[api] Add interceptor for defineProperty().

With the Indexed/GenericNamedPropertyDefinerCallback it is possible to intercept Object.defineProperty() calls.

Requests that call JSReceiver::OrdinaryDefineOwnProperty() internally, also trigger the interceptor. This includes Object.freeze(), Object.preventExtensions(), and Object.seal().

BUG=

Review-Url: https://codereview.chromium.org/2272383002
Cr-Commit-Position: refs/heads/master@{#39094}
This commit is contained in:
franzih 2016-09-01 08:18:04 -07:00 committed by Commit bot
parent 8acb7ab9f1
commit b9d985975c
9 changed files with 678 additions and 21 deletions

View File

@ -4687,6 +4687,29 @@ typedef void (*GenericNamedPropertyDeleterCallback)(
typedef void (*GenericNamedPropertyEnumeratorCallback)(
const PropertyCallbackInfo<Array>& info);
/**
* Interceptor for defineProperty requests on an object.
*
* Use `info.GetReturnValue()` to indicate whether the request was intercepted
* or not. If the definer successfully intercepts the request, i.e., if the
* request should not be further executed, call
* `info.GetReturnValue().Set(value)`. If the definer
* did not intercept the request, i.e., if the request should be handled as
* if no interceptor is present, do not not call `Set()`.
*
* \param property The name of the property for which the request was
* intercepted.
* \param desc The property descriptor which is used to define the
* property if the request is not intercepted.
* \param info Information about the intercepted request, such as
* isolate, receiver, return value, or whether running in `'use strict'` mode.
* See `PropertyCallbackInfo`.
*
* See also `ObjectTemplate::SetNamedPropertyHandler`.
*/
typedef void (*GenericNamedPropertyDefinerCallback)(
Local<Name> key, const PropertyDescriptor& desc,
const PropertyCallbackInfo<Value>& info);
/**
* Returns the value of the property if the getter intercepts the
@ -4733,6 +4756,9 @@ typedef void (*IndexedPropertyDeleterCallback)(
typedef void (*IndexedPropertyEnumeratorCallback)(
const PropertyCallbackInfo<Array>& info);
typedef void (*IndexedPropertyDefinerCallback)(
uint32_t index, const PropertyDescriptor& desc,
const PropertyCallbackInfo<Value>& info);
/**
* Access type specification.
@ -4995,6 +5021,25 @@ struct NamedPropertyHandlerConfiguration {
query(query),
deleter(deleter),
enumerator(enumerator),
definer(0),
data(data),
flags(flags) {}
NamedPropertyHandlerConfiguration(
GenericNamedPropertyGetterCallback getter,
GenericNamedPropertySetterCallback setter,
GenericNamedPropertyQueryCallback query,
GenericNamedPropertyDeleterCallback deleter,
GenericNamedPropertyEnumeratorCallback enumerator,
GenericNamedPropertyDefinerCallback definer,
Local<Value> data = Local<Value>(),
PropertyHandlerFlags flags = PropertyHandlerFlags::kNone)
: getter(getter),
setter(setter),
query(query),
deleter(deleter),
enumerator(enumerator),
definer(definer),
data(data),
flags(flags) {}
@ -5003,6 +5048,7 @@ struct NamedPropertyHandlerConfiguration {
GenericNamedPropertyQueryCallback query;
GenericNamedPropertyDeleterCallback deleter;
GenericNamedPropertyEnumeratorCallback enumerator;
GenericNamedPropertyDefinerCallback definer;
Local<Value> data;
PropertyHandlerFlags flags;
};
@ -5023,6 +5069,24 @@ struct IndexedPropertyHandlerConfiguration {
query(query),
deleter(deleter),
enumerator(enumerator),
definer(0),
data(data),
flags(flags) {}
IndexedPropertyHandlerConfiguration(
IndexedPropertyGetterCallback getter,
IndexedPropertySetterCallback setter, IndexedPropertyQueryCallback query,
IndexedPropertyDeleterCallback deleter,
IndexedPropertyEnumeratorCallback enumerator,
IndexedPropertyDefinerCallback definer,
Local<Value> data = Local<Value>(),
PropertyHandlerFlags flags = PropertyHandlerFlags::kNone)
: getter(getter),
setter(setter),
query(query),
deleter(deleter),
enumerator(enumerator),
definer(definer),
data(data),
flags(flags) {}
@ -5031,6 +5095,7 @@ struct IndexedPropertyHandlerConfiguration {
IndexedPropertyQueryCallback query;
IndexedPropertyDeleterCallback deleter;
IndexedPropertyEnumeratorCallback enumerator;
IndexedPropertyDefinerCallback definer;
Local<Value> data;
PropertyHandlerFlags flags;
};

View File

@ -80,6 +80,24 @@ Handle<Object> PropertyCallbackArguments::Call(
return GetReturnValue<Object>(isolate);
}
Handle<Object> PropertyCallbackArguments::Call(
GenericNamedPropertyDefinerCallback f, Handle<Name> name,
const v8::PropertyDescriptor& desc) {
Isolate* isolate = this->isolate();
RuntimeCallTimerScope timer(
isolate, &RuntimeCallStats::GenericNamedPropertyDefinerCallback);
TRACE_EVENT_RUNTIME_CALL_STATS_TRACING_SCOPED(
isolate,
&tracing::TraceEventStatsTable::GenericNamedPropertyDefinerCallback);
VMState<EXTERNAL> state(isolate);
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
PropertyCallbackInfo<v8::Value> info(begin());
LOG(isolate,
ApiNamedPropertyAccess("interceptor-named-define", holder(), *name));
f(v8::Utils::ToLocal(name), desc, info);
return GetReturnValue<Object>(isolate);
}
Handle<Object> PropertyCallbackArguments::Call(IndexedPropertySetterCallback f,
uint32_t index,
Handle<Object> value) {
@ -97,6 +115,23 @@ Handle<Object> PropertyCallbackArguments::Call(IndexedPropertySetterCallback f,
return GetReturnValue<Object>(isolate);
}
Handle<Object> PropertyCallbackArguments::Call(
IndexedPropertyDefinerCallback f, uint32_t index,
const v8::PropertyDescriptor& desc) {
Isolate* isolate = this->isolate();
RuntimeCallTimerScope timer(
isolate, &RuntimeCallStats::IndexedPropertyDefinerCallback);
TRACE_EVENT_RUNTIME_CALL_STATS_TRACING_SCOPED(
isolate, &tracing::TraceEventStatsTable::IndexedPropertyDefinerCallback);
VMState<EXTERNAL> state(isolate);
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
PropertyCallbackInfo<v8::Value> info(begin());
LOG(isolate,
ApiIndexedPropertyAccess("interceptor-indexed-define", holder(), index));
f(index, desc, info);
return GetReturnValue<Object>(isolate);
}
void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
Handle<Name> name, Handle<Object> value) {
Isolate* isolate = this->isolate();

View File

@ -119,9 +119,16 @@ class PropertyCallbackArguments
inline Handle<Object> Call(GenericNamedPropertySetterCallback f,
Handle<Name> name, Handle<Object> value);
inline Handle<Object> Call(GenericNamedPropertyDefinerCallback f,
Handle<Name> name,
const v8::PropertyDescriptor& desc);
inline Handle<Object> Call(IndexedPropertySetterCallback f, uint32_t index,
Handle<Object> value);
inline Handle<Object> Call(IndexedPropertyDefinerCallback f, uint32_t index,
const v8::PropertyDescriptor& desc);
inline void Call(AccessorNameSetterCallback f, Handle<Name> name,
Handle<Object> value);

View File

@ -1506,10 +1506,10 @@ void ObjectTemplate::SetAccessor(v8::Local<Name> name,
}
template <typename Getter, typename Setter, typename Query, typename Deleter,
typename Enumerator>
typename Enumerator, typename Definer>
static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
i::Isolate* isolate, Getter getter, Setter setter, Query query,
Deleter remover, Enumerator enumerator, Local<Value> data,
Deleter remover, Enumerator enumerator, Definer definer, Local<Value> data,
PropertyHandlerFlags flags) {
auto obj = i::Handle<i::InterceptorInfo>::cast(
isolate->factory()->NewStruct(i::INTERCEPTOR_INFO_TYPE));
@ -1520,6 +1520,7 @@ static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
if (query != 0) SET_FIELD_WRAPPED(obj, set_query, query);
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);
obj->set_can_intercept_symbols(
!(static_cast<int>(flags) &
static_cast<int>(PropertyHandlerFlags::kOnlyInterceptStrings)));
@ -1536,39 +1537,35 @@ static i::Handle<i::InterceptorInfo> CreateInterceptorInfo(
}
template <typename Getter, typename Setter, typename Query, typename Deleter,
typename Enumerator>
static void ObjectTemplateSetNamedPropertyHandler(ObjectTemplate* templ,
Getter getter, Setter setter,
Query query, Deleter remover,
Enumerator enumerator,
Local<Value> data,
PropertyHandlerFlags flags) {
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) {
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, data, flags);
enumerator, definer, data, flags);
cons->set_named_property_handler(*obj);
}
void ObjectTemplate::SetNamedPropertyHandler(
NamedPropertyGetterCallback getter, NamedPropertySetterCallback setter,
NamedPropertyQueryCallback query, NamedPropertyDeleterCallback remover,
NamedPropertyEnumeratorCallback enumerator, Local<Value> data) {
ObjectTemplateSetNamedPropertyHandler(
this, getter, setter, query, remover, enumerator, data,
this, getter, setter, query, 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.data, config.flags);
config.enumerator, config.definer, config.data, config.flags);
}
@ -1628,13 +1625,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.data,
named_handler.flags);
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.data, indexed_handler.flags);
indexed_handler.enumerator, indexed_handler.definer, indexed_handler.data,
indexed_handler.flags);
info->set_indexed_interceptor(*indexed_interceptor);
if (data.IsEmpty()) {
@ -1655,7 +1653,7 @@ void ObjectTemplate::SetHandler(
EnsureNotInstantiated(cons, "v8::ObjectTemplate::SetHandler");
auto obj = CreateInterceptorInfo(
isolate, config.getter, config.setter, config.query, config.deleter,
config.enumerator, config.data, config.flags);
config.enumerator, config.definer, config.data, config.flags);
cons->set_indexed_property_handler(*obj);
}

View File

@ -677,9 +677,11 @@ class RuntimeCallTimer {
V(DeoptimizeCode) \
V(FunctionCallback) \
V(GC) \
V(GenericNamedPropertyDefinerCallback) \
V(GenericNamedPropertyDeleterCallback) \
V(GenericNamedPropertyQueryCallback) \
V(GenericNamedPropertySetterCallback) \
V(IndexedPropertyDefinerCallback) \
V(IndexedPropertyDeleterCallback) \
V(IndexedPropertyGetterCallback) \
V(IndexedPropertyQueryCallback) \

View File

@ -5709,6 +5709,7 @@ ACCESSORS(InterceptorInfo, setter, Object, kSetterOffset)
ACCESSORS(InterceptorInfo, query, Object, kQueryOffset)
ACCESSORS(InterceptorInfo, deleter, Object, kDeleterOffset)
ACCESSORS(InterceptorInfo, enumerator, Object, kEnumeratorOffset)
ACCESSORS(InterceptorInfo, definer, Object, kDefinerOffset)
ACCESSORS(InterceptorInfo, data, Object, kDataOffset)
SMI_ACCESSORS(InterceptorInfo, flags, kFlagsOffset)
BOOL_ACCESSORS(InterceptorInfo, flags, can_intercept_symbols,

View File

@ -1677,6 +1677,71 @@ Maybe<bool> SetPropertyWithInterceptorInternal(
return Just(result);
}
Maybe<bool> DefinePropertyWithInterceptorInternal(
LookupIterator* it, Handle<InterceptorInfo> interceptor,
Object::ShouldThrow should_throw, PropertyDescriptor& desc) {
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc(isolate);
if (interceptor->definer()->IsUndefined(isolate)) return Just(false);
Handle<JSObject> holder = it->GetHolder<JSObject>();
bool result;
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, should_throw);
std::unique_ptr<v8::PropertyDescriptor> descriptor(
new v8::PropertyDescriptor());
if (PropertyDescriptor::IsAccessorDescriptor(&desc)) {
descriptor.reset(new v8::PropertyDescriptor(
v8::Utils::ToLocal(desc.get()), v8::Utils::ToLocal(desc.set())));
} else if (PropertyDescriptor::IsDataDescriptor(&desc)) {
if (desc.has_writable()) {
descriptor.reset(new v8::PropertyDescriptor(
v8::Utils::ToLocal(desc.value()), desc.writable()));
} else {
descriptor.reset(
new v8::PropertyDescriptor(v8::Utils::ToLocal(desc.value())));
}
}
if (desc.has_enumerable()) {
descriptor->set_enumerable(desc.enumerable());
}
if (desc.has_configurable()) {
descriptor->set_configurable(desc.configurable());
}
if (it->IsElement()) {
uint32_t index = it->index();
v8::IndexedPropertyDefinerCallback definer =
v8::ToCData<v8::IndexedPropertyDefinerCallback>(interceptor->definer());
result = !args.Call(definer, index, *descriptor).is_null();
} else {
Handle<Name> name = it->name();
DCHECK(!name->IsPrivate());
if (name->IsSymbol() && !interceptor->can_intercept_symbols()) {
return Just(false);
}
v8::GenericNamedPropertyDefinerCallback definer =
v8::ToCData<v8::GenericNamedPropertyDefinerCallback>(
interceptor->definer());
result = !args.Call(definer, name, *descriptor).is_null();
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
return Just(result);
}
} // namespace
MaybeHandle<Object> JSObject::GetPropertyWithFailedAccessCheck(
@ -6535,6 +6600,35 @@ Maybe<bool> JSReceiver::OrdinaryDefineOwnProperty(Isolate* isolate,
it.Next();
}
// Handle interceptor
if (it.state() == LookupIterator::INTERCEPTOR) {
Handle<Map> store_target_map;
if (it.GetReceiver()->IsJSObject()) {
store_target_map = handle(it.GetStoreTarget()->map(), it.isolate());
}
if (it.HolderIsReceiverOrHiddenPrototype()) {
Maybe<bool> result = DefinePropertyWithInterceptorInternal(
&it, it.GetInterceptor(), should_throw, *desc);
if (result.IsNothing() || result.FromJust()) {
return result;
}
// Interceptor modified the store target but failed to set the
// property.
if (!store_target_map.is_null() &&
*store_target_map != it.GetStoreTarget()->map()) {
it.isolate()->PushStackTraceAndDie(
0xabababaa, v8::ToCData<void*>(it.GetInterceptor()->setter()),
nullptr, 0xabababab);
}
Utils::ApiCheck(store_target_map.is_null() ||
*store_target_map == it.GetStoreTarget()->map(),
it.IsElement() ? "v8::IndexedPropertySetterCallback"
: "v8::NamedPropertySetterCallback",
"Interceptor silently changed store target.");
}
it.Next();
}
return OrdinaryDefineOwnProperty(&it, desc, should_throw);
}

View File

@ -10790,6 +10790,7 @@ class InterceptorInfo: public Struct {
DECL_ACCESSORS(query, Object)
DECL_ACCESSORS(deleter, Object)
DECL_ACCESSORS(enumerator, Object)
DECL_ACCESSORS(definer, Object)
DECL_ACCESSORS(data, Object)
DECL_BOOLEAN_ACCESSORS(can_intercept_symbols)
DECL_BOOLEAN_ACCESSORS(all_can_read)
@ -10809,7 +10810,8 @@ class InterceptorInfo: public Struct {
static const int kQueryOffset = kSetterOffset + kPointerSize;
static const int kDeleterOffset = kQueryOffset + kPointerSize;
static const int kEnumeratorOffset = kDeleterOffset + kPointerSize;
static const int kDataOffset = kEnumeratorOffset + kPointerSize;
static const int kDefinerOffset = kEnumeratorOffset + kPointerSize;
static const int kDataOffset = kDefinerOffset + kPointerSize;
static const int kFlagsOffset = kDataOffset + kPointerSize;
static const int kSize = kFlagsOffset + kPointerSize;

View File

@ -239,6 +239,26 @@ void CheckThisNamedPropertyHandler(
.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) {
@ -1199,6 +1219,435 @@ THREADED_TEST(NamedPropertyHandlerGetter) {
.FromJust());
}
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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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::HandleScope scope(CcTest::isolate());
LocalContext env;
v8::Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(CcTest::isolate());
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
0, 0, 0, 0, 0, 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(env.local())
.FromJust());
}
// 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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(
0, 0, 0, 0, 0, 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());
}
int echo_indexed_call_count = 0;
@ -1246,12 +1695,12 @@ THREADED_TEST(PropertyHandlerInPrototype) {
templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration(
CheckThisIndexedPropertyHandler, CheckThisIndexedPropertySetter,
CheckThisIndexedPropertyQuery, CheckThisIndexedPropertyDeleter,
CheckThisIndexedPropertyEnumerator));
CheckThisIndexedPropertyEnumerator, CheckThisIndexedPropertyDefiner));
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
CheckThisNamedPropertyHandler, CheckThisNamedPropertySetter,
CheckThisNamedPropertyQuery, CheckThisNamedPropertyDeleter,
CheckThisNamedPropertyEnumerator));
CheckThisNamedPropertyEnumerator, CheckThisNamedPropertyDefiner));
bottom = templ->GetFunction(env.local())
.ToLocalChecked()
@ -1288,6 +1737,10 @@ THREADED_TEST(PropertyHandlerInPrototype) {
// Enumerators.
CompileRun("for (var p in obj) ;");
// Indexed and named definer.
CompileRun("Object.defineProperty(obj, 2, {});");
CompileRun("Object.defineProperty(obj, 'z', {});");
}