Revert "[debug] introduced runtime side effect check"
This reverts commit 7a2c371383
.
Reason for revert: msan is broken
Original change's description:
> [debug] introduced runtime side effect check
>
> This CL demonstrates minimum valuable addition to existing debug evaluate
> without side effects mechanism.
> With this CL user can evaluate expressions like:
> [a,b] // create any kind of temporary array literals
> [a,b].reduce((x,y) => x + y, 0); // use reduce method
> [1,2,3].fill(2); // change temporary arrays
>
> The core idea: any change of the object created during evaluation without
> side effects is side effect free. As soon as we try to store this temporary
> object to object existed before evaluation we will terminate execution.
>
> Implementation:
> - track all objects allocated during evaluation and mark them as temporary,
> - patch all bytecodes which change objects.
>
> A little more details (including performance analysis): [1].
>
> [1] https://docs.google.com/document/d/10qqAtZADspPnpYa6SEdYRxrddfKIZJIzbLtGpsZQkRo/edit#
>
> Bug: v8:7588
> Change-Id: I69f7b96e1ebd7ad0022219e8213211c7be72a111
> Reviewed-on: https://chromium-review.googlesource.com/972615
> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
> Reviewed-by: Yang Guo <yangguo@chromium.org>
> Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#52370}
TBR=ulan@chromium.org,rmcilroy@chromium.org,yangguo@chromium.org,kozyatinskiy@chromium.org,leszeks@chromium.org
Change-Id: Ied1739c6308b13a4981189e0999f5912316cf456
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:7588
Reviewed-on: https://chromium-review.googlesource.com/996135
Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52371}
This commit is contained in:
parent
7a2c371383
commit
539a24432b
1
BUILD.gn
1
BUILD.gn
@ -1322,6 +1322,7 @@ v8_source_set("v8_base") {
|
||||
"src/allocation.cc",
|
||||
"src/allocation.h",
|
||||
"src/api-arguments-inl.h",
|
||||
"src/api-arguments.cc",
|
||||
"src/api-arguments.h",
|
||||
"src/api-natives.cc",
|
||||
"src/api-natives.h",
|
||||
|
@ -22,14 +22,14 @@ namespace internal {
|
||||
DCHECK(!name->IsPrivate()); \
|
||||
DCHECK_IMPLIES(name->IsSymbol(), interceptor->can_intercept_symbols());
|
||||
|
||||
#define PREPARE_CALLBACK_INFO(ISOLATE, F, RETURN_VALUE, API_RETURN_TYPE, \
|
||||
CALLBACK_INFO) \
|
||||
if (ISOLATE->debug_execution_mode() == DebugInfo::kSideEffects && \
|
||||
!ISOLATE->debug()->PerformSideEffectCheckForCallback(CALLBACK_INFO)) { \
|
||||
return RETURN_VALUE(); \
|
||||
} \
|
||||
VMState<EXTERNAL> state(ISOLATE); \
|
||||
ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F)); \
|
||||
#define PREPARE_CALLBACK_INFO(ISOLATE, F, RETURN_VALUE, API_RETURN_TYPE, \
|
||||
CALLBACK_INFO) \
|
||||
if (ISOLATE->needs_side_effect_check() && \
|
||||
!PerformSideEffectCheck(ISOLATE, *CALLBACK_INFO)) { \
|
||||
return RETURN_VALUE(); \
|
||||
} \
|
||||
VMState<EXTERNAL> state(ISOLATE); \
|
||||
ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F)); \
|
||||
PropertyCallbackInfo<API_RETURN_TYPE> callback_info(begin());
|
||||
|
||||
#define CREATE_NAMED_CALLBACK(FUNCTION, TYPE, RETURN_TYPE, API_RETURN_TYPE, \
|
||||
@ -83,8 +83,8 @@ Handle<Object> FunctionCallbackArguments::Call(CallHandlerInfo* handler) {
|
||||
RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kFunctionCallback);
|
||||
v8::FunctionCallback f =
|
||||
v8::ToCData<v8::FunctionCallback>(handler->callback());
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects &&
|
||||
!isolate->debug()->PerformSideEffectCheckForCallback(handle(handler))) {
|
||||
if (isolate->needs_side_effect_check() &&
|
||||
!PerformSideEffectCheck(isolate, handler)) {
|
||||
return Handle<Object>();
|
||||
}
|
||||
VMState<EXTERNAL> state(isolate);
|
||||
@ -158,6 +158,7 @@ Handle<Object> PropertyCallbackArguments::CallNamedSetter(
|
||||
Isolate* isolate = this->isolate();
|
||||
RuntimeCallTimerScope timer(isolate,
|
||||
RuntimeCallCounterId::kNamedSetterCallback);
|
||||
DCHECK(!isolate->needs_side_effect_check());
|
||||
Handle<Object> side_effect_check_not_supported;
|
||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value,
|
||||
side_effect_check_not_supported);
|
||||
@ -176,6 +177,8 @@ Handle<Object> PropertyCallbackArguments::CallNamedDefiner(
|
||||
RuntimeCallCounterId::kNamedDefinerCallback);
|
||||
GenericNamedPropertyDefinerCallback f =
|
||||
ToCData<GenericNamedPropertyDefinerCallback>(interceptor->definer());
|
||||
// We should not have come this far when side effect checks are enabled.
|
||||
DCHECK(!isolate->needs_side_effect_check());
|
||||
Handle<Object> side_effect_check_not_supported;
|
||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value,
|
||||
side_effect_check_not_supported);
|
||||
@ -193,6 +196,8 @@ Handle<Object> PropertyCallbackArguments::CallIndexedSetter(
|
||||
RuntimeCallCounterId::kIndexedSetterCallback);
|
||||
IndexedPropertySetterCallback f =
|
||||
ToCData<IndexedPropertySetterCallback>(interceptor->setter());
|
||||
// We should not have come this far when side effect checks are enabled.
|
||||
DCHECK(!isolate->needs_side_effect_check());
|
||||
Handle<Object> side_effect_check_not_supported;
|
||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value,
|
||||
side_effect_check_not_supported);
|
||||
@ -211,6 +216,8 @@ Handle<Object> PropertyCallbackArguments::CallIndexedDefiner(
|
||||
RuntimeCallCounterId::kIndexedDefinerCallback);
|
||||
IndexedPropertyDefinerCallback f =
|
||||
ToCData<IndexedPropertyDefinerCallback>(interceptor->definer());
|
||||
// We should not have come this far when side effect checks are enabled.
|
||||
DCHECK(!isolate->needs_side_effect_check());
|
||||
Handle<Object> side_effect_check_not_supported;
|
||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value,
|
||||
side_effect_check_not_supported);
|
||||
@ -288,6 +295,8 @@ Handle<Object> PropertyCallbackArguments::CallAccessorSetter(
|
||||
RuntimeCallCounterId::kAccessorSetterCallback);
|
||||
AccessorNameSetterCallback f =
|
||||
ToCData<AccessorNameSetterCallback>(accessor_info->setter());
|
||||
// We should not have come this far when side effect checks are enabled.
|
||||
DCHECK(!isolate->needs_side_effect_check());
|
||||
Handle<Object> side_effect_check_not_supported;
|
||||
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, void,
|
||||
side_effect_check_not_supported);
|
||||
|
25
src/api-arguments.cc
Normal file
25
src/api-arguments.cc
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/api-arguments.h"
|
||||
#include "src/api-arguments-inl.h"
|
||||
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/tracing/trace-event.h"
|
||||
#include "src/vm-state-inl.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// static
|
||||
bool CustomArgumentsBase ::PerformSideEffectCheck(Isolate* isolate,
|
||||
Object* callback_info) {
|
||||
// TODO(7515): always pass a valid callback info object.
|
||||
if (callback_info == nullptr) return false;
|
||||
return isolate->debug()->PerformSideEffectCheckForCallback(callback_info);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -6,7 +6,6 @@
|
||||
#define V8_API_ARGUMENTS_H_
|
||||
|
||||
#include "src/api.h"
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/isolate.h"
|
||||
#include "src/visitors.h"
|
||||
|
||||
@ -20,6 +19,7 @@ class CustomArgumentsBase : public Relocatable {
|
||||
protected:
|
||||
explicit inline CustomArgumentsBase(Isolate* isolate)
|
||||
: Relocatable(isolate) {}
|
||||
static bool PerformSideEffectCheck(Isolate* isolate, Object* callback_info);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -1037,31 +1037,11 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ bind(&maybe_load_debug_bytecode_array);
|
||||
__ ldr(r9, FieldMemOperand(r4, DebugInfo::kDebugBytecodeArrayOffset), ne);
|
||||
__ JumpIfRoot(r9, Heap::kUndefinedValueRootIndex, &bytecode_array_loaded);
|
||||
|
||||
__ mov(kInterpreterBytecodeArrayRegister, r9);
|
||||
__ ldr(r9, FieldMemOperand(r4, DebugInfo::kFlagsOffset));
|
||||
__ SmiUntag(r9);
|
||||
__ And(r9, r9, Operand(DebugInfo::kDebugExecutionMode));
|
||||
|
||||
ExternalReference debug_execution_mode =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
__ mov(r4, Operand(debug_execution_mode));
|
||||
__ ldrsb(r4, MemOperand(r4));
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
__ cmp(r4, r9);
|
||||
__ b(eq, &bytecode_array_loaded);
|
||||
|
||||
__ push(closure);
|
||||
__ push(feedback_vector);
|
||||
__ push(kInterpreterBytecodeArrayRegister);
|
||||
__ push(closure);
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ pop(kInterpreterBytecodeArrayRegister);
|
||||
__ pop(feedback_vector);
|
||||
__ pop(closure);
|
||||
__ tst(r9, Operand(DebugInfo::kHasBreakInfo));
|
||||
__ ldr(kInterpreterBytecodeArrayRegister,
|
||||
FieldMemOperand(r4, DebugInfo::kDebugBytecodeArrayOffset), ne);
|
||||
__ b(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
|
@ -1131,27 +1131,12 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ Bind(&maybe_load_debug_bytecode_array);
|
||||
__ Ldrsw(x10, FieldMemOperand(x11, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ JumpIfRoot(x10, Heap::kUndefinedValueRootIndex, &bytecode_array_loaded);
|
||||
|
||||
__ Ldrsw(x10, UntagSmiFieldMemOperand(x11, DebugInfo::kFlagsOffset));
|
||||
__ TestAndBranchIfAllClear(x10, DebugInfo::kHasBreakInfo,
|
||||
&bytecode_array_loaded);
|
||||
__ Ldr(kInterpreterBytecodeArrayRegister,
|
||||
FieldMemOperand(x11, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ Ldrsw(x10, UntagSmiFieldMemOperand(x11, DebugInfo::kFlagsOffset));
|
||||
__ And(x10, x10, Immediate(DebugInfo::kDebugExecutionMode));
|
||||
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
ExternalReference debug_execution_mode =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
__ Mov(x0, Operand(debug_execution_mode));
|
||||
__ Ldrsb(x0, MemOperand(x0));
|
||||
__ CompareAndBranch(x10, x0, eq, &bytecode_array_loaded);
|
||||
|
||||
__ Push(closure, feedback_vector);
|
||||
__ PushArgument(closure);
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ Pop(feedback_vector, closure);
|
||||
__ jmp(&bytecode_array_loaded);
|
||||
__ B(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
|
||||
|
@ -853,8 +853,7 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
|
||||
// Get the bytecode array from the function object (or from the DebugInfo if
|
||||
// it is present) and load it into kInterpreterBytecodeArrayRegister.
|
||||
Label maybe_load_debug_bytecode_array, bytecode_array_loaded,
|
||||
apply_instrumentation;
|
||||
Label maybe_load_debug_bytecode_array, bytecode_array_loaded;
|
||||
__ mov(eax, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
|
||||
__ mov(kInterpreterBytecodeArrayRegister,
|
||||
FieldOperand(eax, SharedFunctionInfo::kFunctionDataOffset));
|
||||
@ -974,29 +973,15 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ bind(&maybe_load_debug_bytecode_array);
|
||||
__ mov(eax, FieldOperand(eax, SharedFunctionInfo::kDebugInfoOffset));
|
||||
__ mov(ecx, FieldOperand(eax, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ JumpIfRoot(ecx, Heap::kUndefinedValueRootIndex, &bytecode_array_loaded);
|
||||
|
||||
__ mov(kInterpreterBytecodeArrayRegister, ecx);
|
||||
__ mov(ecx, FieldOperand(eax, DebugInfo::kFlagsOffset));
|
||||
__ SmiUntag(ecx);
|
||||
__ and_(ecx, Immediate(DebugInfo::kDebugExecutionMode));
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
ExternalReference debug_execution_mode =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
__ cmp(ecx, Operand::StaticVariable(debug_execution_mode));
|
||||
__ j(equal, &bytecode_array_loaded);
|
||||
|
||||
__ pop(ecx); // get JSFunction from stack
|
||||
__ push(ecx);
|
||||
__ push(ebx); // preserve feedback_vector and bytecode array register
|
||||
__ push(kInterpreterBytecodeArrayRegister);
|
||||
__ push(ecx); // pass function as argument
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ pop(kInterpreterBytecodeArrayRegister);
|
||||
__ push(ebx); // feedback_vector == ebx, so save it.
|
||||
__ mov(ecx, FieldOperand(eax, SharedFunctionInfo::kDebugInfoOffset));
|
||||
__ mov(ebx, FieldOperand(ecx, DebugInfo::kFlagsOffset));
|
||||
__ SmiUntag(ebx);
|
||||
__ test(ebx, Immediate(DebugInfo::kHasBreakInfo));
|
||||
__ pop(ebx);
|
||||
__ j(zero, &bytecode_array_loaded);
|
||||
__ mov(kInterpreterBytecodeArrayRegister,
|
||||
FieldOperand(ecx, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ jmp(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
|
@ -1024,30 +1024,12 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ bind(&maybe_load_debug_bytecode_array);
|
||||
__ lw(t1, FieldMemOperand(t0, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ JumpIfRoot(t1, Heap::kUndefinedValueRootIndex, &bytecode_array_loaded);
|
||||
|
||||
__ mov(kInterpreterBytecodeArrayRegister, t1);
|
||||
__ lw(t1, FieldMemOperand(t0, DebugInfo::kFlagsOffset));
|
||||
__ SmiUntag(t1);
|
||||
__ And(t1, t1, Operand(DebugInfo::kDebugExecutionMode));
|
||||
|
||||
ExternalReference debug_execution_mode =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
__ li(t0, Operand(debug_execution_mode));
|
||||
__ lb(t0, MemOperand(t0));
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
__ Branch(&bytecode_array_loaded, eq, t0, Operand(t1));
|
||||
|
||||
__ push(closure);
|
||||
__ push(feedback_vector);
|
||||
__ push(kInterpreterBytecodeArrayRegister);
|
||||
__ push(closure);
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ pop(kInterpreterBytecodeArrayRegister);
|
||||
__ pop(feedback_vector);
|
||||
__ pop(closure);
|
||||
__ And(t1, t1, Operand(DebugInfo::kHasBreakInfo));
|
||||
__ Branch(&bytecode_array_loaded, eq, t1, Operand(zero_reg));
|
||||
__ lw(kInterpreterBytecodeArrayRegister,
|
||||
FieldMemOperand(t0, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ Branch(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
|
@ -1022,30 +1022,12 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ bind(&maybe_load_debug_bytecode_array);
|
||||
__ Ld(a5, FieldMemOperand(a4, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ JumpIfRoot(a5, Heap::kUndefinedValueRootIndex, &bytecode_array_loaded);
|
||||
|
||||
__ mov(kInterpreterBytecodeArrayRegister, a5);
|
||||
__ Ld(a5, FieldMemOperand(a4, DebugInfo::kFlagsOffset));
|
||||
__ SmiUntag(a5);
|
||||
__ And(a5, a5, Operand(DebugInfo::kDebugExecutionMode));
|
||||
|
||||
ExternalReference debug_execution_mode =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
__ li(a4, Operand(debug_execution_mode));
|
||||
__ Lb(a4, MemOperand(a4));
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
__ Branch(&bytecode_array_loaded, eq, a4, Operand(a5));
|
||||
|
||||
__ push(closure);
|
||||
__ push(feedback_vector);
|
||||
__ push(kInterpreterBytecodeArrayRegister);
|
||||
__ push(closure);
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ pop(kInterpreterBytecodeArrayRegister);
|
||||
__ pop(feedback_vector);
|
||||
__ pop(closure);
|
||||
__ And(a5, a5, Operand(DebugInfo::kHasBreakInfo));
|
||||
__ Branch(&bytecode_array_loaded, eq, a5, Operand(zero_reg));
|
||||
__ Ld(kInterpreterBytecodeArrayRegister,
|
||||
FieldMemOperand(a4, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ Branch(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
|
@ -1041,33 +1041,13 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// kInterpreterBytecodeArrayRegister is already loaded with
|
||||
// SharedFunctionInfo::kFunctionDataOffset.
|
||||
__ bind(&maybe_load_debug_bytecode_array);
|
||||
__ movp(rax, FieldOperand(closure, JSFunction::kSharedFunctionInfoOffset));
|
||||
__ movp(rcx, FieldOperand(rax, SharedFunctionInfo::kDebugInfoOffset));
|
||||
__ movp(kScratchRegister,
|
||||
__ SmiToInteger32(kScratchRegister,
|
||||
FieldOperand(rcx, DebugInfo::kFlagsOffset));
|
||||
__ testl(kScratchRegister, Immediate(DebugInfo::kHasBreakInfo));
|
||||
__ j(zero, &bytecode_array_loaded);
|
||||
__ movp(kInterpreterBytecodeArrayRegister,
|
||||
FieldOperand(rcx, DebugInfo::kDebugBytecodeArrayOffset));
|
||||
__ JumpIfRoot(kScratchRegister, Heap::kUndefinedValueRootIndex,
|
||||
&bytecode_array_loaded);
|
||||
|
||||
__ movp(kInterpreterBytecodeArrayRegister, kScratchRegister);
|
||||
__ SmiToInteger32(rax, FieldOperand(rcx, DebugInfo::kFlagsOffset));
|
||||
__ andb(rax, Immediate(DebugInfo::kDebugExecutionMode));
|
||||
STATIC_ASSERT(static_cast<int>(DebugInfo::kDebugExecutionMode) ==
|
||||
static_cast<int>(DebugInfo::kSideEffects));
|
||||
ExternalReference debug_execution_mode_address =
|
||||
ExternalReference::debug_execution_mode_address(masm->isolate());
|
||||
Operand debug_execution_mode =
|
||||
masm->ExternalOperand(debug_execution_mode_address);
|
||||
__ cmpb(rax, debug_execution_mode);
|
||||
__ j(equal, &bytecode_array_loaded);
|
||||
|
||||
__ Push(closure);
|
||||
__ Push(feedback_vector);
|
||||
__ Push(kInterpreterBytecodeArrayRegister);
|
||||
__ Push(closure);
|
||||
__ CallRuntime(Runtime::kDebugApplyInstrumentation);
|
||||
__ Pop(kInterpreterBytecodeArrayRegister);
|
||||
__ Pop(feedback_vector);
|
||||
__ Pop(closure);
|
||||
__ jmp(&bytecode_array_loaded);
|
||||
}
|
||||
|
||||
|
@ -41,11 +41,9 @@ MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
|
||||
Handle<JSFunction> fun =
|
||||
isolate->factory()->NewFunctionFromSharedFunctionInfo(shared_info,
|
||||
context);
|
||||
if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
|
||||
MaybeHandle<Object> result = Execution::Call(
|
||||
isolate, fun, Handle<JSObject>(context->global_proxy()), 0, nullptr);
|
||||
if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
|
||||
return result;
|
||||
NoSideEffectScope no_side_effect(isolate, throw_on_side_effect);
|
||||
return Execution::Call(isolate, fun,
|
||||
Handle<JSObject>(context->global_proxy()), 0, nullptr);
|
||||
}
|
||||
|
||||
MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
|
||||
@ -137,14 +135,11 @@ MaybeHandle<Object> DebugEvaluate::Evaluate(
|
||||
Object);
|
||||
|
||||
Handle<Object> result;
|
||||
bool sucess = false;
|
||||
if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
|
||||
sucess = Execution::Call(isolate, eval_fun, receiver, 0, nullptr)
|
||||
.ToHandle(&result);
|
||||
if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
|
||||
if (!sucess) {
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
return MaybeHandle<Object>();
|
||||
{
|
||||
NoSideEffectScope no_side_effect(isolate, throw_on_side_effect);
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, result,
|
||||
Execution::Call(isolate, eval_fun, receiver, 0, nullptr), Object);
|
||||
}
|
||||
|
||||
// Skip the global proxy as it has no properties and always delegates to the
|
||||
@ -576,6 +571,10 @@ bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
|
||||
case Bytecode::kSetPendingMessage:
|
||||
return true;
|
||||
default:
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
|
||||
Bytecodes::ToString(bytecode));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -850,25 +849,10 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BytecodeRequiresRuntimeCheck(interpreter::Bytecode bytecode) {
|
||||
typedef interpreter::Bytecode Bytecode;
|
||||
switch (bytecode) {
|
||||
case Bytecode::kStaNamedProperty:
|
||||
case Bytecode::kStaNamedOwnProperty:
|
||||
case Bytecode::kStaKeyedProperty:
|
||||
case Bytecode::kStaInArrayLiteral:
|
||||
case Bytecode::kStaDataPropertyInLiteral:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
Handle<SharedFunctionInfo> info) {
|
||||
bool DebugEvaluate::FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info) {
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] Checking function %s for side effect.\n",
|
||||
info->DebugName()->ToCString().get());
|
||||
@ -879,7 +863,6 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
// Check bytecodes against whitelist.
|
||||
Handle<BytecodeArray> bytecode_array(info->bytecode_array());
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) bytecode_array->Print();
|
||||
bool requires_runtime_checks = false;
|
||||
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
|
||||
it.Advance()) {
|
||||
interpreter::Bytecode bytecode = it.current_bytecode();
|
||||
@ -890,29 +873,18 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
? it.GetIntrinsicIdOperand(0)
|
||||
: it.GetRuntimeIdOperand(0);
|
||||
if (IntrinsicHasNoSideEffect(id)) continue;
|
||||
return kHasSideEffects;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BytecodeHasNoSideEffect(bytecode)) continue;
|
||||
if (BytecodeRequiresRuntimeCheck(bytecode)) {
|
||||
requires_runtime_checks = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
|
||||
interpreter::Bytecodes::ToString(bytecode));
|
||||
}
|
||||
|
||||
// Did not match whitelist.
|
||||
return kHasSideEffects;
|
||||
return false;
|
||||
}
|
||||
return requires_runtime_checks ? kRequiresRuntimeChecks : kHasNoSideEffect;
|
||||
return true;
|
||||
} else if (info->IsApiFunction()) {
|
||||
if (info->GetCode()->is_builtin()) {
|
||||
return info->GetCode()->builtin_index() == Builtins::kHandleApiCall
|
||||
? kHasNoSideEffect
|
||||
: kHasSideEffects;
|
||||
return info->GetCode()->builtin_index() == Builtins::kHandleApiCall;
|
||||
}
|
||||
} else {
|
||||
// Check built-ins against whitelist.
|
||||
@ -951,11 +923,11 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
DCHECK(!failed);
|
||||
}
|
||||
#endif // DEBUG
|
||||
return kHasNoSideEffect;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return kHasSideEffects;
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
@ -986,20 +958,5 @@ bool DebugEvaluate::CallbackHasNoSideEffect(Object* callback_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
void DebugEvaluate::ApplySideEffectChecks(
|
||||
Handle<BytecodeArray> bytecode_array) {
|
||||
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
|
||||
it.Advance()) {
|
||||
interpreter::Bytecode bytecode = it.current_bytecode();
|
||||
if (BytecodeRequiresRuntimeCheck(bytecode)) {
|
||||
interpreter::Bytecode debugbreak =
|
||||
interpreter::Bytecodes::GetDebugBreak(bytecode);
|
||||
bytecode_array->set(it.current_offset(),
|
||||
interpreter::Bytecodes::ToByte(debugbreak));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -37,15 +37,8 @@ class DebugEvaluate : public AllStatic {
|
||||
static MaybeHandle<Object> WithTopmostArguments(Isolate* isolate,
|
||||
Handle<String> source);
|
||||
|
||||
enum SideEffectState {
|
||||
kHasSideEffects,
|
||||
kRequiresRuntimeChecks,
|
||||
kHasNoSideEffect
|
||||
};
|
||||
static SideEffectState FunctionGetSideEffectState(
|
||||
Handle<SharedFunctionInfo> info);
|
||||
static bool FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info);
|
||||
static bool CallbackHasNoSideEffect(Object* callback_info);
|
||||
static void ApplySideEffectChecks(Handle<BytecodeArray> bytecode_array);
|
||||
|
||||
private:
|
||||
// This class builds a context chain for evaluation of expressions
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "src/debug/debug.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/api.h"
|
||||
#include "src/arguments.h"
|
||||
@ -23,7 +22,6 @@
|
||||
#include "src/global-handles.h"
|
||||
#include "src/globals.h"
|
||||
#include "src/interpreter/bytecode-array-accessor.h"
|
||||
#include "src/interpreter/bytecode-array-iterator.h"
|
||||
#include "src/interpreter/interpreter.h"
|
||||
#include "src/isolate-inl.h"
|
||||
#include "src/log.h"
|
||||
@ -36,36 +34,6 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class Debug::TemporaryObjectsTracker : public HeapObjectAllocationTracker {
|
||||
public:
|
||||
TemporaryObjectsTracker() = default;
|
||||
~TemporaryObjectsTracker() = default;
|
||||
|
||||
void AllocationEvent(Address addr, int) override { objects_.insert(addr); }
|
||||
|
||||
void MoveEvent(Address from, Address to, int) override {
|
||||
if (from == to) return;
|
||||
auto it = objects_.find(from);
|
||||
if (it == objects_.end()) {
|
||||
// If temporary object was collected we can get MoveEvent which moves
|
||||
// existing non temporary object to the address where we had temporary
|
||||
// object. So we should mark new address as non temporary.
|
||||
objects_.erase(to);
|
||||
return;
|
||||
}
|
||||
objects_.erase(it);
|
||||
objects_.insert(to);
|
||||
}
|
||||
|
||||
bool HasObject(Address addr) const {
|
||||
return objects_.find(addr) != objects_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_set<Address> objects_;
|
||||
DISALLOW_COPY_AND_ASSIGN(TemporaryObjectsTracker);
|
||||
};
|
||||
|
||||
Debug::Debug(Isolate* isolate)
|
||||
: debug_context_(Handle<Context>()),
|
||||
is_active_(false),
|
||||
@ -83,8 +51,6 @@ Debug::Debug(Isolate* isolate)
|
||||
ThreadInit();
|
||||
}
|
||||
|
||||
Debug::~Debug() { DCHECK_NULL(debug_delegate_); }
|
||||
|
||||
BreakLocation BreakLocation::FromFrame(Handle<DebugInfo> debug_info,
|
||||
JavaScriptFrame* frame) {
|
||||
if (debug_info->CanBreakAtEntry()) {
|
||||
@ -452,8 +418,6 @@ void Debug::Break(JavaScriptFrame* frame, Handle<JSFunction> break_target) {
|
||||
// Return if we fail to retrieve debug info.
|
||||
Handle<SharedFunctionInfo> shared(break_target->shared());
|
||||
if (!EnsureBreakInfo(shared)) return;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
|
||||
|
||||
// Find the break location where execution has stopped.
|
||||
@ -644,8 +608,7 @@ bool Debug::SetBreakPoint(Handle<JSFunction> function,
|
||||
// Make sure the function is compiled and has set up the debug info.
|
||||
Handle<SharedFunctionInfo> shared(function->shared());
|
||||
if (!EnsureBreakInfo(shared)) return false;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
PrepareFunctionForBreakPoints(shared);
|
||||
Handle<DebugInfo> debug_info(shared->GetDebugInfo());
|
||||
// Source positions starts with zero.
|
||||
DCHECK_LE(0, *source_position);
|
||||
@ -686,7 +649,7 @@ bool Debug::SetBreakPointForScript(Handle<Script> script,
|
||||
// Make sure the function has set up the debug info.
|
||||
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result);
|
||||
if (!EnsureBreakInfo(shared)) return false;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
PrepareFunctionForBreakPoints(shared);
|
||||
|
||||
// Find position within function. The script position might be before the
|
||||
// source position of the first function.
|
||||
@ -726,19 +689,6 @@ int Debug::FindBreakablePosition(Handle<DebugInfo> debug_info,
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::ApplyInstrumentation(Handle<SharedFunctionInfo> shared) {
|
||||
DCHECK(shared->HasBytecodeArray());
|
||||
Handle<DebugInfo> debug_info(GetOrCreateDebugInfo(shared));
|
||||
DCHECK_NE(debug_info->DebugExecutionMode(), isolate_->debug_execution_mode());
|
||||
if (isolate_->debug_execution_mode() == DebugInfo::kBreakpoints) {
|
||||
ClearSideEffectChecks(debug_info);
|
||||
ApplyBreakPoints(debug_info);
|
||||
} else {
|
||||
ClearBreakPoints(debug_info);
|
||||
ApplySideEffectChecks(debug_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::ApplyBreakPoints(Handle<DebugInfo> debug_info) {
|
||||
DisallowHeapAllocation no_gc;
|
||||
if (debug_info->CanBreakAtEntry()) {
|
||||
@ -756,7 +706,6 @@ void Debug::ApplyBreakPoints(Handle<DebugInfo> debug_info) {
|
||||
it.SetDebugBreak();
|
||||
}
|
||||
}
|
||||
debug_info->SetDebugExecutionMode(DebugInfo::kBreakpoints);
|
||||
}
|
||||
|
||||
void Debug::ClearBreakPoints(Handle<DebugInfo> debug_info) {
|
||||
@ -765,9 +714,7 @@ void Debug::ClearBreakPoints(Handle<DebugInfo> debug_info) {
|
||||
} else {
|
||||
// If we attempt to clear breakpoints but none exist, simply return. This
|
||||
// can happen e.g. CoverageInfos exist but no breakpoints are set.
|
||||
if (!debug_info->HasDebugBytecodeArray() || !debug_info->HasBreakInfo()) {
|
||||
return;
|
||||
}
|
||||
if (!debug_info->HasDebugBytecodeArray()) return;
|
||||
|
||||
DisallowHeapAllocation no_gc;
|
||||
for (BreakIterator it(debug_info); !it.Done(); it.Next()) {
|
||||
@ -781,7 +728,6 @@ void Debug::ClearBreakPoint(Handle<BreakPoint> break_point) {
|
||||
|
||||
for (DebugInfoListNode* node = debug_info_list_; node != nullptr;
|
||||
node = node->next()) {
|
||||
if (!node->debug_info()->HasBreakInfo()) continue;
|
||||
Handle<Object> result =
|
||||
DebugInfo::FindBreakPointInfo(node->debug_info(), break_point);
|
||||
if (result->IsUndefined(isolate_)) continue;
|
||||
@ -826,8 +772,7 @@ void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared,
|
||||
if (IsBlackboxed(shared)) return;
|
||||
// Make sure the function is compiled and has set up the debug info.
|
||||
if (!EnsureBreakInfo(shared)) return;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
PrepareFunctionForBreakPoints(shared);
|
||||
Handle<DebugInfo> debug_info(shared->GetDebugInfo());
|
||||
// Flood the function with break points.
|
||||
DCHECK(debug_info->HasDebugBytecodeArray());
|
||||
@ -1021,8 +966,6 @@ void Debug::PrepareStep(StepAction step_action) {
|
||||
Handle<JSFunction> function(summary.function());
|
||||
Handle<SharedFunctionInfo> shared(function->shared());
|
||||
if (!EnsureBreakInfo(shared)) return;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
Handle<DebugInfo> debug_info(shared->GetDebugInfo());
|
||||
|
||||
BreakLocation location = BreakLocation::FromFrame(debug_info, js_frame);
|
||||
@ -1215,25 +1158,15 @@ void Debug::DeoptimizeFunction(Handle<SharedFunctionInfo> shared) {
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::PrepareFunctionForDebugExecution(
|
||||
Handle<SharedFunctionInfo> shared) {
|
||||
void Debug::PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared) {
|
||||
// To prepare bytecode for debugging, we already need to have the debug
|
||||
// info (containing the debug copy) upfront, but since we do not recompile,
|
||||
// preparing for break points cannot fail.
|
||||
DCHECK(shared->is_compiled());
|
||||
DCHECK(shared->HasDebugInfo());
|
||||
DCHECK(shared->HasBreakInfo());
|
||||
Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared);
|
||||
if (debug_info->flags() & DebugInfo::kPreparedForDebugExecution) return;
|
||||
|
||||
// Make a copy of the bytecode array if available.
|
||||
Handle<Object> maybe_debug_bytecode_array =
|
||||
isolate_->factory()->undefined_value();
|
||||
if (shared->HasBytecodeArray()) {
|
||||
Handle<BytecodeArray> original(shared->bytecode_array());
|
||||
maybe_debug_bytecode_array =
|
||||
isolate_->factory()->CopyBytecodeArray(original);
|
||||
}
|
||||
debug_info->set_debug_bytecode_array(*maybe_debug_bytecode_array);
|
||||
if (debug_info->IsPreparedForBreakpoints()) return;
|
||||
|
||||
if (debug_info->CanBreakAtEntry()) {
|
||||
// Deopt everything in case the function is inlined anywhere.
|
||||
@ -1246,8 +1179,9 @@ void Debug::PrepareFunctionForDebugExecution(
|
||||
redirect_visitor.VisitThread(isolate_, isolate_->thread_local_top());
|
||||
isolate_->thread_manager()->IterateArchivedThreads(&redirect_visitor);
|
||||
}
|
||||
|
||||
debug_info->set_flags(debug_info->flags() |
|
||||
DebugInfo::kPreparedForDebugExecution);
|
||||
DebugInfo::kPreparedForBreakpoints);
|
||||
}
|
||||
|
||||
void Debug::InstallDebugBreakTrampoline() {
|
||||
@ -1334,7 +1268,6 @@ bool Debug::GetPossibleBreakpoints(Handle<Script> script, int start_position,
|
||||
Handle<SharedFunctionInfo> shared =
|
||||
Handle<SharedFunctionInfo>::cast(result);
|
||||
if (!EnsureBreakInfo(shared)) return false;
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
Handle<DebugInfo> debug_info(shared->GetDebugInfo());
|
||||
FindBreakablePositions(debug_info, start_position, end_position, locations);
|
||||
@ -1368,7 +1301,6 @@ bool Debug::GetPossibleBreakpoints(Handle<Script> script, int start_position,
|
||||
}
|
||||
}
|
||||
if (!EnsureBreakInfo(candidate)) return false;
|
||||
PrepareFunctionForDebugExecution(candidate);
|
||||
}
|
||||
if (was_compiled) continue;
|
||||
|
||||
@ -1464,7 +1396,7 @@ Handle<Object> Debug::FindSharedFunctionInfoInScript(Handle<Script> script,
|
||||
// If the iteration count is larger than 1, we had to compile the outer
|
||||
// function in order to create this shared function info. So there can
|
||||
// be no JSFunction referencing it. We can anticipate creating a debug
|
||||
// info while bypassing PrepareFunctionForDebugExecution.
|
||||
// info while bypassing PrepareFunctionForBreakpoints.
|
||||
if (iteration > 1) {
|
||||
AllowHeapAllocation allow_before_return;
|
||||
CreateBreakInfo(shared_handle);
|
||||
@ -1513,10 +1445,18 @@ void Debug::CreateBreakInfo(Handle<SharedFunctionInfo> shared) {
|
||||
Handle<FixedArray> break_points(
|
||||
factory->NewFixedArray(DebugInfo::kEstimatedNofBreakPointsInFunction));
|
||||
|
||||
// Make a copy of the bytecode array if available.
|
||||
Handle<Object> maybe_debug_bytecode_array = factory->undefined_value();
|
||||
if (shared->HasBytecodeArray()) {
|
||||
Handle<BytecodeArray> original(shared->bytecode_array());
|
||||
maybe_debug_bytecode_array = factory->CopyBytecodeArray(original);
|
||||
}
|
||||
|
||||
int flags = debug_info->flags();
|
||||
flags |= DebugInfo::kHasBreakInfo;
|
||||
if (CanBreakAtEntry(shared)) flags |= DebugInfo::kCanBreakAtEntry;
|
||||
debug_info->set_flags(flags);
|
||||
debug_info->set_debug_bytecode_array(*maybe_debug_bytecode_array);
|
||||
debug_info->set_break_points(*break_points);
|
||||
}
|
||||
|
||||
@ -2144,9 +2084,8 @@ void Debug::UpdateState() {
|
||||
|
||||
void Debug::UpdateHookOnFunctionCall() {
|
||||
STATIC_ASSERT(LastStepAction == StepIn);
|
||||
hook_on_function_call_ =
|
||||
thread_local_.last_step_action_ == StepIn ||
|
||||
isolate_->debug_execution_mode() == DebugInfo::kSideEffects;
|
||||
hook_on_function_call_ = thread_local_.last_step_action_ == StepIn ||
|
||||
isolate_->needs_side_effect_check();
|
||||
}
|
||||
|
||||
MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) {
|
||||
@ -2315,57 +2254,8 @@ ReturnValueScope::~ReturnValueScope() {
|
||||
debug_->set_return_value(*return_value_);
|
||||
}
|
||||
|
||||
void Debug::StartSideEffectCheckMode() {
|
||||
DCHECK(isolate_->debug_execution_mode() != DebugInfo::kSideEffects);
|
||||
isolate_->set_debug_execution_mode(DebugInfo::kSideEffects);
|
||||
UpdateHookOnFunctionCall();
|
||||
side_effect_check_failed_ = false;
|
||||
|
||||
DCHECK(!temporary_objects_);
|
||||
temporary_objects_.reset(new TemporaryObjectsTracker());
|
||||
isolate_->heap()->AddHeapObjectAllocationTracker(temporary_objects_.get());
|
||||
}
|
||||
|
||||
void Debug::StopSideEffectCheckMode() {
|
||||
DCHECK(isolate_->debug_execution_mode() == DebugInfo::kSideEffects);
|
||||
if (side_effect_check_failed_) {
|
||||
DCHECK(isolate_->has_pending_exception());
|
||||
DCHECK_EQ(isolate_->heap()->termination_exception(),
|
||||
isolate_->pending_exception());
|
||||
// Convert the termination exception into a regular exception.
|
||||
isolate_->CancelTerminateExecution();
|
||||
isolate_->Throw(*isolate_->factory()->NewEvalError(
|
||||
MessageTemplate::kNoSideEffectDebugEvaluate));
|
||||
}
|
||||
isolate_->set_debug_execution_mode(DebugInfo::kBreakpoints);
|
||||
UpdateHookOnFunctionCall();
|
||||
side_effect_check_failed_ = false;
|
||||
|
||||
DCHECK(temporary_objects_);
|
||||
isolate_->heap()->RemoveHeapObjectAllocationTracker(temporary_objects_.get());
|
||||
temporary_objects_.reset();
|
||||
}
|
||||
|
||||
void Debug::ApplySideEffectChecks(Handle<DebugInfo> debug_info) {
|
||||
DCHECK(debug_info->HasDebugBytecodeArray());
|
||||
Handle<BytecodeArray> debug_bytecode(debug_info->DebugBytecodeArray());
|
||||
DebugEvaluate::ApplySideEffectChecks(debug_bytecode);
|
||||
debug_info->SetDebugExecutionMode(DebugInfo::kSideEffects);
|
||||
}
|
||||
|
||||
void Debug::ClearSideEffectChecks(Handle<DebugInfo> debug_info) {
|
||||
DCHECK(debug_info->HasDebugBytecodeArray());
|
||||
Handle<BytecodeArray> debug_bytecode(debug_info->DebugBytecodeArray());
|
||||
Handle<BytecodeArray> original(debug_info->OriginalBytecodeArray());
|
||||
for (interpreter::BytecodeArrayIterator it(debug_bytecode); !it.done();
|
||||
it.Advance()) {
|
||||
debug_bytecode->set(it.current_offset(),
|
||||
original->get(it.current_offset()));
|
||||
}
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) {
|
||||
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
|
||||
DCHECK(isolate_->needs_side_effect_check());
|
||||
DisallowJavascriptExecution no_js(isolate_);
|
||||
if (!function->is_compiled() &&
|
||||
!Compiler::Compile(function, Compiler::KEEP_EXCEPTION)) {
|
||||
@ -2381,28 +2271,12 @@ bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) {
|
||||
isolate_->TerminateExecution();
|
||||
return false;
|
||||
}
|
||||
// If function has bytecode array then prepare function for debug execution
|
||||
// to perform runtime side effect checks.
|
||||
if (function->shared()->requires_runtime_side_effect_checks()) {
|
||||
Handle<SharedFunctionInfo> shared(function->shared());
|
||||
DCHECK(shared->is_compiled());
|
||||
if (shared->GetCode() ==
|
||||
isolate_->builtins()->builtin(Builtins::kDeserializeLazy)) {
|
||||
Snapshot::EnsureBuiltinIsDeserialized(isolate_, shared);
|
||||
}
|
||||
GetOrCreateDebugInfo(shared);
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheckForCallback(Handle<Object> callback_info) {
|
||||
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
|
||||
// TODO(7515): always pass a valid callback info object.
|
||||
if (!callback_info.is_null() &&
|
||||
DebugEvaluate::CallbackHasNoSideEffect(*callback_info)) {
|
||||
return true;
|
||||
}
|
||||
bool Debug::PerformSideEffectCheckForCallback(Object* callback_info) {
|
||||
DCHECK(isolate_->needs_side_effect_check());
|
||||
if (DebugEvaluate::CallbackHasNoSideEffect(callback_info)) return true;
|
||||
side_effect_check_failed_ = true;
|
||||
// Throw an uncatchable termination exception.
|
||||
isolate_->TerminateExecution();
|
||||
@ -2410,32 +2284,6 @@ bool Debug::PerformSideEffectCheckForCallback(Handle<Object> callback_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheckForObject(Handle<Object> object) {
|
||||
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
|
||||
if (object->IsHeapObject()) {
|
||||
Address address = Handle<HeapObject>::cast(object)->address();
|
||||
if (temporary_objects_->HasObject(address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
JavaScriptFrameIterator it(isolate_);
|
||||
InterpretedFrame* interpreted_frame =
|
||||
reinterpret_cast<InterpretedFrame*>(it.frame());
|
||||
SharedFunctionInfo* shared = interpreted_frame->function()->shared();
|
||||
BytecodeArray* bytecode_array = shared->bytecode_array();
|
||||
int bytecode_offset = interpreted_frame->GetBytecodeOffset();
|
||||
interpreter::Bytecode bytecode =
|
||||
interpreter::Bytecodes::FromByte(bytecode_array->get(bytecode_offset));
|
||||
PrintF("[debug-evaluate] %s failed runtime side effect check.\n",
|
||||
interpreter::Bytecodes::ToString(bytecode));
|
||||
}
|
||||
side_effect_check_failed_ = true;
|
||||
// Throw an uncatchable termination exception.
|
||||
isolate_->TerminateExecution();
|
||||
return false;
|
||||
}
|
||||
|
||||
void LegacyDebugDelegate::PromiseEventOccurred(
|
||||
v8::debug::PromiseDebugActionType type, int id, bool is_blackboxed) {
|
||||
DebugScope debug_scope(isolate_->debug());
|
||||
@ -2546,5 +2394,21 @@ void NativeDebugDelegate::ProcessDebugEvent(v8::DebugEvent event,
|
||||
CHECK(!isolate->has_scheduled_exception());
|
||||
}
|
||||
|
||||
NoSideEffectScope::~NoSideEffectScope() {
|
||||
if (isolate_->needs_side_effect_check() &&
|
||||
isolate_->debug()->side_effect_check_failed_) {
|
||||
DCHECK(isolate_->has_pending_exception());
|
||||
DCHECK_EQ(isolate_->heap()->termination_exception(),
|
||||
isolate_->pending_exception());
|
||||
// Convert the termination exception into a regular exception.
|
||||
isolate_->CancelTerminateExecution();
|
||||
isolate_->Throw(*isolate_->factory()->NewEvalError(
|
||||
MessageTemplate::kNoSideEffectDebugEvaluate));
|
||||
}
|
||||
isolate_->set_needs_side_effect_check(false);
|
||||
isolate_->debug()->UpdateHookOnFunctionCall();
|
||||
isolate_->debug()->side_effect_check_failed_ = false;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -267,7 +267,7 @@ class Debug {
|
||||
void ClearStepOut();
|
||||
|
||||
void DeoptimizeFunction(Handle<SharedFunctionInfo> shared);
|
||||
void PrepareFunctionForDebugExecution(Handle<SharedFunctionInfo> shared);
|
||||
void PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared);
|
||||
void InstallDebugBreakTrampoline();
|
||||
bool GetPossibleBreakpoints(Handle<Script> script, int start_position,
|
||||
int end_position, bool restrict_to_function,
|
||||
@ -336,18 +336,8 @@ class Debug {
|
||||
return is_active() && !debug_context().is_null() && break_id() != 0;
|
||||
}
|
||||
|
||||
// Apply proper instrumentation depends on debug_execution_mode.
|
||||
void ApplyInstrumentation(Handle<SharedFunctionInfo> shared);
|
||||
|
||||
void StartSideEffectCheckMode();
|
||||
void StopSideEffectCheckMode();
|
||||
|
||||
void ApplySideEffectChecks(Handle<DebugInfo> debug_info);
|
||||
void ClearSideEffectChecks(Handle<DebugInfo> debug_info);
|
||||
|
||||
bool PerformSideEffectCheck(Handle<JSFunction> function);
|
||||
bool PerformSideEffectCheckForCallback(Handle<Object> callback_info);
|
||||
bool PerformSideEffectCheckForObject(Handle<Object> object);
|
||||
bool PerformSideEffectCheckForCallback(Object* callback_info);
|
||||
|
||||
// Flags and states.
|
||||
DebugScope* debugger_entry() {
|
||||
@ -413,7 +403,7 @@ class Debug {
|
||||
|
||||
private:
|
||||
explicit Debug(Isolate* isolate);
|
||||
~Debug();
|
||||
~Debug() { DCHECK_NULL(debug_delegate_); }
|
||||
|
||||
void UpdateState();
|
||||
void UpdateHookOnFunctionCall();
|
||||
@ -427,8 +417,7 @@ class Debug {
|
||||
int CurrentFrameCount();
|
||||
|
||||
inline bool ignore_events() const {
|
||||
return is_suppressed_ || !is_active_ ||
|
||||
isolate_->debug_execution_mode() == DebugInfo::kSideEffects;
|
||||
return is_suppressed_ || !is_active_ || isolate_->needs_side_effect_check();
|
||||
}
|
||||
inline bool break_disabled() const { return break_disabled_; }
|
||||
|
||||
@ -533,10 +522,6 @@ class Debug {
|
||||
// List of active debug info objects.
|
||||
DebugInfoListNode* debug_info_list_;
|
||||
|
||||
// Used for side effect check to mark temporary objects.
|
||||
class TemporaryObjectsTracker;
|
||||
std::unique_ptr<TemporaryObjectsTracker> temporary_objects_;
|
||||
|
||||
// Used to collect histogram data on debugger feature usage.
|
||||
DebugFeatureTracker feature_tracker_;
|
||||
|
||||
@ -744,6 +729,23 @@ class SuppressDebug BASE_EMBEDDED {
|
||||
DISALLOW_COPY_AND_ASSIGN(SuppressDebug);
|
||||
};
|
||||
|
||||
class NoSideEffectScope {
|
||||
public:
|
||||
NoSideEffectScope(Isolate* isolate, bool disallow_side_effects)
|
||||
: isolate_(isolate) {
|
||||
// NoSideEffectScope is not re-entrant if already enabled.
|
||||
CHECK(!isolate->needs_side_effect_check());
|
||||
isolate->set_needs_side_effect_check(disallow_side_effects);
|
||||
isolate->debug()->UpdateHookOnFunctionCall();
|
||||
isolate->debug()->side_effect_check_failed_ = false;
|
||||
}
|
||||
~NoSideEffectScope();
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
DISALLOW_COPY_AND_ASSIGN(NoSideEffectScope);
|
||||
};
|
||||
|
||||
// Code generator routines.
|
||||
class DebugCodegen : public AllStatic {
|
||||
public:
|
||||
|
@ -907,11 +907,6 @@ ExternalReference ExternalReference::debug_hook_on_function_call_address(
|
||||
return ExternalReference(isolate->debug()->hook_on_function_call_address());
|
||||
}
|
||||
|
||||
ExternalReference ExternalReference::debug_execution_mode_address(
|
||||
Isolate* isolate) {
|
||||
return ExternalReference(isolate->debug_execution_mode_address());
|
||||
}
|
||||
|
||||
ExternalReference ExternalReference::runtime_function_table_address(
|
||||
Isolate* isolate) {
|
||||
return ExternalReference(
|
||||
|
@ -48,7 +48,6 @@ class StatsCounter;
|
||||
"copy_typed_array_elements_to_typed_array") \
|
||||
V(cpu_features, "cpu_features") \
|
||||
V(date_cache_stamp, "date_cache_stamp") \
|
||||
V(debug_execution_mode_address, "Isolate::debug_execution_mode()") \
|
||||
V(debug_hook_on_function_call_address, \
|
||||
"Debug::hook_on_function_call_address()") \
|
||||
V(debug_is_active_address, "Debug::is_active_address()") \
|
||||
|
@ -359,8 +359,9 @@ AllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationSpace space,
|
||||
|
||||
|
||||
void Heap::OnAllocationEvent(HeapObject* object, int size_in_bytes) {
|
||||
for (auto& tracker : allocation_trackers_) {
|
||||
tracker->AllocationEvent(object->address(), size_in_bytes);
|
||||
HeapProfiler* profiler = isolate_->heap_profiler();
|
||||
if (profiler->is_tracking_allocations()) {
|
||||
profiler->AllocationEvent(object->address(), size_in_bytes);
|
||||
}
|
||||
|
||||
if (FLAG_verify_predictable) {
|
||||
@ -392,9 +393,6 @@ void Heap::OnMoveEvent(HeapObject* target, HeapObject* source,
|
||||
heap_profiler->ObjectMoveEvent(source->address(), target->address(),
|
||||
size_in_bytes);
|
||||
}
|
||||
for (auto& tracker : allocation_trackers_) {
|
||||
tracker->MoveEvent(source->address(), target->address(), size_in_bytes);
|
||||
}
|
||||
if (target->IsSharedFunctionInfo()) {
|
||||
LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source->address(),
|
||||
target->address()));
|
||||
|
@ -442,20 +442,6 @@ void Heap::ReportStatisticsAfterGC() {
|
||||
}
|
||||
}
|
||||
|
||||
void Heap::AddHeapObjectAllocationTracker(
|
||||
HeapObjectAllocationTracker* tracker) {
|
||||
if (allocation_trackers_.empty()) DisableInlineAllocation();
|
||||
allocation_trackers_.push_back(tracker);
|
||||
}
|
||||
|
||||
void Heap::RemoveHeapObjectAllocationTracker(
|
||||
HeapObjectAllocationTracker* tracker) {
|
||||
allocation_trackers_.erase(std::remove(allocation_trackers_.begin(),
|
||||
allocation_trackers_.end(), tracker),
|
||||
allocation_trackers_.end());
|
||||
if (allocation_trackers_.empty()) EnableInlineAllocation();
|
||||
}
|
||||
|
||||
void Heap::AddRetainingPathTarget(Handle<HeapObject> object,
|
||||
RetainingPathOption option) {
|
||||
if (!FLAG_track_retaining_path) {
|
||||
@ -1956,8 +1942,7 @@ static bool IsLogging(Isolate* isolate) {
|
||||
return FLAG_verify_predictable || isolate->logger()->is_logging() ||
|
||||
isolate->is_profiling() ||
|
||||
(isolate->heap_profiler() != nullptr &&
|
||||
isolate->heap_profiler()->is_tracking_object_moves()) ||
|
||||
isolate->heap()->has_heap_object_allocation_tracker();
|
||||
isolate->heap_profiler()->is_tracking_object_moves());
|
||||
}
|
||||
|
||||
class PageScavengingItem final : public ItemParallelJob::Item {
|
||||
@ -3163,10 +3148,11 @@ void Heap::RightTrimFixedArray(FixedArrayBase* object, int elements_to_trim) {
|
||||
// avoid races with the sweeper thread.
|
||||
object->synchronized_set_length(len - elements_to_trim);
|
||||
|
||||
// Notify the heap object allocation tracker of change in object layout. The
|
||||
// array may not be moved during GC, and size has to be adjusted nevertheless.
|
||||
for (auto& tracker : allocation_trackers_) {
|
||||
tracker->UpdateObjectSizeEvent(object->address(), object->Size());
|
||||
// Notify the heap profiler of change in object layout. The array may not be
|
||||
// moved during GC, and size has to be adjusted nevertheless.
|
||||
HeapProfiler* profiler = isolate()->heap_profiler();
|
||||
if (profiler->is_tracking_allocations()) {
|
||||
profiler->UpdateObjectSizeEvent(object->address(), object->Size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,7 +422,6 @@ class GCIdleTimeAction;
|
||||
class GCIdleTimeHandler;
|
||||
class GCIdleTimeHeapState;
|
||||
class GCTracer;
|
||||
class HeapObjectAllocationTracker;
|
||||
class HeapObjectsFilter;
|
||||
class HeapStats;
|
||||
class HistogramTimer;
|
||||
@ -1620,15 +1619,6 @@ class Heap {
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Heap object allocation tracking. ==========================================
|
||||
// ===========================================================================
|
||||
|
||||
void AddHeapObjectAllocationTracker(HeapObjectAllocationTracker* tracker);
|
||||
void RemoveHeapObjectAllocationTracker(HeapObjectAllocationTracker* tracker);
|
||||
bool has_heap_object_allocation_tracker() const {
|
||||
return !allocation_trackers_.empty();
|
||||
}
|
||||
|
||||
// Retaining path tracking. ==================================================
|
||||
// ===========================================================================
|
||||
|
||||
@ -2680,8 +2670,6 @@ class Heap {
|
||||
// stores the option of the corresponding target.
|
||||
std::map<int, RetainingPathOption> retaining_path_target_option_;
|
||||
|
||||
std::vector<HeapObjectAllocationTracker*> allocation_trackers_;
|
||||
|
||||
// Classes in "heap" can be friends.
|
||||
friend class AlwaysAllocateScope;
|
||||
friend class ConcurrentMarking;
|
||||
@ -2954,16 +2942,6 @@ class AllocationObserver {
|
||||
|
||||
V8_EXPORT_PRIVATE const char* AllocationSpaceName(AllocationSpace space);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Allows observation of heap object allocations.
|
||||
class HeapObjectAllocationTracker {
|
||||
public:
|
||||
virtual void AllocationEvent(Address addr, int size) = 0;
|
||||
virtual void MoveEvent(Address from, Address to, int size) {}
|
||||
virtual void UpdateObjectSizeEvent(Address addr, int size) {}
|
||||
virtual ~HeapObjectAllocationTracker() = default;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -3383,8 +3383,7 @@ void MarkCompactCollectorBase::CreateAndExecuteEvacuationTasks(
|
||||
const bool profiling =
|
||||
heap()->isolate()->is_profiling() ||
|
||||
heap()->isolate()->logger()->is_logging_code_events() ||
|
||||
heap()->isolate()->heap_profiler()->is_tracking_object_moves() ||
|
||||
heap()->has_heap_object_allocation_tracker();
|
||||
heap()->isolate()->heap_profiler()->is_tracking_object_moves();
|
||||
ProfilingMigrationObserver profiling_observer(heap());
|
||||
|
||||
const int wanted_num_tasks =
|
||||
|
@ -26,7 +26,6 @@
|
||||
#include "src/heap/heap.h"
|
||||
#include "src/messages.h"
|
||||
#include "src/objects/code.h"
|
||||
#include "src/objects/debug-objects.h"
|
||||
#include "src/runtime/runtime.h"
|
||||
#include "src/unicode.h"
|
||||
|
||||
@ -438,7 +437,7 @@ typedef std::vector<HeapObject*> DebugObjectCache;
|
||||
/* true if a trace is being formatted through Error.prepareStackTrace. */ \
|
||||
V(bool, formatting_stack_trace, false) \
|
||||
/* Perform side effect checks on function call and API callbacks. */ \
|
||||
V(DebugInfo::ExecutionMode, debug_execution_mode, DebugInfo::kBreakpoints) \
|
||||
V(bool, needs_side_effect_check, false) \
|
||||
/* Current code coverage mode */ \
|
||||
V(debug::Coverage::Mode, code_coverage_mode, debug::Coverage::kBestEffort) \
|
||||
V(debug::TypeProfile::Mode, type_profile_mode, debug::TypeProfile::kNone) \
|
||||
@ -1240,10 +1239,6 @@ class Isolate {
|
||||
return reinterpret_cast<Address>(&handle_scope_implementer_);
|
||||
}
|
||||
|
||||
Address debug_execution_mode_address() {
|
||||
return reinterpret_cast<Address>(&debug_execution_mode_);
|
||||
}
|
||||
|
||||
void DebugStateUpdated();
|
||||
|
||||
void SetPromiseHook(PromiseHook hook);
|
||||
|
@ -13641,12 +13641,8 @@ String* SharedFunctionInfo::DebugName() {
|
||||
// static
|
||||
bool SharedFunctionInfo::HasNoSideEffect(Handle<SharedFunctionInfo> info) {
|
||||
if (!info->computed_has_no_side_effect()) {
|
||||
DebugEvaluate::SideEffectState has_no_side_effect =
|
||||
DebugEvaluate::FunctionGetSideEffectState(info);
|
||||
info->set_has_no_side_effect(has_no_side_effect !=
|
||||
DebugEvaluate::kHasSideEffects);
|
||||
info->set_requires_runtime_side_effect_checks(
|
||||
has_no_side_effect == DebugEvaluate::kRequiresRuntimeChecks);
|
||||
bool has_no_side_effect = DebugEvaluate::FunctionHasNoSideEffect(info);
|
||||
info->set_has_no_side_effect(has_no_side_effect);
|
||||
info->set_computed_has_no_side_effect(true);
|
||||
}
|
||||
return info->has_no_side_effect();
|
||||
|
@ -12,13 +12,9 @@ bool DebugInfo::IsEmpty() const { return flags() == kNone; }
|
||||
|
||||
bool DebugInfo::HasBreakInfo() const { return (flags() & kHasBreakInfo) != 0; }
|
||||
|
||||
DebugInfo::ExecutionMode DebugInfo::DebugExecutionMode() const {
|
||||
return (flags() & kDebugExecutionMode) != 0 ? kSideEffects : kBreakpoints;
|
||||
}
|
||||
|
||||
void DebugInfo::SetDebugExecutionMode(ExecutionMode value) {
|
||||
set_flags(value == kSideEffects ? (flags() | kDebugExecutionMode)
|
||||
: (flags() & ~kDebugExecutionMode));
|
||||
bool DebugInfo::IsPreparedForBreakpoints() const {
|
||||
DCHECK(HasBreakInfo());
|
||||
return (flags() & kPreparedForBreakpoints) != 0;
|
||||
}
|
||||
|
||||
bool DebugInfo::ClearBreakInfo() {
|
||||
@ -28,9 +24,8 @@ bool DebugInfo::ClearBreakInfo() {
|
||||
set_break_points(isolate->heap()->empty_fixed_array());
|
||||
|
||||
int new_flags = flags();
|
||||
new_flags &= ~kHasBreakInfo & ~kPreparedForDebugExecution;
|
||||
new_flags &= ~kHasBreakInfo & ~kPreparedForBreakpoints;
|
||||
new_flags &= ~kBreakAtEntry & ~kCanBreakAtEntry;
|
||||
new_flags &= ~kDebugExecutionMode;
|
||||
set_flags(new_flags);
|
||||
|
||||
return new_flags == kNone;
|
||||
|
@ -24,13 +24,11 @@ class DebugInfo : public Struct {
|
||||
enum Flag {
|
||||
kNone = 0,
|
||||
kHasBreakInfo = 1 << 0,
|
||||
kPreparedForDebugExecution = 1 << 1,
|
||||
kPreparedForBreakpoints = 1 << 1,
|
||||
kHasCoverageInfo = 1 << 2,
|
||||
kBreakAtEntry = 1 << 3,
|
||||
kCanBreakAtEntry = 1 << 4,
|
||||
kDebugExecutionMode = 1 << 5
|
||||
kCanBreakAtEntry = 1 << 4
|
||||
};
|
||||
|
||||
typedef base::Flags<Flag> Flags;
|
||||
|
||||
// A bitfield that lists uses of the current instance.
|
||||
@ -45,27 +43,13 @@ class DebugInfo : public Struct {
|
||||
// DebugInfo can be detached from the SharedFunctionInfo iff it is empty.
|
||||
bool IsEmpty() const;
|
||||
|
||||
// --- Debug execution ---
|
||||
// -----------------------
|
||||
|
||||
enum ExecutionMode { kBreakpoints = 0, kSideEffects = kDebugExecutionMode };
|
||||
|
||||
// Returns current debug execution mode. Debug execution mode defines by
|
||||
// applied to bytecode patching. False for breakpoints, true for side effect
|
||||
// checks.
|
||||
ExecutionMode DebugExecutionMode() const;
|
||||
void SetDebugExecutionMode(ExecutionMode value);
|
||||
|
||||
inline bool HasDebugBytecodeArray();
|
||||
|
||||
inline BytecodeArray* OriginalBytecodeArray();
|
||||
inline BytecodeArray* DebugBytecodeArray();
|
||||
|
||||
// --- Break points ---
|
||||
// --------------------
|
||||
|
||||
bool HasBreakInfo() const;
|
||||
|
||||
bool IsPreparedForBreakpoints() const;
|
||||
|
||||
// Clears all fields related to break points. Returns true iff the
|
||||
// DebugInfo is now empty.
|
||||
bool ClearBreakInfo();
|
||||
@ -98,6 +82,11 @@ class DebugInfo : public Struct {
|
||||
// Get the number of break points for this function.
|
||||
int GetBreakPointCount();
|
||||
|
||||
inline bool HasDebugBytecodeArray();
|
||||
|
||||
inline BytecodeArray* OriginalBytecodeArray();
|
||||
inline BytecodeArray* DebugBytecodeArray();
|
||||
|
||||
// Returns whether we should be able to break before entering the function.
|
||||
// This is true for functions with no source, e.g. builtins.
|
||||
bool CanBreakAtEntry() const;
|
||||
|
@ -197,9 +197,6 @@ BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, deserialized,
|
||||
SharedFunctionInfo::IsDeserializedBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, has_no_side_effect,
|
||||
SharedFunctionInfo::HasNoSideEffectBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints,
|
||||
requires_runtime_side_effect_checks,
|
||||
SharedFunctionInfo::RequiresRuntimeSideEffectChecksBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints,
|
||||
computed_has_no_side_effect,
|
||||
SharedFunctionInfo::ComputedHasNoSideEffectBit)
|
||||
|
@ -240,9 +240,6 @@ class SharedFunctionInfo : public HeapObject {
|
||||
// Indicates that the function cannot cause side-effects.
|
||||
DECL_BOOLEAN_ACCESSORS(has_no_side_effect)
|
||||
|
||||
// Indicates that the function requires runtime side-effect checks.
|
||||
DECL_BOOLEAN_ACCESSORS(requires_runtime_side_effect_checks);
|
||||
|
||||
// Indicates that |has_no_side_effect| has been computed and set.
|
||||
DECL_BOOLEAN_ACCESSORS(computed_has_no_side_effect)
|
||||
|
||||
@ -514,16 +511,15 @@ class SharedFunctionInfo : public HeapObject {
|
||||
STATIC_ASSERT(kLastFunctionKind <= FunctionKindBits::kMax);
|
||||
|
||||
// Bit positions in |debugger_hints|.
|
||||
#define DEBUGGER_HINTS_BIT_FIELDS(V, _) \
|
||||
V(IsAnonymousExpressionBit, bool, 1, _) \
|
||||
V(NameShouldPrintAsAnonymousBit, bool, 1, _) \
|
||||
V(IsDeserializedBit, bool, 1, _) \
|
||||
V(HasNoSideEffectBit, bool, 1, _) \
|
||||
V(RequiresRuntimeSideEffectChecksBit, bool, 1, _) \
|
||||
V(ComputedHasNoSideEffectBit, bool, 1, _) \
|
||||
V(DebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(ComputedDebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(HasReportedBinaryCoverageBit, bool, 1, _) \
|
||||
#define DEBUGGER_HINTS_BIT_FIELDS(V, _) \
|
||||
V(IsAnonymousExpressionBit, bool, 1, _) \
|
||||
V(NameShouldPrintAsAnonymousBit, bool, 1, _) \
|
||||
V(IsDeserializedBit, bool, 1, _) \
|
||||
V(HasNoSideEffectBit, bool, 1, _) \
|
||||
V(ComputedHasNoSideEffectBit, bool, 1, _) \
|
||||
V(DebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(ComputedDebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(HasReportedBinaryCoverageBit, bool, 1, _) \
|
||||
V(DebuggingIdBits, int, 20, _)
|
||||
|
||||
DEFINE_BIT_FIELDS(DEBUGGER_HINTS_BIT_FIELDS)
|
||||
|
@ -132,10 +132,10 @@ v8::AllocationProfile* HeapProfiler::GetAllocationProfile() {
|
||||
void HeapProfiler::StartHeapObjectsTracking(bool track_allocations) {
|
||||
ids_->UpdateHeapObjectsMap();
|
||||
is_tracking_object_moves_ = true;
|
||||
DCHECK(!allocation_tracker_);
|
||||
DCHECK(!is_tracking_allocations());
|
||||
if (track_allocations) {
|
||||
allocation_tracker_.reset(new AllocationTracker(ids_.get(), names_.get()));
|
||||
heap()->AddHeapObjectAllocationTracker(this);
|
||||
heap()->DisableInlineAllocation();
|
||||
heap()->isolate()->debug()->feature_tracker()->Track(
|
||||
DebugFeatureTracker::kAllocationTracking);
|
||||
}
|
||||
@ -148,9 +148,9 @@ SnapshotObjectId HeapProfiler::PushHeapObjectsStats(OutputStream* stream,
|
||||
|
||||
void HeapProfiler::StopHeapObjectsTracking() {
|
||||
ids_->StopHeapObjectsTracking();
|
||||
if (allocation_tracker_) {
|
||||
if (is_tracking_allocations()) {
|
||||
allocation_tracker_.reset();
|
||||
heap()->RemoveHeapObjectAllocationTracker(this);
|
||||
heap()->EnableInlineAllocation();
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ Handle<HeapObject> HeapProfiler::FindHeapObjectById(SnapshotObjectId id) {
|
||||
|
||||
void HeapProfiler::ClearHeapObjectMap() {
|
||||
ids_.reset(new HeapObjectsMap(heap()));
|
||||
if (!allocation_tracker_) is_tracking_object_moves_ = false;
|
||||
if (!is_tracking_allocations()) is_tracking_object_moves_ = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ class HeapSnapshot;
|
||||
class SamplingHeapProfiler;
|
||||
class StringsStorage;
|
||||
|
||||
class HeapProfiler : public HeapObjectAllocationTracker {
|
||||
class HeapProfiler {
|
||||
public:
|
||||
explicit HeapProfiler(Heap* heap);
|
||||
~HeapProfiler();
|
||||
@ -57,9 +57,9 @@ class HeapProfiler : public HeapObjectAllocationTracker {
|
||||
|
||||
void ObjectMoveEvent(Address from, Address to, int size);
|
||||
|
||||
void AllocationEvent(Address addr, int size) override;
|
||||
void AllocationEvent(Address addr, int size);
|
||||
|
||||
void UpdateObjectSizeEvent(Address addr, int size) override;
|
||||
void UpdateObjectSizeEvent(Address addr, int size);
|
||||
|
||||
void DefineWrapperClass(
|
||||
uint16_t class_id, v8::HeapProfiler::WrapperInfoCallback callback);
|
||||
@ -79,6 +79,7 @@ class HeapProfiler : public HeapObjectAllocationTracker {
|
||||
}
|
||||
|
||||
bool is_tracking_object_moves() const { return is_tracking_object_moves_; }
|
||||
bool is_tracking_allocations() const { return !!allocation_tracker_; }
|
||||
|
||||
Handle<HeapObject> FindHeapObjectById(SnapshotObjectId id);
|
||||
void ClearHeapObjectMap();
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "src/debug/liveedit.h"
|
||||
#include "src/frames-inl.h"
|
||||
#include "src/globals.h"
|
||||
#include "src/interpreter/bytecode-array-accessor.h"
|
||||
#include "src/interpreter/bytecodes.h"
|
||||
#include "src/interpreter/interpreter.h"
|
||||
#include "src/isolate-inl.h"
|
||||
@ -44,9 +43,7 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
|
||||
|
||||
// Get the top-most JavaScript frame.
|
||||
JavaScriptFrameIterator it(isolate);
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kBreakpoints) {
|
||||
isolate->debug()->Break(it.frame(), handle(it.frame()->function()));
|
||||
}
|
||||
isolate->debug()->Break(it.frame(), handle(it.frame()->function()));
|
||||
|
||||
// Return the handler from the original bytecode array.
|
||||
DCHECK(it.frame()->is_interpreted());
|
||||
@ -56,20 +53,6 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
|
||||
BytecodeArray* bytecode_array = shared->bytecode_array();
|
||||
int bytecode_offset = interpreted_frame->GetBytecodeOffset();
|
||||
Bytecode bytecode = Bytecodes::FromByte(bytecode_array->get(bytecode_offset));
|
||||
|
||||
bool side_effect_check_failed = false;
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
|
||||
int offset = interpreted_frame->GetBytecodeOffset();
|
||||
interpreter::BytecodeArrayAccessor bytecode_accessor(handle(bytecode_array),
|
||||
offset);
|
||||
interpreter::Register reg = bytecode_accessor.GetRegisterOperand(0);
|
||||
Handle<Object> first_operand = handle(
|
||||
interpreted_frame->ReadInterpreterRegister(reg.index()), isolate);
|
||||
if (!isolate->debug()->PerformSideEffectCheckForObject(first_operand)) {
|
||||
side_effect_check_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Bytecodes::Returns(bytecode)) {
|
||||
// If we are returning (or suspending), reset the bytecode array on the
|
||||
// interpreted stack frame to the non-debug variant so that the interpreter
|
||||
@ -87,8 +70,7 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
|
||||
isolate->interpreter()->GetAndMaybeDeserializeBytecodeHandler(bytecode,
|
||||
operand_scale);
|
||||
|
||||
return MakePair(side_effect_check_failed ? isolate->heap()->exception()
|
||||
: isolate->debug()->return_value(),
|
||||
return MakePair(isolate->debug()->return_value(),
|
||||
Smi::FromInt(static_cast<uint8_t>(bytecode)));
|
||||
}
|
||||
|
||||
@ -109,14 +91,6 @@ RUNTIME_FUNCTION(Runtime_DebugBreakAtEntry) {
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DebugApplyInstrumentation) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
|
||||
isolate->debug()->ApplyInstrumentation(handle(function->shared()));
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_HandleDebuggerStatement) {
|
||||
SealHandleScope shs(isolate);
|
||||
DCHECK_EQ(0, args.length());
|
||||
@ -1679,7 +1653,7 @@ RUNTIME_FUNCTION(Runtime_DebugOnFunctionCall) {
|
||||
if (isolate->debug()->last_step_action() >= StepIn) {
|
||||
isolate->debug()->PrepareStepIn(fun);
|
||||
}
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects &&
|
||||
if (isolate->needs_side_effect_check() &&
|
||||
!isolate->debug()->PerformSideEffectCheck(fun)) {
|
||||
return isolate->heap()->exception();
|
||||
}
|
||||
|
@ -130,7 +130,6 @@ namespace internal {
|
||||
F(CheckExecutionState, 1, 1) \
|
||||
F(ClearStepping, 0, 1) \
|
||||
F(CollectGarbage, 1, 1) \
|
||||
F(DebugApplyInstrumentation, 1, 1) \
|
||||
F(DebugAsyncFunctionPromiseCreated, 1, 1) \
|
||||
F(DebugBreakAtEntry, 1, 1) \
|
||||
F(DebugCollectCoverage, 0, 1) \
|
||||
|
@ -6381,7 +6381,6 @@ TEST(BreakLocationIterator) {
|
||||
|
||||
EnableDebugger(isolate);
|
||||
CHECK(i_isolate->debug()->EnsureBreakInfo(shared));
|
||||
i_isolate->debug()->PrepareFunctionForDebugExecution(shared);
|
||||
|
||||
Handle<i::DebugInfo> debug_info(shared->GetDebugInfo());
|
||||
|
||||
@ -6679,9 +6678,10 @@ TEST(DebugEvaluateNoSideEffect) {
|
||||
// itself contains additional sanity checks.
|
||||
for (i::Handle<i::JSFunction> fun : all_functions) {
|
||||
bool failed = false;
|
||||
isolate->debug()->StartSideEffectCheckMode();
|
||||
failed = !isolate->debug()->PerformSideEffectCheck(fun);
|
||||
isolate->debug()->StopSideEffectCheckMode();
|
||||
{
|
||||
i::NoSideEffectScope scope(isolate, true);
|
||||
failed = !isolate->debug()->PerformSideEffectCheck(fun);
|
||||
}
|
||||
if (failed) isolate->clear_pending_exception();
|
||||
}
|
||||
DisableDebugger(env->GetIsolate());
|
||||
|
@ -11,7 +11,6 @@ var symbol_for_a = Symbol.for("a");
|
||||
var typed_array = new Uint8Array([1, 2, 3]);
|
||||
var array_buffer = new ArrayBuffer(3);
|
||||
var data_view = new DataView(new ArrayBuffer(8), 0, 8);
|
||||
var array = [1,2,3];
|
||||
|
||||
function listener(event, exec_state, event_data, data) {
|
||||
if (event != Debug.DebugEvent.Break) return;
|
||||
@ -72,7 +71,7 @@ function listener(event, exec_state, event_data, data) {
|
||||
"map", "findIndex"
|
||||
];
|
||||
var fails = ["toString", "join", "toLocaleString", "pop", "push", "reverse",
|
||||
"shift", "unshift", "splice", "sort", "copyWithin"];
|
||||
"shift", "unshift", "splice", "sort", "copyWithin", "fill"];
|
||||
for (f of Object.getOwnPropertyNames(Array.prototype)) {
|
||||
if (typeof Array.prototype[f] === "function") {
|
||||
if (fails.includes(f)) {
|
||||
@ -89,9 +88,6 @@ function listener(event, exec_state, event_data, data) {
|
||||
}
|
||||
}
|
||||
|
||||
success([1,1,1], '[1,2,3].fill(1)');
|
||||
fail(`array.fill(1)`);
|
||||
|
||||
// Test ArrayBuffer functions.
|
||||
success(3, `array_buffer.byteLength`);
|
||||
success(2, `array_buffer.slice(1, 3).byteLength`);
|
||||
|
@ -73,7 +73,7 @@ function listener(event, exec_state, event_data, data) {
|
||||
success("s2", `string + two`);
|
||||
fail(`[...array]`);
|
||||
success(3, `max(...array)`);
|
||||
success({s:1}, `({[string]:1})`);
|
||||
fail(`({[string]:1})`);
|
||||
fail(`[a, b] = [1, 2]`);
|
||||
success(2, `def(2)`);
|
||||
success(1, `def()`);
|
||||
|
@ -1,64 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
Debug = debug.Debug;
|
||||
|
||||
// StaNamedProperty
|
||||
var a = {name: 'foo'};
|
||||
function set_name(a) {
|
||||
a.name = 'bar';
|
||||
return a.name;
|
||||
}
|
||||
|
||||
fail(`set_name(a)`);
|
||||
success('bar', `set_name({name: 'foo'})`);
|
||||
|
||||
// StaNamedOwnProperty
|
||||
var name_value = 'value';
|
||||
function create_object_literal() {
|
||||
var obj = {name: name_value};
|
||||
return obj.name;
|
||||
};
|
||||
|
||||
success('value', `create_object_literal()`);
|
||||
|
||||
// StaKeyedProperty
|
||||
var arrayValue = 1;
|
||||
function create_array_literal() {
|
||||
return [arrayValue];
|
||||
}
|
||||
var b = { 1: 2 };
|
||||
|
||||
success([arrayValue], `create_array_literal()`)
|
||||
fail(`b[1] ^= 2`);
|
||||
|
||||
// StaInArrayLiteral
|
||||
function return_array_use_spread(a) {
|
||||
return [...a];
|
||||
}
|
||||
|
||||
fail(`return_array_use_spread([1])`);
|
||||
|
||||
// CallAccessorSetter
|
||||
var array = [1,2,3];
|
||||
fail(`array.length = 2`);
|
||||
// TODO(7515): this one should be side effect free
|
||||
fail(`[1,2,3].length = 2`);
|
||||
|
||||
// StaDataPropertyInLiteral
|
||||
function return_literal_with_data_property(a) {
|
||||
return {[a] : 1};
|
||||
}
|
||||
|
||||
success({foo: 1}, `return_literal_with_data_property('foo')`);
|
||||
|
||||
function success(expectation, source) {
|
||||
const result = Debug.evaluateGlobal(source, true).value();
|
||||
if (expectation !== undefined) assertEquals(expectation, result);
|
||||
}
|
||||
|
||||
function fail(source) {
|
||||
assertThrows(() => Debug.evaluateGlobal(source, true),
|
||||
EvalError);
|
||||
}
|
@ -67,8 +67,8 @@ function listener(event, exec_state, event_data, data) {
|
||||
// Constructed literals.
|
||||
success([1], "[1]");
|
||||
success({x: 1}, "({x: 1})");
|
||||
success([1], "[a]");
|
||||
success({x: 1}, "({x: a})");
|
||||
fail("[a]");
|
||||
fail("({x: a})");
|
||||
// Test that template literal evaluation fails.
|
||||
fail("simple_return`1`");
|
||||
// Test that non-read-only code fails.
|
||||
|
@ -731,9 +731,9 @@ class DebugWrapper {
|
||||
};
|
||||
}
|
||||
|
||||
evaluateGlobal(expr, throw_on_side_effect) {
|
||||
execStateEvaluateGlobal(expr) {
|
||||
const {msgid, msg} = this.createMessage(
|
||||
"Runtime.evaluate", { expression : expr, throwOnSideEffect: throw_on_side_effect });
|
||||
"Runtime.evaluate", { expression : expr });
|
||||
this.sendMessage(msg);
|
||||
const reply = this.takeReplyChecked(msgid);
|
||||
|
||||
@ -830,7 +830,7 @@ class DebugWrapper {
|
||||
let execState = { frames : params.callFrames,
|
||||
prepareStep : this.execStatePrepareStep.bind(this),
|
||||
evaluateGlobal :
|
||||
(expr) => this.evaluateGlobal(expr),
|
||||
(expr) => this.execStateEvaluateGlobal(expr),
|
||||
frame : (index) => this.execStateFrame(
|
||||
index ? params.callFrames[index]
|
||||
: params.callFrames[0]),
|
||||
|
@ -1,26 +0,0 @@
|
||||
Tests how breakpoints and side effects live together.
|
||||
Test breakpoint, should pause inside foo:
|
||||
foo (test.js:3:2)
|
||||
(anonymous) (:0:0)
|
||||
|
||||
Run foo with no side effects:
|
||||
{
|
||||
className : EvalError
|
||||
description : EvalError: Possible side-effect in debug-evaluate
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
|
||||
Test breakpoint after run with side effect check:
|
||||
foo (test.js:3:2)
|
||||
(anonymous) (:0:0)
|
||||
|
||||
Run foo with no side effects after debugger disabled:
|
||||
{
|
||||
className : EvalError
|
||||
description : EvalError: Possible side-effect in debug-evaluate
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
let {session, contextGroup, Protocol} =
|
||||
InspectorTest.start('Tests how breakpoints and side effects live together.');
|
||||
|
||||
(async function main() {
|
||||
session.setupScriptMap();
|
||||
Protocol.Debugger.enable();
|
||||
Protocol.Runtime.evaluate({expression: `
|
||||
var a = 1;
|
||||
function foo() {
|
||||
a = 2;
|
||||
}
|
||||
//# sourceURL=test.js`});
|
||||
Protocol.Debugger.setBreakpointByUrl({url: 'test.js', lineNumber: 3});
|
||||
|
||||
InspectorTest.log('Test breakpoint, should pause inside foo:');
|
||||
{
|
||||
const evaluatePromise = Protocol.Runtime.evaluate({expression: 'foo()'});
|
||||
const {params:{callFrames}} = await Protocol.Debugger.oncePaused();
|
||||
session.logCallFrames(callFrames);
|
||||
Protocol.Debugger.resume();
|
||||
await evaluatePromise;
|
||||
}
|
||||
|
||||
InspectorTest.log('\nRun foo with no side effects:');
|
||||
{
|
||||
const {result:{result}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'foo()',
|
||||
throwOnSideEffect: true
|
||||
});
|
||||
InspectorTest.logMessage(result);
|
||||
}
|
||||
|
||||
InspectorTest.log('\nTest breakpoint after run with side effect check:');
|
||||
{
|
||||
const evaluatePromise = Protocol.Runtime.evaluate({expression: 'foo()'});
|
||||
const {params:{callFrames}} = await Protocol.Debugger.oncePaused();
|
||||
session.logCallFrames(callFrames);
|
||||
Protocol.Debugger.resume();
|
||||
await evaluatePromise;
|
||||
}
|
||||
|
||||
await Protocol.Debugger.disable();
|
||||
|
||||
InspectorTest.log('\nRun foo with no side effects after debugger disabled:');
|
||||
{
|
||||
const {result:{result}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'foo()',
|
||||
throwOnSideEffect: true
|
||||
});
|
||||
InspectorTest.logMessage(result);
|
||||
}
|
||||
|
||||
InspectorTest.completeTest();
|
||||
})();
|
Loading…
Reference in New Issue
Block a user