Add a debug v8 API SetDetachedWindowReason

A window is a Blink concept. This API marks the context as backing
a detached window. This doesn't necessarily mean that the context is
detached.

Every time a JS function is called within a context that has a non-zero
DetachedWindowReason, Runtime::kReportDetachedWindowAccess is invoked,
which will report this call to Blink via a callback, which in turn can
report number of such calls via UKM metrics.

Bug: chromium:1018156
Change-Id: I67c89fef459f4efcb912229eed8a4f3ea3b60f54
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1862829
Auto-Submit: Bartek Nowierski <bartekn@chromium.org>
Commit-Queue: Bartek Nowierski <bartekn@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64707}
This commit is contained in:
Bartek Nowierski 2019-10-31 13:07:35 +09:00 committed by Commit Bot
parent f09b1337e8
commit 63dc55568b
12 changed files with 155 additions and 1 deletions

View File

@ -9904,6 +9904,32 @@ class V8_EXPORT Context {
*/
void DetachGlobal();
/**
* Reason for detaching a window.
*/
enum DetachedWindowReason {
kWindowNotDetached = 0,
kDetachedWindowByNavigation,
kDetachedWindowByClosing,
kDetachedWindowByOtherReason
};
/**
* Sets a reason for detaching window, for reporting purposes.
*
* This API is experimental and may change or be deleted. Do not use!
*
* A window is a Blink concept. This API marks the context as backing
* a detached window. This doesn't necessarily mean that the context is
* detached.
*
* Every time a JS function is called within a context that has a non-zero
* DetachedWindowReason, Runtime::kReportDetachedWindowAccess is invoked,
* which will report this call to Blink via a callback, which in turn can
* report number of such calls via UKM metrics.
*/
void SetDetachedWindowReason(DetachedWindowReason reason);
/**
* Creates a new context and returns a handle to the newly allocated
* context.

View File

@ -6002,6 +6002,20 @@ void Context::DetachGlobal() {
isolate->bootstrapper()->DetachGlobal(context);
}
void Context::SetDetachedWindowReason(DetachedWindowReason reason) {
i::Handle<i::Context> context = Utils::OpenHandle(this);
i::Isolate* isolate = context->GetIsolate();
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
CHECK(context->IsNativeContext());
i::Handle<i::NativeContext> native_context =
i::Handle<i::NativeContext>::cast(context);
// Prioritize kDetachedWindowByNavigation over other reasons.
if (native_context->GetDetachedWindowReason() !=
kDetachedWindowByNavigation) {
native_context->SetDetachedWindowReason(reason);
}
}
Local<v8::Object> Context::GetExtrasBindingObject() {
i::Handle<i::Context> context = Utils::OpenHandle(this);
i::Isolate* isolate = context->GetIsolate();

View File

@ -1953,6 +1953,14 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
__ tst(r3, Operand(SharedFunctionInfo::IsNativeBit::kMask |
SharedFunctionInfo::IsStrictBit::kMask));
__ b(ne, &done_convert);
// Check if the window is marked as detached.
Label detached_window, after_detached_window;
__ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX, r3);
__ cmp(r3, Operand(Smi::zero()));
__ b(ne, &detached_window);
__ bind(&after_detached_window);
{
// ----------- S t a t e -------------
// -- r0 : the number of arguments (not including the receiver)
@ -2024,6 +2032,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
__ push(r1);
__ CallRuntime(Runtime::kThrowConstructorNonCallableError);
}
__ bind(&detached_window);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ PushCallerSaved(kDontSaveFPRegs, r3);
__ CallRuntime(Runtime::kReportDetachedWindowAccess);
__ PopCallerSaved(kDontSaveFPRegs, r3);
}
__ jmp(&after_detached_window);
}
namespace {

View File

@ -2361,6 +2361,14 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
SharedFunctionInfo::IsNativeBit::kMask |
SharedFunctionInfo::IsStrictBit::kMask,
&done_convert);
// Check if the window is marked as detached.
Label detached_window, after_detached_window;
__ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX, x3);
__ CmpTagged(x3, Immediate(Smi::zero()));
__ B(ne, &detached_window);
__ bind(&after_detached_window);
{
// ----------- S t a t e -------------
// -- x0 : the number of arguments (not including the receiver)
@ -2431,6 +2439,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
__ PushArgument(x1);
__ CallRuntime(Runtime::kThrowConstructorNonCallableError);
}
__ bind(&detached_window);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ PushCallerSaved(kDontSaveFPRegs, x3);
__ CallRuntime(Runtime::kReportDetachedWindowAccess);
__ PopCallerSaved(kDontSaveFPRegs, x3);
}
__ jmp(&after_detached_window);
}
namespace {

View File

@ -2206,6 +2206,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
Immediate(SharedFunctionInfo::IsNativeBit::kMask |
SharedFunctionInfo::IsStrictBit::kMask));
__ j(not_zero, &done_convert);
// Check if the window is marked as detached.
Label detached_window, after_detached_window;
__ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX,
kScratchRegister);
__ Cmp(kScratchRegister, Smi::zero());
__ j(not_zero, &detached_window);
__ bind(&after_detached_window);
{
// ----------- S t a t e -------------
// -- rax : the number of arguments (not including the receiver)
@ -2283,6 +2292,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm,
__ Push(rdi);
__ CallRuntime(Runtime::kThrowConstructorNonCallableError);
}
__ bind(&detached_window);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ PushCallerSaved(kDontSaveFPRegs, kScratchRegister);
__ CallRuntime(Runtime::kReportDetachedWindowAccess);
__ PopCallerSaved(kDontSaveFPRegs, kScratchRegister);
}
__ jmp(&after_detached_window);
}
namespace {

View File

@ -1185,10 +1185,10 @@ void MacroAssembler::SmiCompare(Register dst, Smi src) {
}
void MacroAssembler::Cmp(Register dst, Smi src) {
DCHECK_NE(dst, kScratchRegister);
if (src.value() == 0) {
test_tagged(dst, dst);
} else {
DCHECK_NE(dst, kScratchRegister);
Register constant_reg = GetSmiConstant(src);
cmp_tagged(dst, constant_reg);
}

View File

@ -1431,6 +1431,7 @@ Handle<NativeContext> Factory::NewNativeContext() {
context->set_scope_info(ReadOnlyRoots(isolate()).native_scope_info());
context->set_previous(Context::unchecked_cast(Smi::zero()));
context->set_extension(*undefined_value());
context->SetDetachedWindowReason(v8::Context::kWindowNotDetached);
context->set_errors_thrown(Smi::zero());
context->set_math_random_index(Smi::zero());
context->set_serialized_objects(*empty_fixed_array());

View File

@ -485,5 +485,16 @@ STATIC_ASSERT(NativeContext::kSize ==
(Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) +
kSystemPointerSize));
void NativeContext::SetDetachedWindowReason(
v8::Context::DetachedWindowReason reason) {
set_detached_window_reason(Smi::FromEnum(reason));
}
v8::Context::DetachedWindowReason NativeContext::GetDetachedWindowReason()
const {
return static_cast<v8::Context::DetachedWindowReason>(
detached_window_reason().value());
}
} // namespace internal
} // namespace v8

View File

@ -366,6 +366,7 @@ enum ContextLookupFlags {
V(WEAKMAP_GET_INDEX, JSFunction, weakmap_get) \
V(WEAKSET_ADD_INDEX, JSFunction, weakset_add) \
V(OSR_CODE_CACHE_INDEX, WeakFixedArray, osr_code_cache) \
V(DETACHED_WINDOW_REASON_INDEX, Smi, detached_window_reason) \
NATIVE_CONTEXT_INTRINSIC_FUNCTIONS(V)
// A table of all script contexts. Every loaded top-level script with top-level
@ -729,6 +730,9 @@ class NativeContext : public Context {
void IncrementErrorsThrown();
int GetErrorsThrown();
void SetDetachedWindowReason(v8::Context::DetachedWindowReason reason);
v8::Context::DetachedWindowReason GetDetachedWindowReason() const;
private:
STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) ==
Internals::kNativeContextEmbedderDataOffset);

View File

@ -82,6 +82,19 @@ RUNTIME_FUNCTION(Runtime_ThrowSymbolAsyncIteratorInvalid) {
isolate, NewTypeError(MessageTemplate::kSymbolAsyncIteratorInvalid));
}
RUNTIME_FUNCTION(Runtime_ReportDetachedWindowAccess) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
Handle<NativeContext> native_context(isolate->context().native_context(),
isolate);
// TODO(bartekn,chromium:1018156): Report this to Blink, for it to emit it
// via UKM. Use native_context->detached_window_reason().value()
// This will be addressed as the first step after this CL lands.
// The return value isn't needed, but RUNTIME_FUNCTION sets it up.
return ReadOnlyRoots(isolate).undefined_value();
}
#define THROW_ERROR(isolate, args, call) \
HandleScope scope(isolate); \
DCHECK_LE(1, args.length()); \

View File

@ -221,6 +221,7 @@ namespace internal {
F(NewTypeError, 2, 1) \
F(OrdinaryHasInstance, 2, 1) \
F(PromoteScheduledException, 0, 1) \
F(ReportDetachedWindowAccess, 0, 1) \
F(ReportMessage, 1, 1) \
F(ReThrow, 1, 1) \
F(RunMicrotaskCallback, 2, 1) \

View File

@ -9646,6 +9646,38 @@ TEST(DetachedAccesses) {
}
TEST(DetachedWindow) {
LocalContext env1;
v8::HandleScope scope(env1->GetIsolate());
// Create second environment.
Local<ObjectTemplate> inner_global_template =
FunctionTemplate::New(env1->GetIsolate())->InstanceTemplate();
v8::Local<Context> env2 =
Context::New(env1->GetIsolate(), nullptr, inner_global_template);
Local<Value> foo = v8_str("foo");
// Set same security token for env1 and env2.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
{
v8::Context::Scope scope(env2);
CompileRun("function fun() { }");
CHECK(env1->Global()
->Set(env1.local(), v8_str("fun"), CompileRun("fun"))
.FromJust());
}
env2->SetDetachedWindowReason(v8::Context::kDetachedWindowByNavigation);
CompileRun("fun()");
// This merely tests for not crashing, because currently
// Runtime_ReportDetachedWindowAccess does nothing.
}
static bool allowed_access = false;
static bool AccessBlocker(Local<v8::Context> accessing_context,
Local<v8::Object> accessed_object,