[debug] allow calls to some builtins on temporary objects
This CL allows SetPrototypeAdd and ArrayIteratorPrototypeNext to be called on temporary objects during side effect free evaluation. Bug: v8:7588 Change-Id: Id77848e48d98c243de91bc6c0fae5a0877e693d4 Reviewed-on: https://chromium-review.googlesource.com/998439 Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#52548}
This commit is contained in:
parent
e921be5c4f
commit
077205be55
@ -1380,6 +1380,13 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
b(eq, &skip_hook);
|
||||
|
||||
{
|
||||
// Load receiver to pass it later to DebugOnFunctionCall hook.
|
||||
if (actual.is_reg()) {
|
||||
mov(r4, actual.reg());
|
||||
} else {
|
||||
mov(r4, Operand(actual.immediate()));
|
||||
}
|
||||
ldr(r4, MemOperand(sp, r4, LSL, kPointerSizeLog2));
|
||||
FrameScope frame(this,
|
||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||
if (expected.is_reg()) {
|
||||
@ -1395,6 +1402,7 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
}
|
||||
Push(fun);
|
||||
Push(fun);
|
||||
Push(r4);
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
Pop(fun);
|
||||
if (new_target.is_valid()) {
|
||||
|
@ -2168,6 +2168,11 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
Cbz(x4, &skip_hook);
|
||||
|
||||
{
|
||||
// Load receiver to pass it later to DebugOnFunctionCall hook.
|
||||
Operand actual_op = actual.is_immediate() ? Operand(actual.immediate())
|
||||
: Operand(actual.reg());
|
||||
Mov(x4, actual_op);
|
||||
Ldr(x4, MemOperand(sp, x4, LSL, kPointerSizeLog2));
|
||||
FrameScope frame(this,
|
||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||
|
||||
@ -2181,8 +2186,7 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
SmiTag(expected_reg);
|
||||
SmiTag(actual_reg);
|
||||
Push(expected_reg, actual_reg, new_target, fun);
|
||||
|
||||
PushArgument(fun);
|
||||
Push(fun, x4);
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
|
||||
// Restore values from stack.
|
||||
|
@ -562,6 +562,8 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
{
|
||||
FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(r1, r4);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ PushRoot(Heap::kTheHoleValueRootIndex);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(r1);
|
||||
__ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
|
||||
|
@ -611,8 +611,9 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
__ Bind(&prepare_step_in_if_stepping);
|
||||
{
|
||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(x1, padreg);
|
||||
__ PushArgument(x4);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ LoadRoot(x5, Heap::kTheHoleValueRootIndex);
|
||||
__ Push(x1, padreg, x4, x5);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(padreg, x1);
|
||||
__ Ldr(x4, FieldMemOperand(x1, JSGeneratorObject::kFunctionOffset));
|
||||
|
@ -608,6 +608,8 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(edx);
|
||||
__ Push(edi);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ PushRoot(Heap::kTheHoleValueRootIndex);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(edx);
|
||||
__ mov(edi, FieldOperand(edx, JSGeneratorObject::kFunctionOffset));
|
||||
|
@ -662,6 +662,8 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
{
|
||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(a1, t0);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ PushRoot(Heap::kTheHoleValueRootIndex);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(a1);
|
||||
}
|
||||
|
@ -551,6 +551,8 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
{
|
||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(a1, a4);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ PushRoot(Heap::kTheHoleValueRootIndex);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(a1);
|
||||
}
|
||||
|
@ -674,6 +674,8 @@ void Builtins::Generate_ResumeGeneratorTrampoline(MacroAssembler* masm) {
|
||||
FrameScope scope(masm, StackFrame::INTERNAL);
|
||||
__ Push(rdx);
|
||||
__ Push(rdi);
|
||||
// Push hole as receiver since we do not use it for stepping.
|
||||
__ PushRoot(Heap::kTheHoleValueRootIndex);
|
||||
__ CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
__ Pop(rdx);
|
||||
__ movp(rdi, FieldOperand(rdx, JSGeneratorObject::kFunctionOffset));
|
||||
|
@ -41,8 +41,8 @@ void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
|
||||
__ Pop(fp, lr); // Frame, Return address.
|
||||
|
||||
__ Ldr(x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
|
||||
__ Ldr(x0,
|
||||
FieldMemOperand(x0, SharedFunctionInfo::kFormalParameterCountOffset));
|
||||
__ Ldrsw(
|
||||
x0, FieldMemOperand(x0, SharedFunctionInfo::kFormalParameterCountOffset));
|
||||
__ mov(x2, x0);
|
||||
|
||||
ParameterCount dummy1(x2);
|
||||
|
@ -580,7 +580,8 @@ bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BuiltinHasNoSideEffect(Builtins::Name id) {
|
||||
SharedFunctionInfo::SideEffectState BuiltinGetSideEffectState(
|
||||
Builtins::Name id) {
|
||||
switch (id) {
|
||||
// Whitelist for builtins.
|
||||
// Object builtins.
|
||||
@ -852,13 +853,16 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
|
||||
case Builtins::kMakeSyntaxError:
|
||||
case Builtins::kMakeRangeError:
|
||||
case Builtins::kMakeURIError:
|
||||
return true;
|
||||
return SharedFunctionInfo::kHasNoSideEffect;
|
||||
case Builtins::kSetPrototypeAdd:
|
||||
case Builtins::kArrayIteratorPrototypeNext:
|
||||
return SharedFunctionInfo::kRequiresRuntimeChecks;
|
||||
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;
|
||||
return SharedFunctionInfo::kHasSideEffects;
|
||||
}
|
||||
}
|
||||
|
||||
@ -880,7 +884,7 @@ bool BytecodeRequiresRuntimeCheck(interpreter::Bytecode bytecode) {
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
SharedFunctionInfo::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
Handle<SharedFunctionInfo> info) {
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] Checking function %s for side effect.\n",
|
||||
@ -903,7 +907,7 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
? it.GetIntrinsicIdOperand(0)
|
||||
: it.GetRuntimeIdOperand(0);
|
||||
if (IntrinsicHasNoSideEffect(id)) continue;
|
||||
return kHasSideEffects;
|
||||
return SharedFunctionInfo::kHasSideEffects;
|
||||
}
|
||||
|
||||
if (BytecodeHasNoSideEffect(bytecode)) continue;
|
||||
@ -918,23 +922,27 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
}
|
||||
|
||||
// Did not match whitelist.
|
||||
return kHasSideEffects;
|
||||
return SharedFunctionInfo::kHasSideEffects;
|
||||
}
|
||||
return requires_runtime_checks ? kRequiresRuntimeChecks : kHasNoSideEffect;
|
||||
return requires_runtime_checks ? SharedFunctionInfo::kRequiresRuntimeChecks
|
||||
: SharedFunctionInfo::kHasNoSideEffect;
|
||||
} else if (info->IsApiFunction()) {
|
||||
if (info->GetCode()->is_builtin()) {
|
||||
return info->GetCode()->builtin_index() == Builtins::kHandleApiCall
|
||||
? kHasNoSideEffect
|
||||
: kHasSideEffects;
|
||||
? SharedFunctionInfo::kHasNoSideEffect
|
||||
: SharedFunctionInfo::kHasSideEffects;
|
||||
}
|
||||
} else {
|
||||
// Check built-ins against whitelist.
|
||||
int builtin_index =
|
||||
info->HasBuiltinId() ? info->builtin_id() : Builtins::kNoBuiltinId;
|
||||
DCHECK_NE(Builtins::kDeserializeLazy, builtin_index);
|
||||
if (Builtins::IsBuiltinId(builtin_index) &&
|
||||
BuiltinHasNoSideEffect(static_cast<Builtins::Name>(builtin_index))) {
|
||||
if (!Builtins::IsBuiltinId(builtin_index))
|
||||
return SharedFunctionInfo::kHasSideEffects;
|
||||
SharedFunctionInfo::SideEffectState state =
|
||||
BuiltinGetSideEffectState(static_cast<Builtins::Name>(builtin_index));
|
||||
#ifdef DEBUG
|
||||
if (state == SharedFunctionInfo::kHasNoSideEffect) {
|
||||
Isolate* isolate = info->GetIsolate();
|
||||
Code* code = isolate->builtins()->builtin(builtin_index);
|
||||
if (code->builtin_index() == Builtins::kDeserializeLazy) {
|
||||
@ -963,12 +971,12 @@ DebugEvaluate::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
|
||||
}
|
||||
DCHECK(!failed);
|
||||
}
|
||||
#endif // DEBUG
|
||||
return kHasNoSideEffect;
|
||||
}
|
||||
#endif // DEBUG
|
||||
return state;
|
||||
}
|
||||
|
||||
return kHasSideEffects;
|
||||
return SharedFunctionInfo::kHasSideEffects;
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "src/frames.h"
|
||||
#include "src/objects.h"
|
||||
#include "src/objects/shared-function-info.h"
|
||||
#include "src/objects/string-table.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -37,12 +38,7 @@ class DebugEvaluate : public AllStatic {
|
||||
static MaybeHandle<Object> WithTopmostArguments(Isolate* isolate,
|
||||
Handle<String> source);
|
||||
|
||||
enum SideEffectState {
|
||||
kHasSideEffects,
|
||||
kRequiresRuntimeChecks,
|
||||
kHasNoSideEffect
|
||||
};
|
||||
static SideEffectState FunctionGetSideEffectState(
|
||||
static SharedFunctionInfo::SideEffectState FunctionGetSideEffectState(
|
||||
Handle<SharedFunctionInfo> info);
|
||||
static bool CallbackHasNoSideEffect(Object* callback_info);
|
||||
static void ApplySideEffectChecks(Handle<BytecodeArray> bytecode_array);
|
||||
|
@ -2367,36 +2367,50 @@ void Debug::ClearSideEffectChecks(Handle<DebugInfo> debug_info) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) {
|
||||
bool Debug::PerformSideEffectCheck(Handle<JSFunction> function,
|
||||
Handle<Object> receiver) {
|
||||
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
|
||||
DisallowJavascriptExecution no_js(isolate_);
|
||||
if (!function->is_compiled() &&
|
||||
!Compiler::Compile(function, Compiler::KEEP_EXCEPTION)) {
|
||||
return false;
|
||||
}
|
||||
if (!SharedFunctionInfo::HasNoSideEffect(handle(function->shared()))) {
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] Function %s failed side effect check.\n",
|
||||
function->shared()->DebugName()->ToCString().get());
|
||||
SharedFunctionInfo::SideEffectState side_effect_state =
|
||||
SharedFunctionInfo::GetSideEffectState(handle(function->shared()));
|
||||
switch (side_effect_state) {
|
||||
case SharedFunctionInfo::kHasSideEffects:
|
||||
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;
|
||||
case SharedFunctionInfo::kRequiresRuntimeChecks: {
|
||||
Handle<SharedFunctionInfo> shared(function->shared());
|
||||
if (!shared->HasBytecodeArray()) {
|
||||
return PerformSideEffectCheckForObject(receiver);
|
||||
}
|
||||
// If function has bytecode array then prepare function for debug
|
||||
// execution to perform runtime side effect checks.
|
||||
DCHECK(shared->is_compiled());
|
||||
if (shared->GetCode() ==
|
||||
isolate_->builtins()->builtin(Builtins::kDeserializeLazy)) {
|
||||
Snapshot::EnsureBuiltinIsDeserialized(isolate_, shared);
|
||||
}
|
||||
GetOrCreateDebugInfo(shared);
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
return true;
|
||||
}
|
||||
side_effect_check_failed_ = true;
|
||||
// Throw an uncatchable termination exception.
|
||||
isolate_->TerminateExecution();
|
||||
return false;
|
||||
case SharedFunctionInfo::kHasNoSideEffect:
|
||||
return true;
|
||||
case SharedFunctionInfo::kNotComputed:
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
// If function has bytecode array then prepare function for debug execution
|
||||
// to perform runtime side effect checks.
|
||||
if (function->shared()->requires_runtime_side_effect_checks()) {
|
||||
Handle<SharedFunctionInfo> shared(function->shared());
|
||||
DCHECK(shared->is_compiled());
|
||||
if (shared->GetCode() ==
|
||||
isolate_->builtins()->builtin(Builtins::kDeserializeLazy)) {
|
||||
Snapshot::EnsureBuiltinIsDeserialized(isolate_, shared);
|
||||
}
|
||||
GetOrCreateDebugInfo(shared);
|
||||
PrepareFunctionForDebugExecution(shared);
|
||||
}
|
||||
return true;
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheckForCallback(Handle<Object> callback_info) {
|
||||
@ -2435,6 +2449,12 @@ bool Debug::PerformSideEffectCheckAtBytecode(InterpretedFrame* frame) {
|
||||
}
|
||||
Handle<Object> object =
|
||||
handle(frame->ReadInterpreterRegister(reg.index()), isolate_);
|
||||
return PerformSideEffectCheckForObject(object);
|
||||
}
|
||||
|
||||
bool Debug::PerformSideEffectCheckForObject(Handle<Object> object) {
|
||||
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
|
||||
|
||||
if (object->IsHeapObject()) {
|
||||
Address address = Handle<HeapObject>::cast(object)->address();
|
||||
if (temporary_objects_->HasObject(address)) {
|
||||
@ -2442,8 +2462,7 @@ bool Debug::PerformSideEffectCheckAtBytecode(InterpretedFrame* frame) {
|
||||
}
|
||||
}
|
||||
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
||||
PrintF("[debug-evaluate] %s failed runtime side effect check.\n",
|
||||
interpreter::Bytecodes::ToString(bytecode));
|
||||
PrintF("[debug-evaluate] failed runtime side effect check.\n");
|
||||
}
|
||||
side_effect_check_failed_ = true;
|
||||
// Throw an uncatchable termination exception.
|
||||
|
@ -345,7 +345,8 @@ class Debug {
|
||||
void ApplySideEffectChecks(Handle<DebugInfo> debug_info);
|
||||
void ClearSideEffectChecks(Handle<DebugInfo> debug_info);
|
||||
|
||||
bool PerformSideEffectCheck(Handle<JSFunction> function);
|
||||
bool PerformSideEffectCheck(Handle<JSFunction> function,
|
||||
Handle<Object> receiver);
|
||||
bool PerformSideEffectCheckForCallback(Handle<Object> callback_info);
|
||||
bool PerformSideEffectCheckAtBytecode(InterpretedFrame* frame);
|
||||
|
||||
@ -501,6 +502,7 @@ class Debug {
|
||||
void FindDebugInfo(Handle<DebugInfo> debug_info, DebugInfoListNode** prev,
|
||||
DebugInfoListNode** curr);
|
||||
void FreeDebugInfoListNode(DebugInfoListNode* prev, DebugInfoListNode* node);
|
||||
bool PerformSideEffectCheckForObject(Handle<Object> object);
|
||||
|
||||
// Global handles.
|
||||
Handle<Context> debug_context_;
|
||||
|
@ -1021,12 +1021,19 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
if (actual.is_reg()) {
|
||||
SmiTag(actual.reg());
|
||||
Push(actual.reg());
|
||||
SmiUntag(actual.reg());
|
||||
}
|
||||
if (new_target.is_valid()) {
|
||||
Push(new_target);
|
||||
}
|
||||
Push(fun);
|
||||
Push(fun);
|
||||
Operand receiver_op =
|
||||
actual.is_reg()
|
||||
? Operand(ebp, actual.reg(), times_pointer_size, kPointerSize * 2)
|
||||
: Operand(ebp, actual.immediate() * times_pointer_size +
|
||||
kPointerSize * 2);
|
||||
Push(receiver_op);
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
Pop(fun);
|
||||
if (new_target.is_valid()) {
|
||||
|
@ -4126,6 +4126,14 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
Branch(&skip_hook, eq, t0, Operand(zero_reg));
|
||||
|
||||
{
|
||||
// Load receiver to pass it later to DebugOnFunctionCall hook.
|
||||
if (actual.is_reg()) {
|
||||
mov(t0, actual.reg());
|
||||
} else {
|
||||
li(t0, actual.immediate());
|
||||
}
|
||||
Lsa(at, sp, t0, kPointerSizeLog2);
|
||||
lw(t0, MemOperand(at));
|
||||
FrameScope frame(this,
|
||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||
if (expected.is_reg()) {
|
||||
@ -4141,6 +4149,7 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
}
|
||||
Push(fun);
|
||||
Push(fun);
|
||||
Push(t0);
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
Pop(fun);
|
||||
if (new_target.is_valid()) {
|
||||
|
@ -4454,6 +4454,14 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
Branch(&skip_hook, eq, t0, Operand(zero_reg));
|
||||
|
||||
{
|
||||
// Load receiver to pass it later to DebugOnFunctionCall hook.
|
||||
if (actual.is_reg()) {
|
||||
mov(t0, actual.reg());
|
||||
} else {
|
||||
li(t0, actual.immediate());
|
||||
}
|
||||
Dlsa(t0, sp, t0, kPointerSizeLog2);
|
||||
Ld(t0, MemOperand(t0));
|
||||
FrameScope frame(this,
|
||||
has_frame() ? StackFrame::NONE : StackFrame::INTERNAL);
|
||||
if (expected.is_reg()) {
|
||||
@ -4469,6 +4477,7 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
}
|
||||
Push(fun);
|
||||
Push(fun);
|
||||
Push(t0);
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
Pop(fun);
|
||||
if (new_target.is_valid()) {
|
||||
|
@ -13644,17 +13644,14 @@ String* SharedFunctionInfo::DebugName() {
|
||||
}
|
||||
|
||||
// static
|
||||
bool SharedFunctionInfo::HasNoSideEffect(Handle<SharedFunctionInfo> info) {
|
||||
if (!info->computed_has_no_side_effect()) {
|
||||
DebugEvaluate::SideEffectState has_no_side_effect =
|
||||
SharedFunctionInfo::SideEffectState SharedFunctionInfo::GetSideEffectState(
|
||||
Handle<SharedFunctionInfo> info) {
|
||||
if (info->side_effect_state() == kNotComputed) {
|
||||
SharedFunctionInfo::SideEffectState has_no_side_effect =
|
||||
DebugEvaluate::FunctionGetSideEffectState(info);
|
||||
info->set_has_no_side_effect(has_no_side_effect !=
|
||||
DebugEvaluate::kHasSideEffects);
|
||||
info->set_requires_runtime_side_effect_checks(
|
||||
has_no_side_effect == DebugEvaluate::kRequiresRuntimeChecks);
|
||||
info->set_computed_has_no_side_effect(true);
|
||||
info->set_side_effect_state(has_no_side_effect);
|
||||
}
|
||||
return info->has_no_side_effect();
|
||||
return static_cast<SideEffectState>(info->side_effect_state());
|
||||
}
|
||||
|
||||
// The filter is a pattern that matches function names in this way:
|
||||
|
@ -217,14 +217,8 @@ BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, is_anonymous_expression,
|
||||
SharedFunctionInfo::IsAnonymousExpressionBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, deserialized,
|
||||
SharedFunctionInfo::IsDeserializedBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, has_no_side_effect,
|
||||
SharedFunctionInfo::HasNoSideEffectBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints,
|
||||
requires_runtime_side_effect_checks,
|
||||
SharedFunctionInfo::RequiresRuntimeSideEffectChecksBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints,
|
||||
computed_has_no_side_effect,
|
||||
SharedFunctionInfo::ComputedHasNoSideEffectBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, side_effect_state,
|
||||
SharedFunctionInfo::SideEffectStateBits)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints, debug_is_blackboxed,
|
||||
SharedFunctionInfo::DebugIsBlackboxedBit)
|
||||
BIT_FIELD_ACCESSORS(SharedFunctionInfo, debugger_hints,
|
||||
|
@ -256,15 +256,6 @@ class SharedFunctionInfo : public HeapObject {
|
||||
// Indicates that the the shared function info is deserialized from cache.
|
||||
DECL_BOOLEAN_ACCESSORS(deserialized)
|
||||
|
||||
// Indicates that the function cannot cause side-effects.
|
||||
DECL_BOOLEAN_ACCESSORS(has_no_side_effect)
|
||||
|
||||
// Indicates that the function requires runtime side-effect checks.
|
||||
DECL_BOOLEAN_ACCESSORS(requires_runtime_side_effect_checks);
|
||||
|
||||
// Indicates that |has_no_side_effect| has been computed and set.
|
||||
DECL_BOOLEAN_ACCESSORS(computed_has_no_side_effect)
|
||||
|
||||
// Indicates that the function should be skipped during stepping.
|
||||
DECL_BOOLEAN_ACCESSORS(debug_is_blackboxed)
|
||||
|
||||
@ -282,8 +273,13 @@ 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.
|
||||
static bool HasNoSideEffect(Handle<SharedFunctionInfo> info);
|
||||
enum SideEffectState {
|
||||
kNotComputed = 0,
|
||||
kHasSideEffects = 1,
|
||||
kRequiresRuntimeChecks = 2,
|
||||
kHasNoSideEffect = 3,
|
||||
};
|
||||
static SideEffectState GetSideEffectState(Handle<SharedFunctionInfo> info);
|
||||
|
||||
// Used for flags such as --turbo-filter.
|
||||
bool PassesFilter(const char* raw_filter);
|
||||
@ -549,16 +545,14 @@ class SharedFunctionInfo : public HeapObject {
|
||||
STATIC_ASSERT(kLastFunctionKind <= FunctionKindBits::kMax);
|
||||
|
||||
// Bit positions in |debugger_hints|.
|
||||
#define DEBUGGER_HINTS_BIT_FIELDS(V, _) \
|
||||
V(IsAnonymousExpressionBit, bool, 1, _) \
|
||||
V(NameShouldPrintAsAnonymousBit, bool, 1, _) \
|
||||
V(IsDeserializedBit, bool, 1, _) \
|
||||
V(HasNoSideEffectBit, bool, 1, _) \
|
||||
V(RequiresRuntimeSideEffectChecksBit, bool, 1, _) \
|
||||
V(ComputedHasNoSideEffectBit, bool, 1, _) \
|
||||
V(DebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(ComputedDebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(HasReportedBinaryCoverageBit, bool, 1, _) \
|
||||
#define DEBUGGER_HINTS_BIT_FIELDS(V, _) \
|
||||
V(IsAnonymousExpressionBit, bool, 1, _) \
|
||||
V(NameShouldPrintAsAnonymousBit, bool, 1, _) \
|
||||
V(IsDeserializedBit, bool, 1, _) \
|
||||
V(SideEffectStateBits, int, 2, _) \
|
||||
V(DebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(ComputedDebugIsBlackboxedBit, bool, 1, _) \
|
||||
V(HasReportedBinaryCoverageBit, bool, 1, _) \
|
||||
V(DebuggingIdBits, int, 20, _)
|
||||
|
||||
DEFINE_BIT_FIELDS(DEBUGGER_HINTS_BIT_FIELDS)
|
||||
@ -580,6 +574,9 @@ class SharedFunctionInfo : public HeapObject {
|
||||
// function.
|
||||
DECL_ACCESSORS(outer_scope_info, HeapObject)
|
||||
|
||||
inline int side_effect_state() const;
|
||||
inline void set_side_effect_state(int value);
|
||||
|
||||
inline void set_kind(FunctionKind kind);
|
||||
|
||||
inline void set_needs_home_object(bool value);
|
||||
|
@ -1664,16 +1664,18 @@ RUNTIME_FUNCTION(Runtime_ScriptPositionInfo2) {
|
||||
// or perform a side effect check.
|
||||
RUNTIME_FUNCTION(Runtime_DebugOnFunctionCall) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
DCHECK_EQ(2, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 1);
|
||||
if (isolate->debug()->needs_check_on_function_call()) {
|
||||
// Ensure that the callee will perform debug check on function call too.
|
||||
Deoptimizer::DeoptimizeFunction(*fun);
|
||||
if (isolate->debug()->last_step_action() >= StepIn) {
|
||||
DCHECK_EQ(isolate->debug_execution_mode(), DebugInfo::kBreakpoints);
|
||||
isolate->debug()->PrepareStepIn(fun);
|
||||
}
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects &&
|
||||
!isolate->debug()->PerformSideEffectCheck(fun)) {
|
||||
!isolate->debug()->PerformSideEffectCheck(fun, receiver)) {
|
||||
return isolate->heap()->exception();
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ namespace internal {
|
||||
F(DebugGetPropertyDetails, 2, 1) \
|
||||
F(DebugGetPrototype, 1, 1) \
|
||||
F(DebugIsActive, 0, 1) \
|
||||
F(DebugOnFunctionCall, 1, 1) \
|
||||
F(DebugOnFunctionCall, 2, 1) \
|
||||
F(DebugPopPromise, 0, 1) \
|
||||
F(DebugPrepareStepInSuspendedGenerator, 0, 1) \
|
||||
F(DebugPropertyAttributesFromDetails, 1, 1) \
|
||||
|
@ -2142,12 +2142,14 @@ void MacroAssembler::CheckDebugHook(Register fun, Register new_target,
|
||||
if (actual.is_reg()) {
|
||||
Integer32ToSmi(actual.reg(), actual.reg());
|
||||
Push(actual.reg());
|
||||
SmiToInteger64(actual.reg(), actual.reg());
|
||||
}
|
||||
if (new_target.is_valid()) {
|
||||
Push(new_target);
|
||||
}
|
||||
Push(fun);
|
||||
Push(fun);
|
||||
Push(StackArgumentsAccessor(rbp, actual).GetReceiverOperand());
|
||||
CallRuntime(Runtime::kDebugOnFunctionCall);
|
||||
Pop(fun);
|
||||
if (new_target.is_valid()) {
|
||||
|
@ -6680,7 +6680,8 @@ TEST(DebugEvaluateNoSideEffect) {
|
||||
for (i::Handle<i::JSFunction> fun : all_functions) {
|
||||
bool failed = false;
|
||||
isolate->debug()->StartSideEffectCheckMode();
|
||||
failed = !isolate->debug()->PerformSideEffectCheck(fun);
|
||||
failed = !isolate->debug()->PerformSideEffectCheck(
|
||||
fun, v8::Utils::OpenHandle(*env->Global()));
|
||||
isolate->debug()->StopSideEffectCheckMode();
|
||||
if (failed) isolate->clear_pending_exception();
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ function listener(event, exec_state, event_data, data) {
|
||||
success(undefined, `set.forEach(()=>1)`);
|
||||
success(true, `set.has(1)`);
|
||||
success(2, `set.size`);
|
||||
fail(`new Set([1])`);
|
||||
success(1, `new Set([1]).size`);
|
||||
fail(`set.add(2)`);
|
||||
fail(`set.delete(1)`);
|
||||
fail(`set.clear()`);
|
||||
|
@ -64,14 +64,16 @@ var successes = [
|
||||
}
|
||||
})()`
|
||||
],
|
||||
[6,
|
||||
`(function() { // Iterator.prototype.next performs stores.
|
||||
var sum = 0;
|
||||
for (let i of [1, 2, 3]) sum += i;
|
||||
return sum;
|
||||
})()`
|
||||
]
|
||||
];
|
||||
|
||||
var fails = [
|
||||
`(function() { // Iterator.prototype.next performs stores.
|
||||
var sum = 0;
|
||||
for (let i of [1, 2, 3]) sum += i;
|
||||
return sum;
|
||||
})()`,
|
||||
`(function() { // Store to scope object.
|
||||
with (o) {
|
||||
p = 2;
|
||||
|
@ -71,13 +71,13 @@ function listener(event, exec_state, event_data, data) {
|
||||
success(NaN, `string * two`);
|
||||
success("s2", `string + two`);
|
||||
success("s2", `string + two`);
|
||||
fail(`[...array]`);
|
||||
success([1,2,3], `[...array]`);
|
||||
success(3, `max(...array)`);
|
||||
success({s:1}, `({[string]:1})`);
|
||||
fail(`[a, b] = [1, 2]`);
|
||||
success(2, `def(2)`);
|
||||
success(1, `def()`);
|
||||
fail(`d1(['a'])`); // Iterator.prototype.next performs stores.
|
||||
success('ab', `d1(['a'])`);
|
||||
success("XYz", `d2({x:'X', y:'Y'})`);
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
|
@ -45,7 +45,7 @@ function return_array_use_spread(a) {
|
||||
return [...a];
|
||||
}
|
||||
|
||||
fail(`return_array_use_spread([1])`);
|
||||
success([1], `return_array_use_spread([1])`);
|
||||
|
||||
// CallAccessorSetter
|
||||
var array = [1,2,3];
|
||||
@ -60,6 +60,33 @@ function return_literal_with_data_property(a) {
|
||||
|
||||
success({foo: 1}, `return_literal_with_data_property('foo')`);
|
||||
|
||||
// Array builtins with temporary objects
|
||||
|
||||
success(6, `(() => {
|
||||
let s = 0;
|
||||
for (const a of [1,2,3])
|
||||
s += a;
|
||||
return s;
|
||||
})()`);
|
||||
success(6, `(() => {
|
||||
let s = 0;
|
||||
for (const a of array)
|
||||
s += a;
|
||||
return s;
|
||||
})()`);
|
||||
var arrayIterator = array.entries();
|
||||
fail(`(() => {
|
||||
let s = 0;
|
||||
for (const a of arrayIterator)
|
||||
s += a;
|
||||
return s;
|
||||
})()`);
|
||||
|
||||
// SetAdd builtin on temporary object
|
||||
var set = new Set([1,2]);
|
||||
fail(`set.add(3).size`);
|
||||
success(1, `new Set().add(1).size`);
|
||||
|
||||
function success(expectation, source) {
|
||||
const result = Debug.evaluateGlobal(source, true).value();
|
||||
if (expectation !== undefined) assertEquals(expectation, result);
|
||||
|
@ -1,38 +1,38 @@
|
||||
Tests Debugger.setScriptSource
|
||||
|
||||
Running test: addLineAfter
|
||||
var x = 1;
|
||||
var x = a;
|
||||
#debugger;
|
||||
return x + 2;
|
||||
return x + b;
|
||||
|
||||
---
|
||||
Break location after LiveEdit:
|
||||
var x = 1;
|
||||
var x = a;
|
||||
#debugger;
|
||||
var x = 3;
|
||||
|
||||
stackChanged: true
|
||||
Protocol.Debugger.stepInto
|
||||
function foo() {
|
||||
var x = #1;
|
||||
function foo(a,b,c) {
|
||||
var x = #a;
|
||||
debugger;
|
||||
|
||||
|
||||
Running test: addLineBefore
|
||||
var x = 1;
|
||||
var x = a;
|
||||
#debugger;
|
||||
return x + 2;
|
||||
return x + b;
|
||||
|
||||
---
|
||||
Break location after LiveEdit:
|
||||
var x = 1;
|
||||
var x = a;
|
||||
var x = #3;
|
||||
debugger;
|
||||
|
||||
stackChanged: true
|
||||
Protocol.Debugger.stepInto
|
||||
function foo() {
|
||||
var x = #1;
|
||||
function foo(a,b,c) {
|
||||
var x = #a;
|
||||
var x = 3;
|
||||
|
||||
|
||||
|
@ -7,10 +7,10 @@ let {session, contextGroup, Protocol} =
|
||||
|
||||
session.setupScriptMap();
|
||||
|
||||
function foo() {
|
||||
var x = 1;
|
||||
function foo(a,b,c) {
|
||||
var x = a;
|
||||
debugger;
|
||||
return x + 2;
|
||||
return x + b;
|
||||
}
|
||||
|
||||
function boo() {
|
||||
@ -25,7 +25,7 @@ InspectorTest.runAsyncTestSuite([
|
||||
Protocol.Runtime.evaluate({expression: foo.toString()});
|
||||
let {params:{scriptId}} = await Protocol.Debugger.onceScriptParsed();
|
||||
Protocol.Runtime.evaluate({
|
||||
expression: 'setTimeout(foo, 0)//# sourceURL=test.js'});
|
||||
expression: 'setTimeout(() => foo(1,2,3), 0)//# sourceURL=test.js'});
|
||||
let {params:{callFrames}} = await Protocol.Debugger.oncePaused();
|
||||
await session.logSourceLocation(callFrames[0].location);
|
||||
await replaceInSource(scriptId, 'debugger;', 'debugger;\nvar x = 3;');
|
||||
|
@ -70,4 +70,4 @@ Test that debug break does not trigger with throwOnSideEffect
|
||||
value : 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user