[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:
parent
e00eae9e89
commit
aa75904e3c
@ -10,6 +10,14 @@
|
|||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
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) \
|
#define FOR_EACH_CALLBACK_TABLE_MAPPING_1_NAME(F) \
|
||||||
F(AccessorNameGetterCallback, "get", v8::Value, Object) \
|
F(AccessorNameGetterCallback, "get", v8::Value, Object) \
|
||||||
F(GenericNamedPropertyQueryCallback, "has", v8::Integer, Object) \
|
F(GenericNamedPropertyQueryCallback, "has", v8::Integer, Object) \
|
||||||
@ -19,6 +27,7 @@ namespace internal {
|
|||||||
Handle<InternalReturn> PropertyCallbackArguments::Call(Function f, \
|
Handle<InternalReturn> PropertyCallbackArguments::Call(Function f, \
|
||||||
Handle<Name> name) { \
|
Handle<Name> name) { \
|
||||||
Isolate* isolate = this->isolate(); \
|
Isolate* isolate = this->isolate(); \
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, InternalReturn); \
|
||||||
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
|
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
|
||||||
VMState<EXTERNAL> state(isolate); \
|
VMState<EXTERNAL> state(isolate); \
|
||||||
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f)); \
|
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, \
|
Handle<InternalReturn> PropertyCallbackArguments::Call(Function f, \
|
||||||
uint32_t index) { \
|
uint32_t index) { \
|
||||||
Isolate* isolate = this->isolate(); \
|
Isolate* isolate = this->isolate(); \
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, InternalReturn); \
|
||||||
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
|
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::Function); \
|
||||||
VMState<EXTERNAL> state(isolate); \
|
VMState<EXTERNAL> state(isolate); \
|
||||||
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f)); \
|
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f)); \
|
||||||
@ -62,6 +72,7 @@ Handle<Object> PropertyCallbackArguments::Call(
|
|||||||
GenericNamedPropertySetterCallback f, Handle<Name> name,
|
GenericNamedPropertySetterCallback f, Handle<Name> name,
|
||||||
Handle<Object> value) {
|
Handle<Object> value) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, Object);
|
||||||
RuntimeCallTimerScope timer(
|
RuntimeCallTimerScope timer(
|
||||||
isolate, &RuntimeCallStats::GenericNamedPropertySetterCallback);
|
isolate, &RuntimeCallStats::GenericNamedPropertySetterCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
@ -77,6 +88,7 @@ Handle<Object> PropertyCallbackArguments::Call(
|
|||||||
GenericNamedPropertyDefinerCallback f, Handle<Name> name,
|
GenericNamedPropertyDefinerCallback f, Handle<Name> name,
|
||||||
const v8::PropertyDescriptor& desc) {
|
const v8::PropertyDescriptor& desc) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, Object);
|
||||||
RuntimeCallTimerScope timer(
|
RuntimeCallTimerScope timer(
|
||||||
isolate, &RuntimeCallStats::GenericNamedPropertyDefinerCallback);
|
isolate, &RuntimeCallStats::GenericNamedPropertyDefinerCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
@ -92,6 +104,7 @@ Handle<Object> PropertyCallbackArguments::Call(IndexedPropertySetterCallback f,
|
|||||||
uint32_t index,
|
uint32_t index,
|
||||||
Handle<Object> value) {
|
Handle<Object> value) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, Object);
|
||||||
RuntimeCallTimerScope timer(isolate,
|
RuntimeCallTimerScope timer(isolate,
|
||||||
&RuntimeCallStats::IndexedPropertySetterCallback);
|
&RuntimeCallStats::IndexedPropertySetterCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
@ -107,6 +120,7 @@ Handle<Object> PropertyCallbackArguments::Call(
|
|||||||
IndexedPropertyDefinerCallback f, uint32_t index,
|
IndexedPropertyDefinerCallback f, uint32_t index,
|
||||||
const v8::PropertyDescriptor& desc) {
|
const v8::PropertyDescriptor& desc) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
SIDE_EFFECT_CHECK(isolate, f, Object);
|
||||||
RuntimeCallTimerScope timer(
|
RuntimeCallTimerScope timer(
|
||||||
isolate, &RuntimeCallStats::IndexedPropertyDefinerCallback);
|
isolate, &RuntimeCallStats::IndexedPropertyDefinerCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
@ -121,6 +135,10 @@ Handle<Object> PropertyCallbackArguments::Call(
|
|||||||
void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
|
void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
|
||||||
Handle<Name> name, Handle<Object> value) {
|
Handle<Name> name, Handle<Object> value) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
if (isolate->needs_side_effect_check() &&
|
||||||
|
!PerformSideEffectCheck(isolate, FUNCTION_ADDR(f))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
RuntimeCallTimerScope timer(isolate,
|
RuntimeCallTimerScope timer(isolate,
|
||||||
&RuntimeCallStats::AccessorNameSetterCallback);
|
&RuntimeCallStats::AccessorNameSetterCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
@ -131,5 +149,7 @@ void PropertyCallbackArguments::Call(AccessorNameSetterCallback f,
|
|||||||
f(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), info);
|
f(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef SIDE_EFFECT_CHECK
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "src/api-arguments.h"
|
#include "src/api-arguments.h"
|
||||||
|
|
||||||
|
#include "src/debug/debug.h"
|
||||||
#include "src/objects-inl.h"
|
#include "src/objects-inl.h"
|
||||||
#include "src/tracing/trace-event.h"
|
#include "src/tracing/trace-event.h"
|
||||||
#include "src/vm-state-inl.h"
|
#include "src/vm-state-inl.h"
|
||||||
@ -13,6 +14,10 @@ namespace internal {
|
|||||||
|
|
||||||
Handle<Object> FunctionCallbackArguments::Call(FunctionCallback f) {
|
Handle<Object> FunctionCallbackArguments::Call(FunctionCallback f) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
if (isolate->needs_side_effect_check() &&
|
||||||
|
!isolate->debug()->PerformSideEffectCheckForCallback(FUNCTION_ADDR(f))) {
|
||||||
|
return Handle<Object>();
|
||||||
|
}
|
||||||
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::FunctionCallback);
|
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::FunctionCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
|
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
|
||||||
@ -24,6 +29,10 @@ Handle<Object> FunctionCallbackArguments::Call(FunctionCallback f) {
|
|||||||
Handle<JSObject> PropertyCallbackArguments::Call(
|
Handle<JSObject> PropertyCallbackArguments::Call(
|
||||||
IndexedPropertyEnumeratorCallback f) {
|
IndexedPropertyEnumeratorCallback f) {
|
||||||
Isolate* isolate = this->isolate();
|
Isolate* isolate = this->isolate();
|
||||||
|
if (isolate->needs_side_effect_check() &&
|
||||||
|
!isolate->debug()->PerformSideEffectCheckForCallback(FUNCTION_ADDR(f))) {
|
||||||
|
return Handle<JSObject>();
|
||||||
|
}
|
||||||
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::PropertyCallback);
|
RuntimeCallTimerScope timer(isolate, &RuntimeCallStats::PropertyCallback);
|
||||||
VMState<EXTERNAL> state(isolate);
|
VMState<EXTERNAL> state(isolate);
|
||||||
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
|
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
|
||||||
@ -32,5 +41,10 @@ Handle<JSObject> PropertyCallbackArguments::Call(
|
|||||||
return GetReturnValue<JSObject>(isolate);
|
return GetReturnValue<JSObject>(isolate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PropertyCallbackArguments::PerformSideEffectCheck(Isolate* isolate,
|
||||||
|
Address function) {
|
||||||
|
return isolate->debug()->PerformSideEffectCheckForCallback(function);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -136,6 +136,8 @@ class PropertyCallbackArguments
|
|||||||
inline JSObject* holder() {
|
inline JSObject* holder() {
|
||||||
return JSObject::cast(this->begin()[T::kHolderIndex]);
|
return JSObject::cast(this->begin()[T::kHolderIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PerformSideEffectCheck(Isolate* isolate, Address function);
|
||||||
};
|
};
|
||||||
|
|
||||||
class FunctionCallbackArguments
|
class FunctionCallbackArguments
|
||||||
|
@ -1758,18 +1758,16 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_avtive =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
mov(r4, Operand(debug_hook_avtive));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
|
||||||
mov(r4, Operand(last_step_action));
|
|
||||||
ldrsb(r4, MemOperand(r4));
|
ldrsb(r4, MemOperand(r4));
|
||||||
cmp(r4, Operand(StepIn));
|
cmp(r4, Operand(0));
|
||||||
b(lt, &skip_flooding);
|
b(eq, &skip_hook);
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -1786,7 +1784,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -1800,7 +1798,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiUntag(expected.reg());
|
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(function.is(r1));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(r3));
|
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(r3));
|
||||||
|
|
||||||
if (call_wrapper.NeedsDebugStepCheck()) {
|
if (call_wrapper.NeedsDebugHookCheck()) {
|
||||||
FloodFunctionIfStepping(function, new_target, expected, actual);
|
CheckDebugHook(function, new_target, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new.target register if not given.
|
// Clear the new.target register if not given.
|
||||||
|
@ -681,9 +681,10 @@ class MacroAssembler: public Assembler {
|
|||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
|
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& expected,
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
const ParameterCount& actual);
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
|
|
||||||
// Invoke the JavaScript function in the given register. Changes the
|
// Invoke the JavaScript function in the given register. Changes the
|
||||||
// current context to the context in the function before invoking.
|
// current context to the context in the function before invoking.
|
||||||
|
@ -2366,17 +2366,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
Bind(®ular_invoke);
|
Bind(®ular_invoke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_active =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
Mov(x4, Operand(debug_hook_active));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
|
||||||
Mov(x4, Operand(last_step_action));
|
|
||||||
Ldrsb(x4, MemOperand(x4));
|
Ldrsb(x4, MemOperand(x4));
|
||||||
CompareAndBranch(x4, Operand(StepIn), lt, &skip_flooding);
|
CompareAndBranch(x4, Operand(0), eq, &skip_hook);
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -2393,7 +2391,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -2407,7 +2405,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiUntag(expected.reg());
|
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(function.is(x1));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(x3));
|
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.
|
// Clear the new.target register if not given.
|
||||||
if (!new_target.is_valid()) {
|
if (!new_target.is_valid()) {
|
||||||
|
@ -1209,9 +1209,11 @@ class MacroAssembler : public Assembler {
|
|||||||
InvokeFlag flag,
|
InvokeFlag flag,
|
||||||
bool* definitely_mismatches,
|
bool* definitely_mismatches,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
|
||||||
const ParameterCount& expected,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& actual);
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
void InvokeFunctionCode(Register function, Register new_target,
|
void InvokeFunctionCode(Register function, Register new_target,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
|
@ -1584,6 +1584,10 @@ ExternalReference ExternalReference::debug_is_active_address(
|
|||||||
return ExternalReference(isolate->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(
|
ExternalReference ExternalReference::debug_after_break_target_address(
|
||||||
Isolate* isolate) {
|
Isolate* isolate) {
|
||||||
|
@ -1061,6 +1061,8 @@ class ExternalReference BASE_EMBEDDED {
|
|||||||
Isolate* isolate);
|
Isolate* isolate);
|
||||||
|
|
||||||
static ExternalReference debug_is_active_address(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 debug_after_break_target_address(Isolate* isolate);
|
||||||
|
|
||||||
static ExternalReference is_profiling_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.
|
// Called just after emitting a call, i.e., at the return site for the call.
|
||||||
virtual void AfterCall() const = 0;
|
virtual void AfterCall() const = 0;
|
||||||
// Return whether call needs to check for debug stepping.
|
// 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 ~CheckDebugStepCallWrapper() {}
|
||||||
virtual void BeforeCall(int call_size) const {}
|
virtual void BeforeCall(int call_size) const {}
|
||||||
virtual void AfterCall() const {}
|
virtual void AfterCall() const {}
|
||||||
virtual bool NeedsDebugStepCheck() const { return true; }
|
virtual bool NeedsDebugHookCheck() const { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -743,13 +743,12 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
__ mov(ip, Operand(debug_hook));
|
||||||
__ mov(ip, Operand(last_step_action));
|
|
||||||
__ ldrsb(ip, MemOperand(ip));
|
__ ldrsb(ip, MemOperand(ip));
|
||||||
__ cmp(ip, Operand(StepIn));
|
__ cmp(ip, Operand(0));
|
||||||
__ b(ge, &prepare_step_in_if_stepping);
|
__ b(ne, &prepare_step_in_if_stepping);
|
||||||
|
|
||||||
// Flood function if we need to continue stepping in the suspended generator.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -817,7 +816,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
{
|
{
|
||||||
FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
|
FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
|
||||||
__ Push(r1, r2, r4);
|
__ Push(r1, r2, r4);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(r1, r2);
|
__ Pop(r1, r2);
|
||||||
__ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
|
__ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
|
||||||
}
|
}
|
||||||
|
@ -750,12 +750,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
__ Mov(x10, Operand(debug_hook));
|
||||||
__ Mov(x10, Operand(last_step_action));
|
|
||||||
__ Ldrsb(x10, MemOperand(x10));
|
__ 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.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -815,7 +814,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
{
|
{
|
||||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||||
__ Push(x1, x2, x4);
|
__ Push(x1, x2, x4);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(x2, x1);
|
__ Pop(x2, x1);
|
||||||
__ Ldr(x4, FieldMemOperand(x1, JSGeneratorObject::kFunctionOffset));
|
__ Ldr(x4, FieldMemOperand(x1, JSGeneratorObject::kFunctionOffset));
|
||||||
}
|
}
|
||||||
|
@ -392,11 +392,10 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
__ cmpb(Operand::StaticVariable(debug_hook), Immediate(0));
|
||||||
__ cmpb(Operand::StaticVariable(last_step_action), Immediate(StepIn));
|
__ j(not_equal, &prepare_step_in_if_stepping);
|
||||||
__ j(greater_equal, &prepare_step_in_if_stepping);
|
|
||||||
|
|
||||||
// Flood function if we need to continue stepping in the suspended generator.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -464,7 +463,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
__ Push(ebx);
|
__ Push(ebx);
|
||||||
__ Push(edx);
|
__ Push(edx);
|
||||||
__ Push(edi);
|
__ Push(edi);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(edx);
|
__ Pop(edx);
|
||||||
__ Pop(ebx);
|
__ Pop(ebx);
|
||||||
__ mov(edi, FieldOperand(ebx, JSGeneratorObject::kFunctionOffset));
|
__ mov(edi, FieldOperand(ebx, JSGeneratorObject::kFunctionOffset));
|
||||||
|
@ -870,12 +870,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
__ li(t1, Operand(debug_hook));
|
||||||
__ li(t1, Operand(last_step_action));
|
|
||||||
__ lb(t1, MemOperand(t1));
|
__ 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.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -942,7 +941,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
{
|
{
|
||||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||||
__ Push(a1, a2, t0);
|
__ Push(a1, a2, t0);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(a1, a2);
|
__ Pop(a1, a2);
|
||||||
}
|
}
|
||||||
__ Branch(USE_DELAY_SLOT, &stepping_prepared);
|
__ Branch(USE_DELAY_SLOT, &stepping_prepared);
|
||||||
|
@ -746,12 +746,11 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
__ li(a5, Operand(debug_hook));
|
||||||
__ li(a5, Operand(last_step_action));
|
|
||||||
__ lb(a5, MemOperand(a5));
|
__ 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.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -817,7 +816,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
{
|
{
|
||||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||||
__ Push(a1, a2, a4);
|
__ Push(a1, a2, a4);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(a1, a2);
|
__ Pop(a1, a2);
|
||||||
}
|
}
|
||||||
__ Branch(USE_DELAY_SLOT, &stepping_prepared);
|
__ Branch(USE_DELAY_SLOT, &stepping_prepared);
|
||||||
|
@ -464,12 +464,12 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
// Flood function if we are stepping.
|
// Flood function if we are stepping.
|
||||||
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
Label prepare_step_in_if_stepping, prepare_step_in_suspended_generator;
|
||||||
Label stepping_prepared;
|
Label stepping_prepared;
|
||||||
ExternalReference last_step_action =
|
ExternalReference debug_hook =
|
||||||
ExternalReference::debug_last_step_action_address(masm->isolate());
|
ExternalReference::debug_hook_on_function_call_address(masm->isolate());
|
||||||
Operand last_step_action_operand = masm->ExternalOperand(last_step_action);
|
Operand debug_hook_operand = masm->ExternalOperand(debug_hook);
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
STATIC_ASSERT(StepFrame > StepIn);
|
||||||
__ cmpb(last_step_action_operand, Immediate(StepIn));
|
__ cmpb(debug_hook_operand, Immediate(0));
|
||||||
__ j(greater_equal, &prepare_step_in_if_stepping);
|
__ j(not_equal, &prepare_step_in_if_stepping);
|
||||||
|
|
||||||
// Flood function if we need to continue stepping in the suspended generator.
|
// Flood function if we need to continue stepping in the suspended generator.
|
||||||
ExternalReference debug_suspended_generator =
|
ExternalReference debug_suspended_generator =
|
||||||
@ -539,7 +539,7 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
|||||||
__ Push(rbx);
|
__ Push(rbx);
|
||||||
__ Push(rdx);
|
__ Push(rdx);
|
||||||
__ Push(rdi);
|
__ Push(rdi);
|
||||||
__ CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
__ Pop(rdx);
|
__ Pop(rdx);
|
||||||
__ Pop(rbx);
|
__ Pop(rbx);
|
||||||
__ movp(rdi, FieldOperand(rbx, JSGeneratorObject::kFunctionOffset));
|
__ movp(rdi, FieldOperand(rbx, JSGeneratorObject::kFunctionOffset));
|
||||||
|
@ -136,7 +136,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ LeaveFrame(StackFrame::INTERNAL);
|
__ LeaveFrame(StackFrame::INTERNAL);
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(r1, no_reg, dummy, dummy);
|
__ CheckDebugHook(r1, no_reg, dummy, dummy);
|
||||||
|
|
||||||
{ ConstantPoolUnavailableScope constant_pool_unavailable(masm);
|
{ ConstantPoolUnavailableScope constant_pool_unavailable(masm);
|
||||||
// Load context from the function.
|
// Load context from the function.
|
||||||
|
@ -147,7 +147,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ Pop(fp, lr); // Frame, Return address.
|
__ Pop(fp, lr); // Frame, Return address.
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(x1, no_reg, dummy, dummy);
|
__ CheckDebugHook(x1, no_reg, dummy, dummy);
|
||||||
|
|
||||||
UseScratchRegisterScope temps(masm);
|
UseScratchRegisterScope temps(masm);
|
||||||
Register scratch = temps.AcquireX();
|
Register scratch = temps.AcquireX();
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include "src/debug/debug.h"
|
#include "src/debug/debug.h"
|
||||||
#include "src/frames-inl.h"
|
#include "src/frames-inl.h"
|
||||||
#include "src/globals.h"
|
#include "src/globals.h"
|
||||||
|
#include "src/interpreter/bytecode-array-iterator.h"
|
||||||
|
#include "src/interpreter/bytecodes.h"
|
||||||
#include "src/isolate-inl.h"
|
#include "src/isolate-inl.h"
|
||||||
|
|
||||||
namespace v8 {
|
namespace v8 {
|
||||||
@ -92,9 +94,13 @@ MaybeHandle<Object> DebugEvaluate::Evaluate(
|
|||||||
Object);
|
Object);
|
||||||
|
|
||||||
Handle<Object> result;
|
Handle<Object> result;
|
||||||
ASSIGN_RETURN_ON_EXCEPTION(
|
{
|
||||||
isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL),
|
NoSideEffectScope no_side_effect(isolate,
|
||||||
Object);
|
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
|
// Skip the global proxy as it has no properties and always delegates to the
|
||||||
// real global object.
|
// real global object.
|
||||||
@ -249,5 +255,145 @@ void DebugEvaluate::ContextBuilder::MaterializeReceiver(
|
|||||||
JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
|
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 internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -24,6 +24,9 @@ class DebugEvaluate : public AllStatic {
|
|||||||
int inlined_jsframe_index,
|
int inlined_jsframe_index,
|
||||||
Handle<String> source);
|
Handle<String> source);
|
||||||
|
|
||||||
|
static bool FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info);
|
||||||
|
static bool CallbackHasNoSideEffect(Address function_addr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// This class builds a context chain for evaluation of expressions
|
// This class builds a context chain for evaluation of expressions
|
||||||
// in debugger.
|
// in debugger.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "src/compilation-cache.h"
|
#include "src/compilation-cache.h"
|
||||||
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
|
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
|
||||||
#include "src/compiler.h"
|
#include "src/compiler.h"
|
||||||
|
#include "src/debug/debug-evaluate.h"
|
||||||
#include "src/debug/liveedit.h"
|
#include "src/debug/liveedit.h"
|
||||||
#include "src/deoptimizer.h"
|
#include "src/deoptimizer.h"
|
||||||
#include "src/execution.h"
|
#include "src/execution.h"
|
||||||
@ -42,6 +43,7 @@ Debug::Debug(Isolate* isolate)
|
|||||||
command_received_(0),
|
command_received_(0),
|
||||||
command_queue_(isolate->logger(), kQueueInitialSize),
|
command_queue_(isolate->logger(), kQueueInitialSize),
|
||||||
is_active_(false),
|
is_active_(false),
|
||||||
|
hook_on_function_call_(false),
|
||||||
is_suppressed_(false),
|
is_suppressed_(false),
|
||||||
live_edit_enabled_(true), // TODO(yangguo): set to false by default.
|
live_edit_enabled_(true), // TODO(yangguo): set to false by default.
|
||||||
break_disabled_(false),
|
break_disabled_(false),
|
||||||
@ -49,6 +51,7 @@ Debug::Debug(Isolate* isolate)
|
|||||||
in_debug_event_listener_(false),
|
in_debug_event_listener_(false),
|
||||||
break_on_exception_(false),
|
break_on_exception_(false),
|
||||||
break_on_uncaught_exception_(false),
|
break_on_uncaught_exception_(false),
|
||||||
|
side_effect_check_failed_(false),
|
||||||
debug_info_list_(NULL),
|
debug_info_list_(NULL),
|
||||||
feature_tracker_(isolate),
|
feature_tracker_(isolate),
|
||||||
isolate_(isolate) {
|
isolate_(isolate) {
|
||||||
@ -406,6 +409,7 @@ void Debug::ThreadInit() {
|
|||||||
// TODO(isolates): frames_are_dropped_?
|
// TODO(isolates): frames_are_dropped_?
|
||||||
base::NoBarrier_Store(&thread_local_.current_debug_scope_,
|
base::NoBarrier_Store(&thread_local_.current_debug_scope_,
|
||||||
static_cast<base::AtomicWord>(0));
|
static_cast<base::AtomicWord>(0));
|
||||||
|
UpdateHookOnFunctionCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -906,16 +910,19 @@ bool Debug::IsBreakOnException(ExceptionBreakType type) {
|
|||||||
|
|
||||||
void Debug::PrepareStepIn(Handle<JSFunction> function) {
|
void Debug::PrepareStepIn(Handle<JSFunction> function) {
|
||||||
CHECK(last_step_action() >= StepIn);
|
CHECK(last_step_action() >= StepIn);
|
||||||
if (!is_active()) return;
|
if (ignore_events()) return;
|
||||||
if (in_debug_scope()) return;
|
if (in_debug_scope()) return;
|
||||||
|
if (break_disabled()) return;
|
||||||
FloodWithOneShot(function);
|
FloodWithOneShot(function);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Debug::PrepareStepInSuspendedGenerator() {
|
void Debug::PrepareStepInSuspendedGenerator() {
|
||||||
CHECK(has_suspended_generator());
|
CHECK(has_suspended_generator());
|
||||||
if (!is_active()) return;
|
if (ignore_events()) return;
|
||||||
if (in_debug_scope()) return;
|
if (in_debug_scope()) return;
|
||||||
|
if (break_disabled()) return;
|
||||||
thread_local_.last_step_action_ = StepIn;
|
thread_local_.last_step_action_ = StepIn;
|
||||||
|
UpdateHookOnFunctionCall();
|
||||||
Handle<JSFunction> function(
|
Handle<JSFunction> function(
|
||||||
JSGeneratorObject::cast(thread_local_.suspended_generator_)->function());
|
JSGeneratorObject::cast(thread_local_.suspended_generator_)->function());
|
||||||
FloodWithOneShot(function);
|
FloodWithOneShot(function);
|
||||||
@ -923,9 +930,10 @@ void Debug::PrepareStepInSuspendedGenerator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Debug::PrepareStepOnThrow() {
|
void Debug::PrepareStepOnThrow() {
|
||||||
if (!is_active()) return;
|
|
||||||
if (last_step_action() == StepNone) return;
|
if (last_step_action() == StepNone) return;
|
||||||
|
if (ignore_events()) return;
|
||||||
if (in_debug_scope()) return;
|
if (in_debug_scope()) return;
|
||||||
|
if (break_disabled()) return;
|
||||||
|
|
||||||
ClearOneShot();
|
ClearOneShot();
|
||||||
|
|
||||||
@ -976,6 +984,7 @@ void Debug::PrepareStep(StepAction step_action) {
|
|||||||
feature_tracker()->Track(DebugFeatureTracker::kStepping);
|
feature_tracker()->Track(DebugFeatureTracker::kStepping);
|
||||||
|
|
||||||
thread_local_.last_step_action_ = step_action;
|
thread_local_.last_step_action_ = step_action;
|
||||||
|
UpdateHookOnFunctionCall();
|
||||||
|
|
||||||
// If the function on the top frame is unresolved perform step out. This will
|
// 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
|
// 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_statement_position_ = kNoSourcePosition;
|
||||||
thread_local_.last_fp_ = 0;
|
thread_local_.last_fp_ = 0;
|
||||||
thread_local_.target_fp_ = 0;
|
thread_local_.target_fp_ = 0;
|
||||||
|
UpdateHookOnFunctionCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2136,6 +2146,13 @@ void Debug::UpdateState() {
|
|||||||
is_active_ = is_active;
|
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
|
// Calls the registered debug message handler. This callback is part of the
|
||||||
// public API.
|
// public API.
|
||||||
void Debug::InvokeMessageHandler(MessageImpl message) {
|
void Debug::InvokeMessageHandler(MessageImpl message) {
|
||||||
@ -2333,6 +2350,50 @@ DebugScope::~DebugScope() {
|
|||||||
debug_->UpdateState();
|
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,
|
MessageImpl MessageImpl::NewEvent(DebugEvent event, bool running,
|
||||||
Handle<JSObject> exec_state,
|
Handle<JSObject> exec_state,
|
||||||
Handle<JSObject> event_data) {
|
Handle<JSObject> event_data) {
|
||||||
|
@ -514,6 +514,9 @@ class Debug {
|
|||||||
return is_active() && !debug_context().is_null() && break_id() != 0;
|
return is_active() && !debug_context().is_null() && break_id() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PerformSideEffectCheck(Handle<JSFunction> function);
|
||||||
|
bool PerformSideEffectCheckForCallback(Address function);
|
||||||
|
|
||||||
// Flags and states.
|
// Flags and states.
|
||||||
DebugScope* debugger_entry() {
|
DebugScope* debugger_entry() {
|
||||||
return reinterpret_cast<DebugScope*>(
|
return reinterpret_cast<DebugScope*>(
|
||||||
@ -547,6 +550,10 @@ class Debug {
|
|||||||
return reinterpret_cast<Address>(&is_active_);
|
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() {
|
Address after_break_target_address() {
|
||||||
return reinterpret_cast<Address>(&after_break_target_);
|
return reinterpret_cast<Address>(&after_break_target_);
|
||||||
}
|
}
|
||||||
@ -567,6 +574,7 @@ class Debug {
|
|||||||
explicit Debug(Isolate* isolate);
|
explicit Debug(Isolate* isolate);
|
||||||
|
|
||||||
void UpdateState();
|
void UpdateState();
|
||||||
|
void UpdateHookOnFunctionCall();
|
||||||
void Unload();
|
void Unload();
|
||||||
void SetNextBreakId() {
|
void SetNextBreakId() {
|
||||||
thread_local_.break_id_ = ++thread_local_.break_count_;
|
thread_local_.break_id_ = ++thread_local_.break_count_;
|
||||||
@ -662,16 +670,30 @@ class Debug {
|
|||||||
base::Semaphore command_received_; // Signaled for each command received.
|
base::Semaphore command_received_; // Signaled for each command received.
|
||||||
LockingCommandMessageQueue command_queue_;
|
LockingCommandMessageQueue command_queue_;
|
||||||
|
|
||||||
|
// Debugger is active, i.e. there is a debug event listener attached.
|
||||||
bool is_active_;
|
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_;
|
bool is_suppressed_;
|
||||||
|
// LiveEdit is enabled.
|
||||||
bool live_edit_enabled_;
|
bool live_edit_enabled_;
|
||||||
|
// Do not trigger debug break events.
|
||||||
bool break_disabled_;
|
bool break_disabled_;
|
||||||
|
// Do not break on break points.
|
||||||
bool break_points_active_;
|
bool break_points_active_;
|
||||||
|
// Nested inside a debug event listener.
|
||||||
bool in_debug_event_listener_;
|
bool in_debug_event_listener_;
|
||||||
|
// Trigger debug break events for all exceptions.
|
||||||
bool break_on_exception_;
|
bool break_on_exception_;
|
||||||
|
// Trigger debug break events for uncaught exceptions.
|
||||||
bool break_on_uncaught_exception_;
|
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.
|
// Storage location for jump when exiting debug break calls.
|
||||||
// Note that this address is not GC safe. It should be computed immediately
|
// Note that this address is not GC safe. It should be computed immediately
|
||||||
@ -731,6 +753,7 @@ class Debug {
|
|||||||
friend class DisableBreak;
|
friend class DisableBreak;
|
||||||
friend class LiveEdit;
|
friend class LiveEdit;
|
||||||
friend class SuppressDebug;
|
friend class SuppressDebug;
|
||||||
|
friend class NoSideEffectScope;
|
||||||
|
|
||||||
friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
|
friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
|
||||||
friend void CheckDebuggerUnloaded(bool check_functions); // 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);
|
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.
|
// Code generator routines.
|
||||||
class DebugCodegen : public AllStatic {
|
class DebugCodegen : public AllStatic {
|
||||||
|
@ -129,7 +129,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ pop(ebp);
|
__ pop(ebp);
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(edi, no_reg, dummy, dummy);
|
__ CheckDebugHook(edi, no_reg, dummy, dummy);
|
||||||
|
|
||||||
// Load context from the function.
|
// Load context from the function.
|
||||||
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
|
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
|
||||||
|
@ -130,7 +130,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ LeaveFrame(StackFrame::INTERNAL);
|
__ LeaveFrame(StackFrame::INTERNAL);
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
|
__ CheckDebugHook(a1, no_reg, dummy, dummy);
|
||||||
|
|
||||||
// Load context from the function.
|
// Load context from the function.
|
||||||
__ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
|
__ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
|
||||||
|
@ -132,7 +132,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ LeaveFrame(StackFrame::INTERNAL);
|
__ LeaveFrame(StackFrame::INTERNAL);
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
|
__ CheckDebugHook(a1, no_reg, dummy, dummy);
|
||||||
|
|
||||||
// Load context from the function.
|
// Load context from the function.
|
||||||
__ ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
|
__ ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
|
||||||
|
@ -129,7 +129,7 @@ void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
|||||||
__ popq(rbp);
|
__ popq(rbp);
|
||||||
|
|
||||||
ParameterCount dummy(0);
|
ParameterCount dummy(0);
|
||||||
__ FloodFunctionIfStepping(rdi, no_reg, dummy, dummy);
|
__ CheckDebugHook(rdi, no_reg, dummy, dummy);
|
||||||
|
|
||||||
// Load context from the function.
|
// Load context from the function.
|
||||||
__ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));
|
__ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));
|
||||||
|
@ -261,6 +261,8 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
|
|||||||
"Debug::after_break_target_address()");
|
"Debug::after_break_target_address()");
|
||||||
Add(ExternalReference::debug_is_active_address(isolate).address(),
|
Add(ExternalReference::debug_is_active_address(isolate).address(),
|
||||||
"Debug::is_active_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(),
|
Add(ExternalReference::debug_last_step_action_address(isolate).address(),
|
||||||
"Debug::step_in_enabled_address()");
|
"Debug::step_in_enabled_address()");
|
||||||
Add(ExternalReference::debug_suspended_generator_address(isolate).address(),
|
Add(ExternalReference::debug_suspended_generator_address(isolate).address(),
|
||||||
|
@ -697,6 +697,11 @@ DEFINE_IMPLICATION(trace_array_abuse, trace_external_array_abuse)
|
|||||||
// debugger
|
// debugger
|
||||||
DEFINE_BOOL(trace_debug_json, false, "trace debugging JSON request/response")
|
DEFINE_BOOL(trace_debug_json, false, "trace debugging JSON request/response")
|
||||||
DEFINE_BOOL(enable_liveedit, true, "enable liveedit experimental feature")
|
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")
|
DEFINE_BOOL(hard_abort, true, "abort by crashing")
|
||||||
|
|
||||||
// execution.cc
|
// execution.cc
|
||||||
|
@ -1937,16 +1937,14 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_active =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
cmpb(Operand::StaticVariable(debug_hook_active), Immediate(0));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
j(equal, &skip_hook);
|
||||||
cmpb(Operand::StaticVariable(last_step_action), Immediate(StepIn));
|
|
||||||
j(less, &skip_flooding);
|
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -1963,7 +1961,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -1977,7 +1975,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiUntag(expected.reg());
|
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(function.is(edi));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(edx));
|
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(edx));
|
||||||
|
|
||||||
if (call_wrapper.NeedsDebugStepCheck()) {
|
if (call_wrapper.NeedsDebugHookCheck()) {
|
||||||
FloodFunctionIfStepping(function, new_target, expected, actual);
|
CheckDebugHook(function, new_target, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new.target register if not given.
|
// Clear the new.target register if not given.
|
||||||
|
@ -334,9 +334,10 @@ class MacroAssembler: public Assembler {
|
|||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
|
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& expected,
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
const ParameterCount& actual);
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
|
|
||||||
// Invoke the JavaScript function in the given register. Changes the
|
// Invoke the JavaScript function in the given register. Changes the
|
||||||
// current context to the context in the function before invoking.
|
// current context to the context in the function before invoking.
|
||||||
|
@ -419,6 +419,8 @@ typedef List<HeapObject*> DebugObjectCache;
|
|||||||
V(bool, is_profiling, false) \
|
V(bool, is_profiling, false) \
|
||||||
/* true if a trace is being formatted through Error.prepareStackTrace. */ \
|
/* true if a trace is being formatted through Error.prepareStackTrace. */ \
|
||||||
V(bool, formatting_stack_trace, false) \
|
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)
|
ISOLATE_INIT_SIMULATOR_LIST(V)
|
||||||
|
|
||||||
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
|
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
|
||||||
|
@ -654,6 +654,7 @@ class ErrorUtils : public AllStatic {
|
|||||||
T(YieldInParameter, "Yield expression not allowed in formal parameter") \
|
T(YieldInParameter, "Yield expression not allowed in formal parameter") \
|
||||||
/* EvalError */ \
|
/* EvalError */ \
|
||||||
T(CodeGenFromStrings, "%") \
|
T(CodeGenFromStrings, "%") \
|
||||||
|
T(NoSideEffectDebugEvaluate, "Possible side-effect in debug-evaluate") \
|
||||||
/* URIError */ \
|
/* URIError */ \
|
||||||
T(URIMalformed, "URI malformed") \
|
T(URIMalformed, "URI malformed") \
|
||||||
/* Wasm errors (currently Error) */ \
|
/* Wasm errors (currently Error) */ \
|
||||||
|
@ -4558,17 +4558,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_active =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
li(t0, Operand(debug_hook_active));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
|
||||||
li(t0, Operand(last_step_action));
|
|
||||||
lb(t0, MemOperand(t0));
|
lb(t0, MemOperand(t0));
|
||||||
Branch(&skip_flooding, lt, t0, Operand(StepIn));
|
Branch(&skip_hook, eq, t0, Operand(zero_reg));
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -4585,7 +4583,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -4599,7 +4597,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiUntag(expected.reg());
|
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(function.is(a1));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
|
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
|
||||||
|
|
||||||
if (call_wrapper.NeedsDebugStepCheck()) {
|
if (call_wrapper.NeedsDebugHookCheck()) {
|
||||||
FloodFunctionIfStepping(function, new_target, expected, actual);
|
CheckDebugHook(function, new_target, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new.target register if not given.
|
// Clear the new.target register if not given.
|
||||||
|
@ -1047,9 +1047,10 @@ class MacroAssembler: public Assembler {
|
|||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
|
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& expected,
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
const ParameterCount& actual);
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
|
|
||||||
// Invoke the JavaScript function in the given register. Changes the
|
// Invoke the JavaScript function in the given register. Changes the
|
||||||
// current context to the context in the function before invoking.
|
// current context to the context in the function before invoking.
|
||||||
|
@ -4751,17 +4751,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_active =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
li(t0, Operand(debug_hook_active));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
|
||||||
li(t0, Operand(last_step_action));
|
|
||||||
lb(t0, MemOperand(t0));
|
lb(t0, MemOperand(t0));
|
||||||
Branch(&skip_flooding, lt, t0, Operand(StepIn));
|
Branch(&skip_hook, eq, t0, Operand(zero_reg));
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -4778,7 +4776,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -4792,7 +4790,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiUntag(expected.reg());
|
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(function.is(a1));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
|
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(a3));
|
||||||
|
|
||||||
if (call_wrapper.NeedsDebugStepCheck()) {
|
if (call_wrapper.NeedsDebugHookCheck()) {
|
||||||
FloodFunctionIfStepping(function, new_target, expected, actual);
|
CheckDebugHook(function, new_target, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new.target register if not given.
|
// Clear the new.target register if not given.
|
||||||
|
@ -1100,9 +1100,10 @@ class MacroAssembler: public Assembler {
|
|||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
|
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& expected,
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
const ParameterCount& actual);
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
|
|
||||||
// Invoke the JavaScript function in the given register. Changes the
|
// Invoke the JavaScript function in the given register. Changes the
|
||||||
// current context to the context in the function before invoking.
|
// current context to the context in the function before invoking.
|
||||||
|
@ -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, dont_flush, kDontFlush)
|
||||||
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, is_asm_wasm_broken,
|
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, is_asm_wasm_broken,
|
||||||
kIsAsmWasmBroken)
|
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() {
|
bool Script::HasValidSource() {
|
||||||
Object* src = this->source();
|
Object* src = this->source();
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "src/counters-inl.h"
|
#include "src/counters-inl.h"
|
||||||
#include "src/counters.h"
|
#include "src/counters.h"
|
||||||
#include "src/date.h"
|
#include "src/date.h"
|
||||||
|
#include "src/debug/debug-evaluate.h"
|
||||||
#include "src/debug/debug.h"
|
#include "src/debug/debug.h"
|
||||||
#include "src/deoptimizer.h"
|
#include "src/deoptimizer.h"
|
||||||
#include "src/elements.h"
|
#include "src/elements.h"
|
||||||
@ -13342,6 +13343,16 @@ String* SharedFunctionInfo::DebugName() {
|
|||||||
return String::cast(n);
|
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:
|
// The filter is a pattern that matches function names in this way:
|
||||||
// "*" all; the default
|
// "*" all; the default
|
||||||
// "-" all but the top-level function
|
// "-" all but the top-level function
|
||||||
|
@ -7359,6 +7359,9 @@ class SharedFunctionInfo: public HeapObject {
|
|||||||
// The function's name if it is non-empty, otherwise the inferred name.
|
// The function's name if it is non-empty, otherwise the inferred name.
|
||||||
String* DebugName();
|
String* DebugName();
|
||||||
|
|
||||||
|
// The function cannot cause any side effects.
|
||||||
|
bool HasNoSideEffect();
|
||||||
|
|
||||||
// Used for flags such as --hydrogen-filter.
|
// Used for flags such as --hydrogen-filter.
|
||||||
bool PassesFilter(const char* raw_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.
|
// Indicates that asm->wasm conversion failed and should not be re-attempted.
|
||||||
DECL_BOOLEAN_ACCESSORS(is_asm_wasm_broken)
|
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 FunctionKind kind() const;
|
||||||
inline void set_kind(FunctionKind kind);
|
inline void set_kind(FunctionKind kind);
|
||||||
|
|
||||||
@ -7754,6 +7763,8 @@ class SharedFunctionInfo: public HeapObject {
|
|||||||
// byte 3
|
// byte 3
|
||||||
kDeserialized = kFunctionKind + 10,
|
kDeserialized = kFunctionKind + 10,
|
||||||
kIsAsmWasmBroken,
|
kIsAsmWasmBroken,
|
||||||
|
kHasNoSideEffect,
|
||||||
|
kComputedHasNoSideEffect,
|
||||||
kCompilerHintsCount, // Pseudo entry
|
kCompilerHintsCount, // Pseudo entry
|
||||||
};
|
};
|
||||||
// kFunctionKind has to be byte-aligned
|
// kFunctionKind has to be byte-aligned
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "src/runtime/runtime-utils.h"
|
#include "src/runtime/runtime-utils.h"
|
||||||
|
|
||||||
#include "src/arguments.h"
|
#include "src/arguments.h"
|
||||||
|
#include "src/compiler.h"
|
||||||
#include "src/debug/debug-evaluate.h"
|
#include "src/debug/debug-evaluate.h"
|
||||||
#include "src/debug/debug-frames.h"
|
#include "src/debug/debug-frames.h"
|
||||||
#include "src/debug/debug-scopes.h"
|
#include "src/debug/debug-scopes.h"
|
||||||
@ -1845,14 +1846,19 @@ RUNTIME_FUNCTION(Runtime_ScriptSourceLine) {
|
|||||||
return *str;
|
return *str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set one shot breakpoints for the callback function that is passed to a
|
// On function call, depending on circumstances, prepare for stepping in,
|
||||||
// built-in function such as Array.forEach to enable stepping into the callback,
|
// or perform a side effect check.
|
||||||
// if we are indeed stepping and the callback is subject to debugging.
|
RUNTIME_FUNCTION(Runtime_DebugOnFunctionCall) {
|
||||||
RUNTIME_FUNCTION(Runtime_DebugPrepareStepInIfStepping) {
|
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
DCHECK_EQ(1, args.length());
|
DCHECK_EQ(1, args.length());
|
||||||
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
|
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();
|
return isolate->heap()->undefined_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ namespace internal {
|
|||||||
F(ScriptPositionInfo, 3, 1) \
|
F(ScriptPositionInfo, 3, 1) \
|
||||||
F(ScriptPositionInfo2, 3, 1) \
|
F(ScriptPositionInfo2, 3, 1) \
|
||||||
F(ScriptSourceLine, 2, 1) \
|
F(ScriptSourceLine, 2, 1) \
|
||||||
F(DebugPrepareStepInIfStepping, 1, 1) \
|
F(DebugOnFunctionCall, 1, 1) \
|
||||||
F(DebugPrepareStepInSuspendedGenerator, 0, 1) \
|
F(DebugPrepareStepInSuspendedGenerator, 0, 1) \
|
||||||
F(DebugRecordGenerator, 1, 1) \
|
F(DebugRecordGenerator, 1, 1) \
|
||||||
F(DebugPushPromise, 1, 1) \
|
F(DebugPushPromise, 1, 1) \
|
||||||
|
@ -4211,8 +4211,8 @@ void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
|
|||||||
DCHECK(function.is(rdi));
|
DCHECK(function.is(rdi));
|
||||||
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(rdx));
|
DCHECK_IMPLIES(new_target.is_valid(), new_target.is(rdx));
|
||||||
|
|
||||||
if (call_wrapper.NeedsDebugStepCheck()) {
|
if (call_wrapper.NeedsDebugHookCheck()) {
|
||||||
FloodFunctionIfStepping(function, new_target, expected, actual);
|
CheckDebugHook(function, new_target, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new.target register if not given.
|
// Clear the new.target register if not given.
|
||||||
@ -4312,17 +4312,15 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||||
void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
const ParameterCount& expected,
|
||||||
const ParameterCount& expected,
|
const ParameterCount& actual) {
|
||||||
const ParameterCount& actual) {
|
Label skip_hook;
|
||||||
Label skip_flooding;
|
ExternalReference debug_hook_active =
|
||||||
ExternalReference last_step_action =
|
ExternalReference::debug_hook_on_function_call_address(isolate());
|
||||||
ExternalReference::debug_last_step_action_address(isolate());
|
Operand debug_hook_active_operand = ExternalOperand(debug_hook_active);
|
||||||
Operand last_step_action_operand = ExternalOperand(last_step_action);
|
cmpb(debug_hook_active_operand, Immediate(0));
|
||||||
STATIC_ASSERT(StepFrame > StepIn);
|
j(equal, &skip_hook);
|
||||||
cmpb(last_step_action_operand, Immediate(StepIn));
|
|
||||||
j(less, &skip_flooding);
|
|
||||||
{
|
{
|
||||||
FrameScope frame(this,
|
FrameScope frame(this,
|
||||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||||
@ -4339,7 +4337,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
}
|
}
|
||||||
Push(fun);
|
Push(fun);
|
||||||
Push(fun);
|
Push(fun);
|
||||||
CallRuntime(Runtime::kDebugPrepareStepInIfStepping);
|
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||||
Pop(fun);
|
Pop(fun);
|
||||||
if (new_target.is_valid()) {
|
if (new_target.is_valid()) {
|
||||||
Pop(new_target);
|
Pop(new_target);
|
||||||
@ -4353,7 +4351,7 @@ void MacroAssembler::FloodFunctionIfStepping(Register fun, Register new_target,
|
|||||||
SmiToInteger64(expected.reg(), expected.reg());
|
SmiToInteger64(expected.reg(), expected.reg());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bind(&skip_flooding);
|
bind(&skip_hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroAssembler::StubPrologue(StackFrame::Type type) {
|
void MacroAssembler::StubPrologue(StackFrame::Type type) {
|
||||||
|
@ -390,9 +390,10 @@ class MacroAssembler: public Assembler {
|
|||||||
const ParameterCount& actual, InvokeFlag flag,
|
const ParameterCount& actual, InvokeFlag flag,
|
||||||
const CallWrapper& call_wrapper);
|
const CallWrapper& call_wrapper);
|
||||||
|
|
||||||
void FloodFunctionIfStepping(Register fun, Register new_target,
|
// On function call, call into the debugger if necessary.
|
||||||
const ParameterCount& expected,
|
void CheckDebugHook(Register fun, Register new_target,
|
||||||
const ParameterCount& actual);
|
const ParameterCount& expected,
|
||||||
|
const ParameterCount& actual);
|
||||||
|
|
||||||
// Invoke the JavaScript function in the given register. Changes the
|
// Invoke the JavaScript function in the given register. Changes the
|
||||||
// current context to the context in the function before invoking.
|
// current context to the context in the function before invoking.
|
||||||
|
83
test/debugger/debug/debug-evaluate-no-side-effect.js
Normal file
83
test/debugger/debug/debug-evaluate-no-side-effect.js
Normal 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);
|
Loading…
Reference in New Issue
Block a user