[debugger] infrastructure for side-effect-free debug-evaluate.

R=jgruber@chromium.org, mstarzinger@chromium.org
BUG=v8:5821

Review-Url: https://codereview.chromium.org/2622863003
Cr-Commit-Position: refs/heads/master@{#42270}
This commit is contained in:
yangguo 2017-01-12 06:18:45 -08:00 committed by Commit bot
parent e00eae9e89
commit aa75904e3c
43 changed files with 570 additions and 161 deletions

View File

@ -10,6 +10,14 @@
namespace v8 {
namespace internal {
#define SIDE_EFFECT_CHECK(ISOLATE, F, RETURN_TYPE) \
do { \
if (ISOLATE->needs_side_effect_check() && \
!PerformSideEffectCheck(ISOLATE, FUNCTION_ADDR(F))) { \
return Handle<RETURN_TYPE>(); \
} \
} while (false)
#define FOR_EACH_CALLBACK_TABLE_MAPPING_1_NAME(F) \
F(AccessorNameGetterCallback, "get", v8::Value, Object) \
F(GenericNamedPropertyQueryCallback, "has", v8::Integer, Object) \
@ -19,6 +27,7 @@ namespace internal {
Handle<InternalReturn> PropertyCallbackArguments::Call(Function f, \
Handle<Name> name) { \
Isolate* isolate = this->isolate(); \
SIDE_EFFECT_CHECK(isolate, f, InternalReturn); \
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
VMState<EXTERNAL> state(isolate); \
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f)); \
@ -43,6 +52,7 @@ FOR_EACH_CALLBACK_TABLE_MAPPING_1_NAME(WRITE_CALL_1_NAME)
Handle<InternalReturn> PropertyCallbackArguments::Call(Function f, \
uint32_t index) { \
Isolate* isolate = this->isolate(); \
SIDE_EFFECT_CHECK(isolate, f, InternalReturn); \
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
VMState<EXTERNAL> state(isolate); \
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f)); \
@ -62,6 +72,7 @@ Handle<Object> PropertyCallbackArguments::Call(
GenericNamedPropertySetterCallback f, Handle<Name> name,
Handle<Object> value) {
Isolate* isolate = this->isolate();
SIDE_EFFECT_CHECK(isolate, f, Object);
RuntimeCallTimerScope timer(
isolate, &RuntimeCallStats::GenericNamedPropertySetterCallback);
VMState<EXTERNAL> state(isolate);
@ -77,6 +88,7 @@ Handle<Object> PropertyCallbackArguments::Call(
GenericNamedPropertyDefinerCallback f, Handle<Name> name,
const v8::PropertyDescriptor& desc) {
Isolate* isolate = this->isolate();
SIDE_EFFECT_CHECK(isolate, f, Object);
RuntimeCallTimerScope timer(
isolate, &RuntimeCallStats::GenericNamedPropertyDefinerCallback);
VMState<EXTERNAL> state(isolate);
@ -92,6 +104,7 @@ Handle<Object> PropertyCallbackArguments::Call(IndexedPropertySetterCallback f,
uint32_t index,
Handle<Object> value) {
Isolate* isolate = this->isolate();
SIDE_EFFECT_CHECK(isolate, f, Object);
RuntimeCallTimerScope timer(isolate,
&RuntimeCallStats::IndexedPropertySetterCallback);
VMState<EXTERNAL> state(isolate);
@ -107,6 +120,7 @@ Handle<Object> PropertyCallbackArguments::Call(
IndexedPropertyDefinerCallback f, uint32_t index,
const v8::PropertyDescriptor& desc) {
Isolate* isolate = this->isolate();
SIDE_EFFECT_CHECK(isolate, f, Object);
RuntimeCallTimerScope timer(
isolate, &RuntimeCallStats::IndexedPropertyDefinerCallback);
VMState<EXTERNAL> state(isolate);
@ -121,6 +135,10 @@ Handle<Object> PropertyCallbackArguments::Call(
void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
Handle<Name> name, Handle<Object> value) {
Isolate* isolate = this->isolate();
if (isolate->needs_side_effect_check() &&
!PerformSideEffectCheck(isolate, FUNCTION_ADDR(f))) {
return;
}
RuntimeCallTimerScope timer(isolate,
&RuntimeCallStats::AccessorNameSetterCallback);
VMState<EXTERNAL> state(isolate);
@ -131,5 +149,7 @@ void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
f(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), info);
}
#undef SIDE_EFFECT_CHECK
} // namespace internal
} // namespace v8

View File

@ -4,6 +4,7 @@
#include "src/api-arguments.h"
#include "src/debug/debug.h"
#include "src/objects-inl.h"
#include "src/tracing/trace-event.h"
#include "src/vm-state-inl.h"
@ -13,6 +14,10 @@ namespace internal {
Handle<Object> FunctionCallbackArguments::Call(FunctionCallback f) {
Isolate* isolate = this->isolate();
if (isolate->needs_side_effect_check() &&
!isolate->debug()->PerformSideEffectCheckForCallback(FUNCTION_ADDR(f))) {
return Handle<Object>();
}
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::FunctionCallback);
VMState<EXTERNAL> state(isolate);
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
@ -24,6 +29,10 @@ Handle<Object> FunctionCallbackArguments::Call(FunctionCallback f) {
Handle<JSObject> PropertyCallbackArguments::Call(
IndexedPropertyEnumeratorCallback f) {
Isolate* isolate = this->isolate();
if (isolate->needs_side_effect_check() &&
!isolate->debug()->PerformSideEffectCheckForCallback(FUNCTION_ADDR(f))) {
return Handle<JSObject>();
}
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::PropertyCallback);
VMState<EXTERNAL> state(isolate);
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
@ -32,5 +41,10 @@ Handle<JSObject> PropertyCallbackArguments::Call(
return GetReturnValue<JSObject>(isolate);
}
bool PropertyCallbackArguments::PerformSideEffectCheck(Isolate* isolate,
Address function) {
return isolate->debug()->PerformSideEffectCheckForCallback(function);
}
} // namespace internal
} // namespace v8

View File

@ -136,6 +136,8 @@ class PropertyCallbackArguments
inline JSObject* holder() {
return JSObject::cast(this->begin()[T::kHolderIndex]);
}
bool PerformSideEffectCheck(Isolate* isolate, Address function);
};
class FunctionCallbackArguments

View File

@ -1758,18 +1758,16 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
}
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
STATIC_ASSERT(StepFrame > StepIn);
mov(r4, Operand(last_step_action));
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_avtive =
ExternalReference::debug_hook_on_function_call_address(isolate());
mov(r4, Operand(debug_hook_avtive));
ldrsb(r4, MemOperand(r4));
cmp(r4, Operand(StepIn));
b(lt, &skip_flooding);
cmp(r4, Operand(0));
b(eq, &skip_hook);
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -1786,7 +1784,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -1800,7 +1798,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiUntag(expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
@ -1814,8 +1812,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(r1));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(r3));
if (call_wrapper.NeedsDebugStepCheck()) {
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.

View File

@ -681,9 +681,10 @@ class MacroAssembler: public Assembler {
const ParameterCount& actual, InvokeFlag flag,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// Invoke the JavaScript function in the given register. Changes the
// current context to the context in the function before invoking.

View File

@ -2366,17 +2366,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
Bind(&regular_invoke);
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
STATIC_ASSERT(StepFrame > StepIn);
Mov(x4, Operand(last_step_action));
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
Mov(x4, Operand(debug_hook_active));
Ldrsb(x4, MemOperand(x4));
CompareAndBranch(x4, Operand(StepIn), lt, &skip_flooding);
CompareAndBranch(x4, Operand(0), eq, &skip_hook);
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -2393,7 +2391,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -2407,7 +2405,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiUntag(expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
@ -2421,7 +2419,9 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(x1));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(x3));
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.
if (!new_target.is_valid()) {

View File

@ -1209,9 +1209,11 @@ class MacroAssembler : public Assembler {
InvokeFlag flag,
bool* definitely_mismatches,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
void InvokeFunctionCode(Register function, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual, InvokeFlag flag,

View File

@ -1584,6 +1584,10 @@ ExternalReference ExternalReference::debug_is_active_address(
return ExternalReference(isolate->debug()->is_active_address());
}
ExternalReference ExternalReference::debug_hook_on_function_call_address(
Isolate* isolate) {
return ExternalReference(isolate->debug()->hook_on_function_call_address());
}
ExternalReference ExternalReference::debug_after_break_target_address(
Isolate* isolate) {

View File

@ -1061,6 +1061,8 @@ class ExternalReference BASE_EMBEDDED {
Isolate* isolate);
static ExternalReference debug_is_active_address(Isolate* isolate);
static ExternalReference debug_hook_on_function_call_address(
Isolate* isolate);
static ExternalReference debug_after_break_target_address(Isolate* isolate);
static ExternalReference is_profiling_address(Isolate* isolate);
@ -1167,7 +1169,7 @@ class CallWrapper {
// Called just after emitting a call, i.e., at the return site for the call.
virtual void AfterCall() const = 0;
// Return whether call needs to check for debug stepping.
virtual bool NeedsDebugStepCheck() const { return false; }
virtual bool NeedsDebugHookCheck() const { return false; }
};
@ -1186,7 +1188,7 @@ class CheckDebugStepCallWrapper : public CallWrapper {
virtual ~CheckDebugStepCallWrapper() {}
virtual void BeforeCall(int call_size) const {}
virtual void AfterCall() const {}
virtual bool NeedsDebugStepCheck() const { return true; }
virtual bool NeedsDebugHookCheck() const { return true; }
};

View File

@ -743,13 +743,12 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
STATIC_ASSERT(StepFrame > StepIn);
__ mov(ip, Operand(last_step_action));
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
__ mov(ip, Operand(debug_hook));
__ ldrsb(ip, MemOperand(ip));
__ cmp(ip, Operand(StepIn));
__ b(ge, &prepare_step_in_if_stepping);
__ cmp(ip, Operand(0));
__ b(ne, &prepare_step_in_if_stepping);
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -817,7 +816,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
{
FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
__ Push(r1, r2, r4);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(r1, r2);
__ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
}

View File

@ -750,12 +750,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
STATIC_ASSERT(StepFrame > StepIn);
__ Mov(x10, Operand(last_step_action));
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
__ Mov(x10, Operand(debug_hook));
__ Ldrsb(x10, MemOperand(x10));
__ CompareAndBranch(x10, Operand(StepIn), ge, &prepare_step_in_if_stepping);
__ CompareAndBranch(x10, Operand(0), ne, &prepare_step_in_if_stepping);
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -815,7 +814,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ Push(x1, x2, x4);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(x2, x1);
__ Ldr(x4, FieldMemOperand(x1, JSGeneratorObject::kFunctionOffset));
}

View File

@ -392,11 +392,10 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
STATIC_ASSERT(StepFrame > StepIn);
__ cmpb(Operand::StaticVariable(last_step_action), Immediate(StepIn));
__ j(greater_equal, &prepare_step_in_if_stepping);
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
__ cmpb(Operand::StaticVariable(debug_hook), Immediate(0));
__ j(not_equal, &prepare_step_in_if_stepping);
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -464,7 +463,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
__ Push(ebx);
__ Push(edx);
__ Push(edi);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(edx);
__ Pop(ebx);
__ mov(edi, FieldOperand(ebx, JSGeneratorObject::kFunctionOffset));

View File

@ -870,12 +870,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
STATIC_ASSERT(StepFrame > StepIn);
__ li(t1, Operand(last_step_action));
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
__ li(t1, Operand(debug_hook));
__ lb(t1, MemOperand(t1));
__ Branch(&prepare_step_in_if_stepping, ge, t1, Operand(StepIn));
__ Branch(&prepare_step_in_if_stepping, ne, t1, Operand(zero_reg));
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -942,7 +941,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ Push(a1, a2, t0);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(a1, a2);
}
__ Branch(USE_DELAY_SLOT, &stepping_prepared);

View File

@ -746,12 +746,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
STATIC_ASSERT(StepFrame > StepIn);
__ li(a5, Operand(last_step_action));
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
__ li(a5, Operand(debug_hook));
__ lb(a5, MemOperand(a5));
__ Branch(&prepare_step_in_if_stepping, ge, a5, Operand(StepIn));
__ Branch(&prepare_step_in_if_stepping, ne, a5, Operand(zero_reg));
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -817,7 +816,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ Push(a1, a2, a4);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(a1, a2);
}
__ Branch(USE_DELAY_SLOT, &stepping_prepared);

View File

@ -464,12 +464,12 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
// Flood function if we are stepping.
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
Label stepping_prepared;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(masm->isolate());
Operand last_step_action_operand = masm->ExternalOperand(last_step_action);
ExternalReference debug_hook =
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
Operand debug_hook_operand = masm->ExternalOperand(debug_hook);
STATIC_ASSERT(StepFrame > StepIn);
__ cmpb(last_step_action_operand, Immediate(StepIn));
__ j(greater_equal, &prepare_step_in_if_stepping);
__ cmpb(debug_hook_operand, Immediate(0));
__ j(not_equal, &prepare_step_in_if_stepping);
// Flood function if we need to continue stepping in the suspended generator.
ExternalReference debug_suspended_generator =
@ -539,7 +539,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
__ Push(rbx);
__ Push(rdx);
__ Push(rdi);
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
__ CallRuntime(Runtime::kDebugOnFunctionCall);
__ Pop(rdx);
__ Pop(rbx);
__ movp(rdi, FieldOperand(rbx, JSGeneratorObject::kFunctionOffset));

View File

@ -136,7 +136,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ FloodFunctionIfStepping(r1, no_reg, dummy, dummy);
__ CheckDebugHook(r1, no_reg, dummy, dummy);
{ ConstantPoolUnavailableScope constant_pool_unavailable(masm);
// Load context from the function.

View File

@ -147,7 +147,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ Pop(fp, lr); // Frame, Return address.
ParameterCount dummy(0);
__ FloodFunctionIfStepping(x1, no_reg, dummy, dummy);
__ CheckDebugHook(x1, no_reg, dummy, dummy);
UseScratchRegisterScope temps(masm);
Register scratch = temps.AcquireX();

View File

@ -12,6 +12,8 @@
#include "src/debug/debug.h"
#include "src/frames-inl.h"
#include "src/globals.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecodes.h"
#include "src/isolate-inl.h"
namespace v8 {
@ -92,9 +94,13 @@ MaybeHandle<Object> DebugEvaluate::Evaluate(
Object);
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL),
Object);
{
NoSideEffectScope no_side_effect(isolate,
FLAG_side_effect_free_debug_evaluate);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL),
Object);
}
// Skip the global proxy as it has no properties and always delegates to the
// real global object.
@ -249,5 +255,145 @@ void DebugEvaluate::ContextBuilder::MaterializeReceiver(
JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
}
namespace {
bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
DCHECK_EQ(Runtime::INLINE, Runtime::FunctionForId(id)->intrinsic_type);
switch (id) {
// Whitelist for intrinsics.
case Runtime::kInlineToObject:
return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] intrinsic %s may cause side effect.\n",
Runtime::FunctionForId(id)->name);
}
return false;
}
}
bool RuntimeFunctionHasNoSideEffect(Runtime::FunctionId id) {
DCHECK_EQ(Runtime::RUNTIME, Runtime::FunctionForId(id)->intrinsic_type);
switch (id) {
// Whitelist for runtime functions.
case Runtime::kToObject:
case Runtime::kLoadLookupSlotForCall:
case Runtime::kThrowReferenceError:
return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] runtime %s may cause side effect.\n",
Runtime::FunctionForId(id)->name);
}
return false;
}
}
bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
typedef interpreter::Bytecode Bytecode;
typedef interpreter::Bytecodes Bytecodes;
if (Bytecodes::IsWithoutExternalSideEffects(bytecode)) return true;
if (Bytecodes::IsCallOrNew(bytecode)) return true;
switch (bytecode) {
// Whitelist for bytecodes.
case Bytecode::kStackCheck:
case Bytecode::kLdaLookupSlot:
case Bytecode::kLdaGlobal:
case Bytecode::kLdaNamedProperty:
case Bytecode::kLdaKeyedProperty:
case Bytecode::kAdd:
case Bytecode::kReturn:
case Bytecode::kCreateCatchContext:
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;
}
}
bool BuiltinHasNoSideEffect(Builtins::Name id) {
switch (id) {
// Whitelist for builtins.
case Builtins::kMathSin:
return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] built-in %s may cause side effect.\n",
Builtins::name(id));
}
return false;
}
}
static const Address accessors_with_no_side_effect[] = {
// Whitelist for accessors.
FUNCTION_ADDR(Accessors::StringLengthGetter),
FUNCTION_ADDR(Accessors::ArrayLengthGetter)};
} // anonymous namespace
// static
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());
}
DCHECK(info->is_compiled());
if (info->HasBytecodeArray()) {
// Check bytecodes against whitelist.
Handle<BytecodeArray> bytecode_array(info->bytecode_array());
if (FLAG_trace_side_effect_free_debug_evaluate) bytecode_array->Print();
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
it.Advance()) {
interpreter::Bytecode bytecode = it.current_bytecode();
if (interpreter::Bytecodes::IsCallRuntime(bytecode)) {
if (bytecode == interpreter::Bytecode::kInvokeIntrinsic) {
Runtime::FunctionId id = it.GetIntrinsicIdOperand(0);
if (IntrinsicHasNoSideEffect(id)) continue;
} else {
Runtime::FunctionId id = it.GetRuntimeIdOperand(0);
if (RuntimeFunctionHasNoSideEffect(id)) continue;
}
return false;
}
if (BytecodeHasNoSideEffect(bytecode)) continue;
// Did not match whitelist.
return false;
}
return true;
} else {
// Check built-ins against whitelist.
int builtin_index = info->code()->builtin_index();
if (builtin_index >= 0 && builtin_index < Builtins::builtin_count &&
BuiltinHasNoSideEffect(static_cast<Builtins::Name>(builtin_index))) {
return true;
}
}
return false;
}
// static
bool DebugEvaluate::CallbackHasNoSideEffect(Address function_addr) {
for (size_t i = 0; i < arraysize(accessors_with_no_side_effect); i++) {
if (function_addr == accessors_with_no_side_effect[i]) return true;
}
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] API Callback at %p may cause side effect.\n",
reinterpret_cast<void*>(function_addr));
}
return false;
}
} // namespace internal
} // namespace v8

View File

@ -24,6 +24,9 @@ class DebugEvaluate : public AllStatic {
int inlined_jsframe_index,
Handle<String> source);
static bool FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info);
static bool CallbackHasNoSideEffect(Address function_addr);
private:
// This class builds a context chain for evaluation of expressions
// in debugger.

View File

@ -14,6 +14,7 @@
#include "src/compilation-cache.h"
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
#include "src/compiler.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/liveedit.h"
#include "src/deoptimizer.h"
#include "src/execution.h"
@ -42,6 +43,7 @@ Debug::Debug(Isolate* isolate)
command_received_(0),
command_queue_(isolate->logger(), kQueueInitialSize),
is_active_(false),
hook_on_function_call_(false),
is_suppressed_(false),
live_edit_enabled_(true), // TODO(yangguo): set to false by default.
break_disabled_(false),
@ -49,6 +51,7 @@ Debug::Debug(Isolate* isolate)
in_debug_event_listener_(false),
break_on_exception_(false),
break_on_uncaught_exception_(false),
side_effect_check_failed_(false),
debug_info_list_(NULL),
feature_tracker_(isolate),
isolate_(isolate) {
@ -406,6 +409,7 @@ void Debug::ThreadInit() {
// TODO(isolates): frames_are_dropped_?
base::NoBarrier_Store(&thread_local_.current_debug_scope_,
static_cast<base::AtomicWord>(0));
UpdateHookOnFunctionCall();
}
@ -906,16 +910,19 @@ bool Debug::IsBreakOnException(ExceptionBreakType type) {
void Debug::PrepareStepIn(Handle<JSFunction> function) {
CHECK(last_step_action() >= StepIn);
if (!is_active()) return;
if (ignore_events()) return;
if (in_debug_scope()) return;
if (break_disabled()) return;
FloodWithOneShot(function);
}
void Debug::PrepareStepInSuspendedGenerator() {
CHECK(has_suspended_generator());
if (!is_active()) return;
if (ignore_events()) return;
if (in_debug_scope()) return;
if (break_disabled()) return;
thread_local_.last_step_action_ = StepIn;
UpdateHookOnFunctionCall();
Handle<JSFunction> function(
JSGeneratorObject::cast(thread_local_.suspended_generator_)->function());
FloodWithOneShot(function);
@ -923,9 +930,10 @@ void Debug::PrepareStepInSuspendedGenerator() {
}
void Debug::PrepareStepOnThrow() {
if (!is_active()) return;
if (last_step_action() == StepNone) return;
if (ignore_events()) return;
if (in_debug_scope()) return;
if (break_disabled()) return;
ClearOneShot();
@ -976,6 +984,7 @@ void Debug::PrepareStep(StepAction step_action) {
feature_tracker()->Track(DebugFeatureTracker::kStepping);
thread_local_.last_step_action_ = step_action;
UpdateHookOnFunctionCall();
// If the function on the top frame is unresolved perform step out. This will
// be the case when calling unknown function and having the debugger stopped
@ -1106,6 +1115,7 @@ void Debug::ClearStepping() {
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_fp_ = 0;
thread_local_.target_fp_ = 0;
UpdateHookOnFunctionCall();
}
@ -2136,6 +2146,13 @@ void Debug::UpdateState() {
is_active_ = is_active;
}
void Debug::UpdateHookOnFunctionCall() {
STATIC_ASSERT(StepFrame > StepIn);
STATIC_ASSERT(LastStepAction == StepFrame);
hook_on_function_call_ = thread_local_.last_step_action_ >= StepIn ||
isolate_->needs_side_effect_check();
}
// Calls the registered debug message handler. This callback is part of the
// public API.
void Debug::InvokeMessageHandler(MessageImpl message) {
@ -2333,6 +2350,50 @@ DebugScope::~DebugScope() {
debug_->UpdateState();
}
bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) {
DCHECK(isolate_->needs_side_effect_check());
DisallowJavascriptExecution no_js(isolate_);
if (!Compiler::Compile(function, Compiler::KEEP_EXCEPTION)) return false;
Deoptimizer::DeoptimizeFunction(*function);
if (!function->shared()->HasNoSideEffect()) {
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] Function %s failed side effect check.\n",
function->shared()->DebugName()->ToCString().get());
}
side_effect_check_failed_ = true;
// Throw an uncatchable termination exception.
isolate_->TerminateExecution();
return false;
}
return true;
}
bool Debug::PerformSideEffectCheckForCallback(Address function) {
DCHECK(isolate_->needs_side_effect_check());
if (DebugEvaluate::CallbackHasNoSideEffect(function)) return true;
side_effect_check_failed_ = true;
// Throw an uncatchable termination exception.
isolate_->TerminateExecution();
isolate_->OptionalRescheduleException(false);
return false;
}
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(old_needs_side_effect_check_);
isolate_->debug()->UpdateHookOnFunctionCall();
isolate_->debug()->side_effect_check_failed_ = false;
}
MessageImpl MessageImpl::NewEvent(DebugEvent event, bool running,
Handle<JSObject> exec_state,
Handle<JSObject> event_data) {

View File

@ -514,6 +514,9 @@ class Debug {
return is_active() && !debug_context().is_null() && break_id() != 0;
}
bool PerformSideEffectCheck(Handle<JSFunction> function);
bool PerformSideEffectCheckForCallback(Address function);
// Flags and states.
DebugScope* debugger_entry() {
return reinterpret_cast<DebugScope*>(
@ -547,6 +550,10 @@ class Debug {
return reinterpret_cast<Address>(&is_active_);
}
Address hook_on_function_call_address() {
return reinterpret_cast<Address>(&hook_on_function_call_);
}
Address after_break_target_address() {
return reinterpret_cast<Address>(&after_break_target_);
}
@ -567,6 +574,7 @@ class Debug {
explicit Debug(Isolate* isolate);
void UpdateState();
void UpdateHookOnFunctionCall();
void Unload();
void SetNextBreakId() {
thread_local_.break_id_ = ++thread_local_.break_count_;
@ -662,16 +670,30 @@ class Debug {
base::Semaphore command_received_; // Signaled for each command received.
LockingCommandMessageQueue command_queue_;
// Debugger is active, i.e. there is a debug event listener attached.
bool is_active_;
// Debugger needs to be notified on every new function call.
// Used for stepping and read-only checks
bool hook_on_function_call_;
// Suppress debug events.
bool is_suppressed_;
// LiveEdit is enabled.
bool live_edit_enabled_;
// Do not trigger debug break events.
bool break_disabled_;
// Do not break on break points.
bool break_points_active_;
// Nested inside a debug event listener.
bool in_debug_event_listener_;
// Trigger debug break events for all exceptions.
bool break_on_exception_;
// Trigger debug break events for uncaught exceptions.
bool break_on_uncaught_exception_;
// Termination exception because side effect check has failed.
bool side_effect_check_failed_;
DebugInfoListNode* debug_info_list_; // List of active debug info objects.
// List of active debug info objects.
DebugInfoListNode* debug_info_list_;
// Storage location for jump when exiting debug break calls.
// Note that this address is not GC safe. It should be computed immediately
@ -731,6 +753,7 @@ class Debug {
friend class DisableBreak;
friend class LiveEdit;
friend class SuppressDebug;
friend class NoSideEffectScope;
friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
friend void CheckDebuggerUnloaded(bool check_functions); // In test-debug.cc
@ -804,6 +827,23 @@ class SuppressDebug BASE_EMBEDDED {
DISALLOW_COPY_AND_ASSIGN(SuppressDebug);
};
class NoSideEffectScope {
public:
NoSideEffectScope(Isolate* isolate, bool disallow_side_effects)
: isolate_(isolate),
old_needs_side_effect_check_(isolate->needs_side_effect_check()) {
isolate->set_needs_side_effect_check(old_needs_side_effect_check_ ||
disallow_side_effects);
isolate->debug()->UpdateHookOnFunctionCall();
isolate->debug()->side_effect_check_failed_ = false;
}
~NoSideEffectScope();
private:
Isolate* isolate_;
bool old_needs_side_effect_check_;
DISALLOW_COPY_AND_ASSIGN(NoSideEffectScope);
};
// Code generator routines.
class DebugCodegen : public AllStatic {

View File

@ -129,7 +129,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ pop(ebp);
ParameterCount dummy(0);
__ FloodFunctionIfStepping(edi, no_reg, dummy, dummy);
__ CheckDebugHook(edi, no_reg, dummy, dummy);
// Load context from the function.
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));

View File

@ -130,7 +130,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
__ CheckDebugHook(a1, no_reg, dummy, dummy);
// Load context from the function.
__ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));

View File

@ -132,7 +132,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
__ CheckDebugHook(a1, no_reg, dummy, dummy);
// Load context from the function.
__ ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));

View File

@ -129,7 +129,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
__ popq(rbp);
ParameterCount dummy(0);
__ FloodFunctionIfStepping(rdi, no_reg, dummy, dummy);
__ CheckDebugHook(rdi, no_reg, dummy, dummy);
// Load context from the function.
__ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));

View File

@ -261,6 +261,8 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
"Debug::after_break_target_address()");
Add(ExternalReference::debug_is_active_address(isolate).address(),
"Debug::is_active_address()");
Add(ExternalReference::debug_hook_on_function_call_address(isolate).address(),
"Debug::hook_on_function_call_address()");
Add(ExternalReference::debug_last_step_action_address(isolate).address(),
"Debug::step_in_enabled_address()");
Add(ExternalReference::debug_suspended_generator_address(isolate).address(),

View File

@ -697,6 +697,11 @@ DEFINE_IMPLICATION(trace_array_abuse, trace_external_array_abuse)
// debugger
DEFINE_BOOL(trace_debug_json, false, "trace debugging JSON request/response")
DEFINE_BOOL(enable_liveedit, true, "enable liveedit experimental feature")
DEFINE_BOOL(side_effect_free_debug_evaluate, false,
"use side-effect-free debug-evaluate for testing")
DEFINE_BOOL(
trace_side_effect_free_debug_evaluate, false,
"print debug messages for side-effect-free debug-evaluate for testing")
DEFINE_BOOL(hard_abort, true, "abort by crashing")
// execution.cc

View File

@ -1937,16 +1937,14 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
}
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
STATIC_ASSERT(StepFrame > StepIn);
cmpb(Operand::StaticVariable(last_step_action), Immediate(StepIn));
j(less, &skip_flooding);
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
cmpb(Operand::StaticVariable(debug_hook_active), Immediate(0));
j(equal, &skip_hook);
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -1963,7 +1961,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -1977,7 +1975,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiUntag(expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
@ -1991,8 +1989,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(edi));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(edx));
if (call_wrapper.NeedsDebugStepCheck()) {
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.

View File

@ -334,9 +334,10 @@ class MacroAssembler: public Assembler {
const ParameterCount& actual, InvokeFlag flag,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// Invoke the JavaScript function in the given register. Changes the
// current context to the context in the function before invoking.

View File

@ -419,6 +419,8 @@ typedef List<HeapObject*> DebugObjectCache;
V(bool, is_profiling, false) \
/* 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(bool, needs_side_effect_check, false) \
ISOLATE_INIT_SIMULATOR_LIST(V)
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \

View File

@ -654,6 +654,7 @@ class ErrorUtils : public AllStatic {
T(YieldInParameter, "Yield expression not allowed in formal parameter") \
/* EvalError */ \
T(CodeGenFromStrings, "%") \
T(NoSideEffectDebugEvaluate, "Possible side-effect in debug-evaluate") \
/* URIError */ \
T(URIMalformed, "URI malformed") \
/* Wasm errors (currently Error) */ \

View File

@ -4558,17 +4558,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
}
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
STATIC_ASSERT(StepFrame > StepIn);
li(t0, Operand(last_step_action));
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
li(t0, Operand(debug_hook_active));
lb(t0, MemOperand(t0));
Branch(&skip_flooding, lt, t0, Operand(StepIn));
Branch(&skip_hook, eq, t0, Operand(zero_reg));
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -4585,7 +4583,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -4599,7 +4597,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiUntag(expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
@ -4613,8 +4611,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(a1));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
if (call_wrapper.NeedsDebugStepCheck()) {
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.

View File

@ -1047,9 +1047,10 @@ class MacroAssembler: public Assembler {
const ParameterCount& actual, InvokeFlag flag,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// Invoke the JavaScript function in the given register. Changes the
// current context to the context in the function before invoking.

View File

@ -4751,17 +4751,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
}
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
STATIC_ASSERT(StepFrame > StepIn);
li(t0, Operand(last_step_action));
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
li(t0, Operand(debug_hook_active));
lb(t0, MemOperand(t0));
Branch(&skip_flooding, lt, t0, Operand(StepIn));
Branch(&skip_hook, eq, t0, Operand(zero_reg));
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -4778,7 +4776,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -4792,7 +4790,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiUntag(expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
@ -4806,8 +4804,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(a1));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
if (call_wrapper.NeedsDebugStepCheck()) {
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.

View File

@ -1100,9 +1100,10 @@ class MacroAssembler: public Assembler {
const ParameterCount& actual, InvokeFlag flag,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// Invoke the JavaScript function in the given register. Changes the
// current context to the context in the function before invoking.

View File

@ -6252,6 +6252,10 @@ BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, must_use_ignition_turbo,
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, dont_flush, kDontFlush)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, is_asm_wasm_broken,
kIsAsmWasmBroken)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, has_no_side_effect,
kHasNoSideEffect)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, computed_has_no_side_effect,
kComputedHasNoSideEffect)
bool Script::HasValidSource() {
Object* src = this->source();

View File

@ -28,6 +28,7 @@
#include "src/counters-inl.h"
#include "src/counters.h"
#include "src/date.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/debug.h"
#include "src/deoptimizer.h"
#include "src/elements.h"
@ -13342,6 +13343,16 @@ String* SharedFunctionInfo::DebugName() {
return String::cast(n);
}
bool SharedFunctionInfo::HasNoSideEffect() {
if (!computed_has_no_side_effect()) {
DisallowHeapAllocation not_handlified;
Handle<SharedFunctionInfo> info(this);
set_has_no_side_effect(DebugEvaluate::FunctionHasNoSideEffect(info));
set_computed_has_no_side_effect(true);
}
return has_no_side_effect();
}
// The filter is a pattern that matches function names in this way:
// "*" all; the default
// "-" all but the top-level function

View File

@ -7359,6 +7359,9 @@ class SharedFunctionInfo: public HeapObject {
// The function's name if it is non-empty, otherwise the inferred name.
String* DebugName();
// The function cannot cause any side effects.
bool HasNoSideEffect();
// Used for flags such as --hydrogen-filter.
bool PassesFilter(const char* raw_filter);
@ -7468,6 +7471,12 @@ class SharedFunctionInfo: public HeapObject {
// Indicates that asm->wasm conversion failed and should not be re-attempted.
DECL_BOOLEAN_ACCESSORS(is_asm_wasm_broken)
// Indicates that the function cannot cause side-effects.
DECL_BOOLEAN_ACCESSORS(has_no_side_effect)
// Indicates that |has_no_side_effect| has been computed and set.
DECL_BOOLEAN_ACCESSORS(computed_has_no_side_effect)
inline FunctionKind kind() const;
inline void set_kind(FunctionKind kind);
@ -7754,6 +7763,8 @@ class SharedFunctionInfo: public HeapObject {
// byte 3
kDeserialized = kFunctionKind + 10,
kIsAsmWasmBroken,
kHasNoSideEffect,
kComputedHasNoSideEffect,
kCompilerHintsCount, // Pseudo entry
};
// kFunctionKind has to be byte-aligned

View File

@ -5,6 +5,7 @@
#include "src/runtime/runtime-utils.h"
#include "src/arguments.h"
#include "src/compiler.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/debug-frames.h"
#include "src/debug/debug-scopes.h"
@ -1845,14 +1846,19 @@ RUNTIME_FUNCTION(Runtime_ScriptSourceLine) {
return *str;
}
// Set one shot breakpoints for the callback function that is passed to a
// built-in function such as Array.forEach to enable stepping into the callback,
// if we are indeed stepping and the callback is subject to debugging.
RUNTIME_FUNCTION(Runtime_DebugPrepareStepInIfStepping) {
// On function call, depending on circumstances, prepare for stepping in,
// or perform a side effect check.
RUNTIME_FUNCTION(Runtime_DebugOnFunctionCall) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
isolate->debug()->PrepareStepIn(fun);
if (isolate->debug()->last_step_action() >= StepIn) {
isolate->debug()->PrepareStepIn(fun);
}
if (isolate->needs_side_effect_check() &&
!isolate->debug()->PerformSideEffectCheck(fun)) {
return isolate->heap()->exception();
}
return isolate->heap()->undefined_value();
}

View File

@ -194,7 +194,7 @@ namespace internal {
F(ScriptPositionInfo, 3, 1) \
F(ScriptPositionInfo2, 3, 1) \
F(ScriptSourceLine, 2, 1) \
F(DebugPrepareStepInIfStepping, 1, 1) \
F(DebugOnFunctionCall, 1, 1) \
F(DebugPrepareStepInSuspendedGenerator, 0, 1) \
F(DebugRecordGenerator, 1, 1) \
F(DebugPushPromise, 1, 1) \

View File

@ -4211,8 +4211,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
DCHECK(function.is(rdi));
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(rdx));
if (call_wrapper.NeedsDebugStepCheck()) {
FloodFunctionIfStepping(function, new_target, expected, actual);
if (call_wrapper.NeedsDebugHookCheck()) {
CheckDebugHook(function, new_target, expected, actual);
}
// Clear the new.target register if not given.
@ -4312,17 +4312,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
}
}
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_flooding;
ExternalReference last_step_action =
ExternalReference::debug_last_step_action_address(isolate());
Operand last_step_action_operand = ExternalOperand(last_step_action);
STATIC_ASSERT(StepFrame > StepIn);
cmpb(last_step_action_operand, Immediate(StepIn));
j(less, &skip_flooding);
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual) {
Label skip_hook;
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
Operand debug_hook_active_operand = ExternalOperand(debug_hook_active);
cmpb(debug_hook_active_operand, Immediate(0));
j(equal, &skip_hook);
{
FrameScope frame(this,
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
@ -4339,7 +4337,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
}
Push(fun);
Push(fun);
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
@ -4353,7 +4351,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
SmiToInteger64(expected.reg(), expected.reg());
}
}
bind(&skip_flooding);
bind(&skip_hook);
}
void MacroAssembler::StubPrologue(StackFrame::Type type) {

View File

@ -390,9 +390,10 @@ class MacroAssembler: public Assembler {
const ParameterCount& actual, InvokeFlag flag,
const CallWrapper& call_wrapper);
void FloodFunctionIfStepping(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// On function call, call into the debugger if necessary.
void CheckDebugHook(Register fun, Register new_target,
const ParameterCount& expected,
const ParameterCount& actual);
// Invoke the JavaScript function in the given register. Changes the
// current context to the context in the function before invoking.

View File

@ -0,0 +1,83 @@
// Copyright 2017 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.
// Flags: --ignition --side-effect-free-debug-evaluate
Debug = debug.Debug
var exception = null;
let a = 1;
var object = { property : 2,
get getter() { return 3; }
};
var string1 = { toString() { return "x"; } };
var string2 = { toString() { print("x"); return "x"; } };
var array = [4, 5];
var error = new Error();
function set_a() { a = 2; }
function get_a() { return a; }
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
function success(expectation, source) {
assertEquals(expectation, exec_state.frame(0).evaluate(source).value());
}
function fail(source) {
assertThrows(() => exec_state.frame(0).evaluate(source), EvalError);
}
// Simple test.
success(3, "1 + 2");
// Dymanic load.
success(array, "array");
// Context load.
success(1, "a");
// Global and named property load.
success(2, "object.property");
// Load via read-only getter.
success(3, "object.getter");
// Implicit call to read-only toString.
success("xy", "string1 + 'y'");
// Keyed property load.
success(5, "array[1]");
// Call to read-only function.
success(1, "get_a()");
// Call to read-only function within try-catch.
success(1, "try { get_a() } catch (e) {}");
// Call to C++ built-in.
success(Math.sin(2), "Math.sin(2)");
// Call to whitelisted get accessors.
success(3, "'abc'.length");
success(2, "array.length");
// Test that non-read-only code fails.
fail("exception = 1");
// Test that calling a non-read-only function fails.
fail("set_a()");
// Test that implicit call to a non-read-only function fails.
fail("string2 + 'y'");
// Test that try-catch does not catch the EvalError.
fail("try { set_a() } catch (e) {}");
// Test that call to set accessor fails.
fail("array.length = 4");
// Test that call to non-whitelisted get accessor fails.
fail("error.stack");
} catch (e) {
exception = e;
print(e, e.stack);
};
};
// Add the debug event listener.
Debug.setListener(listener);
function f() {
debugger;
};
f();
assertNull(exception);
assertEquals(1, a);