[debug] use flag to decide whether interceptor has side effect
Adds a flag onto InterceptorInfo to mark an interceptor's getter, query, and enumerator callbacks as side-effect-free. Bug: v8:7515 Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: Iafc5d2fa554d6d9a38604e179ea5b884c3b77af0 Reviewed-on: https://chromium-review.googlesource.com/957870 Commit-Queue: Erik Luo <luoe@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#51969}
This commit is contained in:
parent
3669ff293e
commit
3813cbf210
@ -13,9 +13,9 @@
|
|||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
#define FOR_EACH_CALLBACK(F) \
|
#define FOR_EACH_CALLBACK(F) \
|
||||||
F(Query, query, Object, v8::Integer) \
|
F(Query, query, Object, v8::Integer, interceptor) \
|
||||||
F(Deleter, deleter, Object, v8::Boolean)
|
F(Deleter, deleter, Object, v8::Boolean, Handle<Object>())
|
||||||
|
|
||||||
#define DCHECK_NAME_COMPATIBLE(interceptor, name) \
|
#define DCHECK_NAME_COMPATIBLE(interceptor, name) \
|
||||||
DCHECK(interceptor->is_named()); \
|
DCHECK(interceptor->is_named()); \
|
||||||
@ -32,44 +32,44 @@ namespace internal {
|
|||||||
ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F)); \
|
ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F)); \
|
||||||
PropertyCallbackInfo<API_RETURN_TYPE> callback_info(begin());
|
PropertyCallbackInfo<API_RETURN_TYPE> callback_info(begin());
|
||||||
|
|
||||||
#define CREATE_NAMED_CALLBACK(Function, type, ReturnType, ApiReturnType) \
|
#define CREATE_NAMED_CALLBACK(FUNCTION, TYPE, RETURN_TYPE, API_RETURN_TYPE, \
|
||||||
Handle<ReturnType> PropertyCallbackArguments::CallNamed##Function( \
|
INFO_FOR_SIDE_EFFECT) \
|
||||||
|
Handle<RETURN_TYPE> PropertyCallbackArguments::CallNamed##FUNCTION( \
|
||||||
Handle<InterceptorInfo> interceptor, Handle<Name> name) { \
|
Handle<InterceptorInfo> interceptor, Handle<Name> name) { \
|
||||||
DCHECK_NAME_COMPATIBLE(interceptor, name); \
|
DCHECK_NAME_COMPATIBLE(interceptor, name); \
|
||||||
Isolate* isolate = this->isolate(); \
|
Isolate* isolate = this->isolate(); \
|
||||||
RuntimeCallTimerScope timer( \
|
RuntimeCallTimerScope timer( \
|
||||||
isolate, RuntimeCallCounterId::kNamed##Function##Callback); \
|
isolate, RuntimeCallCounterId::kNamed##FUNCTION##Callback); \
|
||||||
GenericNamedProperty##Function##Callback f = \
|
GenericNamedProperty##FUNCTION##Callback f = \
|
||||||
ToCData<GenericNamedProperty##Function##Callback>( \
|
ToCData<GenericNamedProperty##FUNCTION##Callback>( \
|
||||||
interceptor->type()); \
|
interceptor->TYPE()); \
|
||||||
Handle<Object> side_effect_check_not_supported; \
|
PREPARE_CALLBACK_INFO(isolate, f, Handle<RETURN_TYPE>, API_RETURN_TYPE, \
|
||||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<ReturnType>, ApiReturnType, \
|
INFO_FOR_SIDE_EFFECT); \
|
||||||
side_effect_check_not_supported); \
|
|
||||||
LOG(isolate, \
|
LOG(isolate, \
|
||||||
ApiNamedPropertyAccess("interceptor-named-" #type, holder(), *name)); \
|
ApiNamedPropertyAccess("interceptor-named-" #TYPE, holder(), *name)); \
|
||||||
f(v8::Utils::ToLocal(name), callback_info); \
|
f(v8::Utils::ToLocal(name), callback_info); \
|
||||||
return GetReturnValue<ReturnType>(isolate); \
|
return GetReturnValue<RETURN_TYPE>(isolate); \
|
||||||
}
|
}
|
||||||
|
|
||||||
FOR_EACH_CALLBACK(CREATE_NAMED_CALLBACK)
|
FOR_EACH_CALLBACK(CREATE_NAMED_CALLBACK)
|
||||||
#undef CREATE_NAMED_CALLBACK
|
#undef CREATE_NAMED_CALLBACK
|
||||||
|
|
||||||
#define CREATE_INDEXED_CALLBACK(Function, type, ReturnType, ApiReturnType) \
|
#define CREATE_INDEXED_CALLBACK(FUNCTION, TYPE, RETURN_TYPE, API_RETURN_TYPE, \
|
||||||
Handle<ReturnType> PropertyCallbackArguments::CallIndexed##Function( \
|
INFO_FOR_SIDE_EFFECT) \
|
||||||
Handle<InterceptorInfo> interceptor, uint32_t index) { \
|
Handle<RETURN_TYPE> PropertyCallbackArguments::CallIndexed##FUNCTION( \
|
||||||
DCHECK(!interceptor->is_named()); \
|
Handle<InterceptorInfo> interceptor, uint32_t index) { \
|
||||||
Isolate* isolate = this->isolate(); \
|
DCHECK(!interceptor->is_named()); \
|
||||||
RuntimeCallTimerScope timer( \
|
Isolate* isolate = this->isolate(); \
|
||||||
isolate, RuntimeCallCounterId::kIndexed##Function##Callback); \
|
RuntimeCallTimerScope timer( \
|
||||||
IndexedProperty##Function##Callback f = \
|
isolate, RuntimeCallCounterId::kIndexed##FUNCTION##Callback); \
|
||||||
ToCData<IndexedProperty##Function##Callback>(interceptor->type()); \
|
IndexedProperty##FUNCTION##Callback f = \
|
||||||
Handle<Object> side_effect_check_not_supported; \
|
ToCData<IndexedProperty##FUNCTION##Callback>(interceptor->TYPE()); \
|
||||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<ReturnType>, ApiReturnType, \
|
PREPARE_CALLBACK_INFO(isolate, f, Handle<RETURN_TYPE>, API_RETURN_TYPE, \
|
||||||
side_effect_check_not_supported); \
|
INFO_FOR_SIDE_EFFECT); \
|
||||||
LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-" #type, \
|
LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-" #TYPE, \
|
||||||
holder(), index)); \
|
holder(), index)); \
|
||||||
f(index, callback_info); \
|
f(index, callback_info); \
|
||||||
return GetReturnValue<ReturnType>(isolate); \
|
return GetReturnValue<RETURN_TYPE>(isolate); \
|
||||||
}
|
}
|
||||||
|
|
||||||
FOR_EACH_CALLBACK(CREATE_INDEXED_CALLBACK)
|
FOR_EACH_CALLBACK(CREATE_INDEXED_CALLBACK)
|
||||||
@ -87,8 +87,7 @@ Handle<Object> PropertyCallbackArguments::CallNamedGetter(
|
|||||||
ApiNamedPropertyAccess("interceptor-named-getter", holder(), *name));
|
ApiNamedPropertyAccess("interceptor-named-getter", holder(), *name));
|
||||||
GenericNamedPropertyGetterCallback f =
|
GenericNamedPropertyGetterCallback f =
|
||||||
ToCData<GenericNamedPropertyGetterCallback>(interceptor->getter());
|
ToCData<GenericNamedPropertyGetterCallback>(interceptor->getter());
|
||||||
Handle<Object> side_effect_check_not_supported;
|
return BasicCallNamedGetterCallback(f, name, interceptor);
|
||||||
return BasicCallNamedGetterCallback(f, name, side_effect_check_not_supported);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Object> PropertyCallbackArguments::CallNamedDescriptor(
|
Handle<Object> PropertyCallbackArguments::CallNamedDescriptor(
|
||||||
@ -102,8 +101,7 @@ Handle<Object> PropertyCallbackArguments::CallNamedDescriptor(
|
|||||||
GenericNamedPropertyDescriptorCallback f =
|
GenericNamedPropertyDescriptorCallback f =
|
||||||
ToCData<GenericNamedPropertyDescriptorCallback>(
|
ToCData<GenericNamedPropertyDescriptorCallback>(
|
||||||
interceptor->descriptor());
|
interceptor->descriptor());
|
||||||
Handle<Object> side_effect_check_not_supported;
|
return BasicCallNamedGetterCallback(f, name, interceptor);
|
||||||
return BasicCallNamedGetterCallback(f, name, side_effect_check_not_supported);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Object> PropertyCallbackArguments::BasicCallNamedGetterCallback(
|
Handle<Object> PropertyCallbackArguments::BasicCallNamedGetterCallback(
|
||||||
@ -204,7 +202,7 @@ Handle<Object> PropertyCallbackArguments::CallIndexedGetter(
|
|||||||
ApiIndexedPropertyAccess("interceptor-indexed-getter", holder(), index));
|
ApiIndexedPropertyAccess("interceptor-indexed-getter", holder(), index));
|
||||||
IndexedPropertyGetterCallback f =
|
IndexedPropertyGetterCallback f =
|
||||||
ToCData<IndexedPropertyGetterCallback>(interceptor->getter());
|
ToCData<IndexedPropertyGetterCallback>(interceptor->getter());
|
||||||
return BasicCallIndexedGetterCallback(f, index);
|
return BasicCallIndexedGetterCallback(f, index, interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Object> PropertyCallbackArguments::CallIndexedDescriptor(
|
Handle<Object> PropertyCallbackArguments::CallIndexedDescriptor(
|
||||||
@ -217,15 +215,13 @@ Handle<Object> PropertyCallbackArguments::CallIndexedDescriptor(
|
|||||||
holder(), index));
|
holder(), index));
|
||||||
IndexedPropertyDescriptorCallback f =
|
IndexedPropertyDescriptorCallback f =
|
||||||
ToCData<IndexedPropertyDescriptorCallback>(interceptor->descriptor());
|
ToCData<IndexedPropertyDescriptorCallback>(interceptor->descriptor());
|
||||||
return BasicCallIndexedGetterCallback(f, index);
|
return BasicCallIndexedGetterCallback(f, index, interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Object> PropertyCallbackArguments::BasicCallIndexedGetterCallback(
|
Handle<Object> PropertyCallbackArguments::BasicCallIndexedGetterCallback(
|
||||||
IndexedPropertyGetterCallback f, uint32_t index) {
|
IndexedPropertyGetterCallback f, uint32_t index, Handle<Object> info) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
Handle<Object> side_effect_check_not_supported;
|
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value, info);
|
||||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value,
|
|
||||||
side_effect_check_not_supported);
|
|
||||||
f(index, callback_info);
|
f(index, callback_info);
|
||||||
return GetReturnValue<Object>(isolate);
|
return GetReturnValue<Object>(isolate);
|
||||||
}
|
}
|
||||||
@ -237,9 +233,7 @@ Handle<JSObject> PropertyCallbackArguments::CallPropertyEnumerator(
|
|||||||
v8::ToCData<IndexedPropertyEnumeratorCallback>(interceptor->enumerator());
|
v8::ToCData<IndexedPropertyEnumeratorCallback>(interceptor->enumerator());
|
||||||
// TODO(cbruni): assert same type for indexed and named callback.
|
// TODO(cbruni): assert same type for indexed and named callback.
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
Handle<Object> side_effect_check_not_supported;
|
PREPARE_CALLBACK_INFO(isolate, f, Handle<JSObject>, v8::Array, interceptor);
|
||||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<JSObject>, v8::Array,
|
|
||||||
side_effect_check_not_supported);
|
|
||||||
f(callback_info);
|
f(callback_info);
|
||||||
return GetReturnValue<JSObject>(isolate);
|
return GetReturnValue<JSObject>(isolate);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ class PropertyCallbackArguments
|
|||||||
Handle<InterceptorInfo> interceptor);
|
Handle<InterceptorInfo> interceptor);
|
||||||
|
|
||||||
inline Handle<Object> BasicCallIndexedGetterCallback(
|
inline Handle<Object> BasicCallIndexedGetterCallback(
|
||||||
IndexedPropertyGetterCallback f, uint32_t index);
|
IndexedPropertyGetterCallback f, uint32_t index, Handle<Object> info);
|
||||||
inline Handle<Object> BasicCallNamedGetterCallback(
|
inline Handle<Object> BasicCallNamedGetterCallback(
|
||||||
GenericNamedPropertyGetterCallback f, Handle<Name> name,
|
GenericNamedPropertyGetterCallback f, Handle<Name> name,
|
||||||
Handle<Object> info);
|
Handle<Object> info);
|
||||||
|
@ -937,6 +937,12 @@ bool DebugEvaluate::CallbackHasNoSideEffect(Object* callback_info) {
|
|||||||
info->name()->ShortPrint();
|
info->name()->ShortPrint();
|
||||||
PrintF("' may cause side effect.\n");
|
PrintF("' may cause side effect.\n");
|
||||||
}
|
}
|
||||||
|
} else if (callback_info->IsInterceptorInfo()) {
|
||||||
|
InterceptorInfo* info = InterceptorInfo::cast(callback_info);
|
||||||
|
if (info->has_no_side_effect()) return true;
|
||||||
|
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||||
|
PrintF("[debug-evaluate] API Interceptor may cause side effect.\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2401,6 +2401,7 @@ BOOL_ACCESSORS(InterceptorInfo, flags, can_intercept_symbols,
|
|||||||
BOOL_ACCESSORS(InterceptorInfo, flags, all_can_read, kAllCanReadBit)
|
BOOL_ACCESSORS(InterceptorInfo, flags, all_can_read, kAllCanReadBit)
|
||||||
BOOL_ACCESSORS(InterceptorInfo, flags, non_masking, kNonMasking)
|
BOOL_ACCESSORS(InterceptorInfo, flags, non_masking, kNonMasking)
|
||||||
BOOL_ACCESSORS(InterceptorInfo, flags, is_named, kNamed)
|
BOOL_ACCESSORS(InterceptorInfo, flags, is_named, kNamed)
|
||||||
|
BOOL_ACCESSORS(InterceptorInfo, flags, has_no_side_effect, kHasNoSideEffect)
|
||||||
|
|
||||||
ACCESSORS(CallHandlerInfo, callback, Object, kCallbackOffset)
|
ACCESSORS(CallHandlerInfo, callback, Object, kCallbackOffset)
|
||||||
ACCESSORS(CallHandlerInfo, js_callback, Object, kJsCallbackOffset)
|
ACCESSORS(CallHandlerInfo, js_callback, Object, kJsCallbackOffset)
|
||||||
|
@ -4557,6 +4557,7 @@ class InterceptorInfo: public Struct {
|
|||||||
DECL_BOOLEAN_ACCESSORS(all_can_read)
|
DECL_BOOLEAN_ACCESSORS(all_can_read)
|
||||||
DECL_BOOLEAN_ACCESSORS(non_masking)
|
DECL_BOOLEAN_ACCESSORS(non_masking)
|
||||||
DECL_BOOLEAN_ACCESSORS(is_named)
|
DECL_BOOLEAN_ACCESSORS(is_named)
|
||||||
|
DECL_BOOLEAN_ACCESSORS(has_no_side_effect)
|
||||||
|
|
||||||
inline int flags() const;
|
inline int flags() const;
|
||||||
inline void set_flags(int flags);
|
inline void set_flags(int flags);
|
||||||
@ -4582,6 +4583,7 @@ class InterceptorInfo: public Struct {
|
|||||||
static const int kAllCanReadBit = 1;
|
static const int kAllCanReadBit = 1;
|
||||||
static const int kNonMasking = 2;
|
static const int kNonMasking = 2;
|
||||||
static const int kNamed = 3;
|
static const int kNamed = 3;
|
||||||
|
static const int kHasNoSideEffect = 4;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DISALLOW_IMPLICIT_CONSTRUCTORS(InterceptorInfo);
|
DISALLOW_IMPLICIT_CONSTRUCTORS(InterceptorInfo);
|
||||||
|
@ -64,6 +64,14 @@ void EmptyInterceptorGetter(Local<Name> name,
|
|||||||
void EmptyInterceptorSetter(Local<Name> name, Local<Value> value,
|
void EmptyInterceptorSetter(Local<Name> name, Local<Value> value,
|
||||||
const v8::PropertyCallbackInfo<v8::Value>& info) {}
|
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,
|
void SimpleAccessorGetter(Local<String> name,
|
||||||
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||||
@ -2587,6 +2595,47 @@ static void InterceptorForHiddenProperties(
|
|||||||
interceptor_for_hidden_properties_called = true;
|
interceptor_for_hidden_properties_called = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
THREADED_TEST(NoSideEffectPropertyHandler) {
|
||||||
|
v8::Isolate* isolate = CcTest::isolate();
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
||||||
|
EmptyInterceptorGetter, EmptyInterceptorSetter, EmptyInterceptorQuery,
|
||||||
|
EmptyInterceptorDeleter, EmptyInterceptorEnumerator));
|
||||||
|
|
||||||
|
LocalContext context;
|
||||||
|
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"), true).IsEmpty());
|
||||||
|
CHECK(
|
||||||
|
v8::debug::EvaluateGlobal(isolate, v8_str("obj.x = 1"), true).IsEmpty());
|
||||||
|
CHECK(
|
||||||
|
v8::debug::EvaluateGlobal(isolate, v8_str("'x' in obj"), true).IsEmpty());
|
||||||
|
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("delete obj.x"), true)
|
||||||
|
.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) ; })()"), true)
|
||||||
|
.IsEmpty());
|
||||||
|
|
||||||
|
// TODO(luoe): turn on has_no_side_effect flag from API once it is exposed.
|
||||||
|
i::Handle<i::JSObject> internal_object =
|
||||||
|
i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*object));
|
||||||
|
internal_object->GetNamedInterceptor()->set_has_no_side_effect(true);
|
||||||
|
|
||||||
|
v8::debug::EvaluateGlobal(isolate, v8_str("obj.x"), true).ToLocalChecked();
|
||||||
|
CHECK(
|
||||||
|
v8::debug::EvaluateGlobal(isolate, v8_str("obj.x = 1"), true).IsEmpty());
|
||||||
|
v8::debug::EvaluateGlobal(isolate, v8_str("'x' in obj"), true)
|
||||||
|
.ToLocalChecked();
|
||||||
|
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("delete obj.x"), true)
|
||||||
|
.IsEmpty());
|
||||||
|
v8::debug::EvaluateGlobal(
|
||||||
|
isolate, v8_str("(function() { for (var p in obj) ; })()"), true)
|
||||||
|
.ToLocalChecked();
|
||||||
|
}
|
||||||
|
|
||||||
THREADED_TEST(HiddenPropertiesWithInterceptors) {
|
THREADED_TEST(HiddenPropertiesWithInterceptors) {
|
||||||
LocalContext context;
|
LocalContext context;
|
||||||
|
Loading…
Reference in New Issue
Block a user