Commit 6da12d42 authored by Erik Luo's avatar Erik Luo Committed by Commit Bot

[debug] expose SideEffectType for whitelisting embedder callbacks

This exposes new flags to allow embedders to whitelist callbacks as
side-effect-free during evaluation with throwOnSideEffect.

Accessors and Functions/FunctionTemplates can take a new param on:
- v8::Object::SetNativeDataProperty
- v8::Object::SetLazyDataProperty
- v8::Object::SetAccessor
- v8::FunctionTemplate::New
- v8::FunctionTemplate::NewWithCache
- v8::Function::New

While Interceptors can be created with an additional flag:
PropertyHandlerFlag::kHasNoSideEffect

Bug: v8:7515
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: I14823316bdd6de6d362a1104b65f13504d0db056
Reviewed-on: https://chromium-review.googlesource.com/994550
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52367}
parent 96e83b78
......@@ -3109,6 +3109,15 @@ enum PropertyFilter {
SKIP_SYMBOLS = 16
};
/**
* Options for marking whether callbacks may trigger JS-observable side effects.
* Side-effect-free callbacks are whitelisted during debug evaluation with
* throwOnSideEffect. It applies when calling a Function, FunctionTemplate,
* or an Accessor's getter callback. For Interceptors, please see
* PropertyHandlerFlags's kHasNoSideEffect.
*/
enum class SideEffectType { kHasSideEffect, kHasNoSideEffect };
/**
* Keys/Properties filter enums:
*
......@@ -3241,13 +3250,15 @@ class V8_EXPORT Object : public Value {
V8_WARN_UNUSED_RESULT Maybe<bool> Delete(Local<Context> context,
uint32_t index);
V8_WARN_UNUSED_RESULT Maybe<bool> SetAccessor(Local<Context> context,
Local<Name> name,
AccessorNameGetterCallback getter,
AccessorNameSetterCallback setter = 0,
MaybeLocal<Value> data = MaybeLocal<Value>(),
AccessControl settings = DEFAULT,
PropertyAttribute attribute = None);
/**
* Note: SideEffectType affects the getter only, not the setter.
*/
V8_WARN_UNUSED_RESULT Maybe<bool> SetAccessor(
Local<Context> context, Local<Name> name,
AccessorNameGetterCallback getter, AccessorNameSetterCallback setter = 0,
MaybeLocal<Value> data = MaybeLocal<Value>(),
AccessControl settings = DEFAULT, PropertyAttribute attribute = None,
SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect);
void SetAccessorProperty(Local<Name> name, Local<Function> getter,
Local<Function> setter = Local<Function>(),
......@@ -3262,7 +3273,8 @@ class V8_EXPORT Object : public Value {
Local<Context> context, Local<Name> name,
AccessorNameGetterCallback getter,
AccessorNameSetterCallback setter = nullptr,
Local<Value> data = Local<Value>(), PropertyAttribute attributes = None);
Local<Value> data = Local<Value>(), PropertyAttribute attributes = None,
SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect);
/**
* Attempts to create a property with the given name which behaves like a data
......@@ -3275,7 +3287,8 @@ class V8_EXPORT Object : public Value {
V8_WARN_UNUSED_RESULT Maybe<bool> SetLazyDataProperty(
Local<Context> context, Local<Name> name,
AccessorNameGetterCallback getter, Local<Value> data = Local<Value>(),
PropertyAttribute attributes = None);
PropertyAttribute attributes = None,
SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect);
/**
* Functionality for private properties.
......@@ -3867,7 +3880,8 @@ class V8_EXPORT Function : public Object {
static MaybeLocal<Function> New(
Local<Context> context, FunctionCallback callback,
Local<Value> data = Local<Value>(), int length = 0,
ConstructorBehavior behavior = ConstructorBehavior::kAllow);
ConstructorBehavior behavior = ConstructorBehavior::kAllow,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
static V8_DEPRECATE_SOON(
"Use maybe version",
Local<Function> New(Isolate* isolate, FunctionCallback callback,
......@@ -5521,7 +5535,8 @@ class V8_EXPORT FunctionTemplate : public Template {
Isolate* isolate, FunctionCallback callback = 0,
Local<Value> data = Local<Value>(),
Local<Signature> signature = Local<Signature>(), int length = 0,
ConstructorBehavior behavior = ConstructorBehavior::kAllow);
ConstructorBehavior behavior = ConstructorBehavior::kAllow,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
/** Get a template included in the snapshot by index. */
static MaybeLocal<FunctionTemplate> FromSnapshot(Isolate* isolate,
......@@ -5533,7 +5548,8 @@ class V8_EXPORT FunctionTemplate : public Template {
static Local<FunctionTemplate> NewWithCache(
Isolate* isolate, FunctionCallback callback,
Local<Private> cache_property, Local<Value> data = Local<Value>(),
Local<Signature> signature = Local<Signature>(), int length = 0);
Local<Signature> signature = Local<Signature>(), int length = 0,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
/** Returns the unique function instance in the current execution context.*/
V8_DEPRECATE_SOON("Use maybe version", Local<Function> GetFunction());
......@@ -5554,8 +5570,9 @@ class V8_EXPORT FunctionTemplate : public Template {
* callback is called whenever the function created from this
* FunctionTemplate is called.
*/
void SetCallHandler(FunctionCallback callback,
Local<Value> data = Local<Value>());
void SetCallHandler(
FunctionCallback callback, Local<Value> data = Local<Value>(),
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
/** Set the predefined length property for the FunctionTemplate. */
void SetLength(int length);
......@@ -5666,6 +5683,11 @@ enum class PropertyHandlerFlags {
* named interceptors.
*/
kOnlyInterceptStrings = 1 << 2,
/**
* The getter, query, enumerator callbacks do not produce side effects.
*/
kHasNoSideEffect = 1 << 3,
};
struct NamedPropertyHandlerConfiguration {
......
This diff is collapsed.
......@@ -231,6 +231,9 @@ TEST(CachedAccessorOnGlobalObject) {
namespace {
static void Getter(v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {}
static void Setter(v8::Local<v8::String> name, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {}
}
......@@ -258,3 +261,94 @@ TEST(RedeclareAccessor) {
v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty();
CHECK(try_catch.HasCaught());
}
// Accessors can be whitelisted as side-effect-free via SetAccessor.
TEST(AccessorSetHasNoSideEffect) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Object> obj = templ->NewInstance(env.local()).ToLocalChecked();
CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust());
obj->SetAccessor(context, v8_str("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty());
obj->SetAccessor(context, v8_str("foo"), Getter, 0,
v8::MaybeLocal<v8::Value>(), v8::AccessControl::DEFAULT,
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked();
// Check that setter is not whitelisted.
v8::TryCatch try_catch(isolate);
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false)
.ToLocalChecked()
->Int32Value(env.local())
.FromJust());
}
// Accessors can be whitelisted as side-effect-free via SetNativeDataProperty.
TEST(AccessorSetNativeDataPropertyHasNoSideEffect) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Object> obj = templ->NewInstance(env.local()).ToLocalChecked();
CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust());
obj->SetNativeDataProperty(context, v8_str("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty());
obj->SetNativeDataProperty(
context, v8_str("foo"), Getter, nullptr, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked();
// Check that setter is not whitelisted.
v8::TryCatch try_catch(isolate);
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false)
.ToLocalChecked()
->Int32Value(env.local())
.FromJust());
}
// Accessors can be whitelisted as side-effect-free via SetLazyDataProperty.
TEST(AccessorSetLazyDataPropertyHasNoSideEffect) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Object> obj = templ->NewInstance(env.local()).ToLocalChecked();
CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust());
obj->SetLazyDataProperty(context, v8_str("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty());
obj->SetLazyDataProperty(context, v8_str("foo"), Getter,
v8::Local<v8::Value>(), v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked();
// Check that setter is not whitelisted.
v8::TryCatch try_catch(isolate);
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false)
.ToLocalChecked()
->Int32Value(env.local())
.FromJust());
}
......@@ -2598,12 +2598,12 @@ static void InterceptorForHiddenProperties(
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));
LocalContext context;
v8::Local<v8::Object> object =
templ->NewInstance(context.local()).ToLocalChecked();
context->Global()->Set(context.local(), v8_str("obj"), object).FromJust();
......@@ -2620,20 +2620,25 @@ THREADED_TEST(NoSideEffectPropertyHandler) {
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);
// 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("obj.x"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("obj2.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)
v8::debug::EvaluateGlobal(isolate, v8_str("obj2.x = 1"), true).IsEmpty());
v8::debug::EvaluateGlobal(isolate, v8_str("'x' in obj2"), true)
.ToLocalChecked();
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("delete obj.x"), true)
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("delete obj2.x"), true)
.IsEmpty());
v8::debug::EvaluateGlobal(
isolate, v8_str("(function() { for (var p in obj) ; })()"), true)
isolate, v8_str("(function() { for (var p in obj2) ; })()"), true)
.ToLocalChecked();
}
......
......@@ -12981,16 +12981,95 @@ TEST(CallHandlerHasNoSideEffect) {
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("new f()"), true).IsEmpty());
// Side-effect-free version.
// TODO(luoe): turn on has_no_side_effect flag from API once it is exposed.
i::Handle<i::FunctionTemplateInfo> info =
i::Handle<i::FunctionTemplateInfo>::cast(v8::Utils::OpenHandle(*templ));
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
i::CallHandlerInfo* handler_info =
i::CallHandlerInfo::cast(info->call_code());
CHECK(!handler_info->IsSideEffectFreeCallHandlerInfo());
handler_info->set_map(heap->side_effect_free_call_handler_info_map());
v8::debug::EvaluateGlobal(isolate, v8_str("f()"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("new f()"), true).ToLocalChecked();
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New(isolate);
templ2->SetCallHandler(EmptyHandler, v8::Local<Value>(),
v8::SideEffectType::kHasNoSideEffect);
CHECK(context->Global()
->Set(context.local(), v8_str("f2"),
templ2->GetFunction(context.local()).ToLocalChecked())
.FromJust());
v8::debug::EvaluateGlobal(isolate, v8_str("f2()"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("new f2()"), true).ToLocalChecked();
}
TEST(FunctionTemplateNewHasNoSideEffect) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext context;
// Function template with call handler.
Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(isolate, EmptyHandler);
CHECK(context->Global()
->Set(context.local(), v8_str("f"),
templ->GetFunction(context.local()).ToLocalChecked())
.FromJust());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("f()"), true).IsEmpty());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("new f()"), true).IsEmpty());
// Side-effect-free version.
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New(
isolate, EmptyHandler, v8::Local<Value>(), v8::Local<v8::Signature>(), 0,
v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasNoSideEffect);
CHECK(context->Global()
->Set(context.local(), v8_str("f2"),
templ2->GetFunction(context.local()).ToLocalChecked())
.FromJust());
v8::debug::EvaluateGlobal(isolate, v8_str("f2()"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("new f2()"), true).ToLocalChecked();
}
TEST(FunctionTemplateNewWithCacheHasNoSideEffect) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext context;
v8::Local<v8::Private> priv =
v8::Private::ForApi(isolate, v8_str("Foo#draft"));
// Function template with call handler.
Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::NewWithCache(isolate, EmptyHandler, priv);
CHECK(context->Global()
->Set(context.local(), v8_str("f"),
templ->GetFunction(context.local()).ToLocalChecked())
.FromJust());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("f()"), true).IsEmpty());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("new f()"), true).IsEmpty());
// Side-effect-free version.
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::NewWithCache(
isolate, EmptyHandler, priv, v8::Local<Value>(),
v8::Local<v8::Signature>(), 0, v8::SideEffectType::kHasNoSideEffect);
CHECK(context->Global()
->Set(context.local(), v8_str("f2"),
templ2->GetFunction(context.local()).ToLocalChecked())
.FromJust());
v8::debug::EvaluateGlobal(isolate, v8_str("f2()"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("new f2()"), true).ToLocalChecked();
}
TEST(FunctionNewHasNoSideEffect) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext context;
// Function with side-effect.
Local<Function> func =
Function::New(context.local(), EmptyHandler).ToLocalChecked();
CHECK(context->Global()->Set(context.local(), v8_str("f"), func).FromJust());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("f()"), true).IsEmpty());
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("new f()"), true).IsEmpty());
// Side-effect-free version.
Local<Function> func2 =
Function::New(context.local(), EmptyHandler, Local<Value>(), 0,
v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasNoSideEffect)
.ToLocalChecked();
CHECK(
context->Global()->Set(context.local(), v8_str("f2"), func2).FromJust());
v8::debug::EvaluateGlobal(isolate, v8_str("f2()"), true).ToLocalChecked();
v8::debug::EvaluateGlobal(isolate, v8_str("new f2()"), true).ToLocalChecked();
}
TEST(CallHandlerAsFunctionHasNoSideEffectNotSupported) {
......@@ -13006,7 +13085,6 @@ TEST(CallHandlerAsFunctionHasNoSideEffectNotSupported) {
CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj()"), true).IsEmpty());
// Side-effect-free version is not supported.
// TODO(luoe): turn on has_no_side_effect flag from API once it is exposed.
i::FunctionTemplateInfo* cons = i::FunctionTemplateInfo::cast(
v8::Utils::OpenHandle(*templ)->constructor());
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment