blob: ebb79d43f4c703b12d81d07d8129d4794f19d2e1 [file] [log] [blame]
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include "src/init/v8.h"
#include "src/api/api-inl.h"
#include "src/codegen/compilation-cache.h"
#include "src/debug/debug-interface.h"
#include "src/debug/debug.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/frames.h"
#include "src/objects/objects-inl.h"
#include "src/snapshot/natives.h"
#include "src/snapshot/snapshot.h"
#include "src/utils/utils.h"
#include "test/cctest/cctest.h"
using ::v8::internal::Handle;
using ::v8::internal::StepNone; // From StepAction enum
using ::v8::internal::StepIn; // From StepAction enum
using ::v8::internal::StepNext; // From StepAction enum
using ::v8::internal::StepOut; // From StepAction enum
// --- H e l p e r F u n c t i o n s
// Compile and run the supplied source and return the requested function.
static v8::Local<v8::Function> CompileFunction(v8::Isolate* isolate,
const char* source,
const char* function_name) {
CompileRunChecked(isolate, source);
v8::Local<v8::String> name = v8_str(isolate, function_name);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::MaybeLocal<v8::Value> maybe_function =
context->Global()->Get(context, name);
return v8::Local<v8::Function>::Cast(maybe_function.ToLocalChecked());
}
// Compile and run the supplied source and return the requested function.
static v8::Local<v8::Function> CompileFunction(LocalContext* env,
const char* source,
const char* function_name) {
return CompileFunction((*env)->GetIsolate(), source, function_name);
}
// Is there any debug info for the function?
static bool HasBreakInfo(v8::Local<v8::Function> fun) {
Handle<v8::internal::JSFunction> f =
Handle<v8::internal::JSFunction>::cast(v8::Utils::OpenHandle(*fun));
Handle<v8::internal::SharedFunctionInfo> shared(f->shared(), f->GetIsolate());
return shared->HasBreakInfo();
}
// Set a break point in a function with a position relative to function start,
// and return the associated break point number.
static i::Handle<i::BreakPoint> SetBreakPoint(v8::Local<v8::Function> fun,
int position,
const char* condition = nullptr) {
i::Handle<i::JSFunction> function =
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*fun));
position += function->shared().StartPosition();
static int break_point_index = 0;
i::Isolate* isolate = function->GetIsolate();
i::Handle<i::String> condition_string =
condition ? isolate->factory()->NewStringFromAsciiChecked(condition)
: isolate->factory()->empty_string();
i::Debug* debug = isolate->debug();
i::Handle<i::BreakPoint> break_point =
isolate->factory()->NewBreakPoint(++break_point_index, condition_string);
debug->SetBreakpoint(handle(function->shared(), isolate), break_point,
&position);
return break_point;
}
static void ClearBreakPoint(i::Handle<i::BreakPoint> break_point) {
v8::internal::Isolate* isolate = CcTest::i_isolate();
v8::internal::Debug* debug = isolate->debug();
debug->ClearBreakPoint(break_point);
}
// Change break on exception.
static void ChangeBreakOnException(bool caught, bool uncaught) {
v8::internal::Debug* debug = CcTest::i_isolate()->debug();
debug->ChangeBreakOnException(v8::internal::BreakException, caught);
debug->ChangeBreakOnException(v8::internal::BreakUncaughtException, uncaught);
}
// Prepare to step to next break location.
static void PrepareStep(i::StepAction step_action) {
v8::internal::Debug* debug = CcTest::i_isolate()->debug();
debug->PrepareStep(step_action);
}
// This function is in namespace v8::internal to be friend with class
// v8::internal::Debug.
namespace v8 {
namespace internal {
// Collect the currently debugged functions.
Handle<FixedArray> GetDebuggedFunctions() {
Debug* debug = CcTest::i_isolate()->debug();
v8::internal::DebugInfoListNode* node = debug->debug_info_list_;
// Find the number of debugged functions.
int count = 0;
while (node) {
count++;
node = node->next();
}
// Allocate array for the debugged functions
Handle<FixedArray> debugged_functions =
CcTest::i_isolate()->factory()->NewFixedArray(count);
// Run through the debug info objects and collect all functions.
count = 0;
while (node) {
debugged_functions->set(count++, *node->debug_info());
node = node->next();
}
return debugged_functions;
}
// Check that the debugger has been fully unloaded.
void CheckDebuggerUnloaded() {
// Check that the debugger context is cleared and that there is no debug
// information stored for the debugger.
CHECK(!CcTest::i_isolate()->debug()->debug_info_list_);
// Collect garbage to ensure weak handles are cleared.
CcTest::CollectAllGarbage();
CcTest::CollectAllGarbage();
// Iterate the heap and check that there are no debugger related objects left.
HeapIterator iterator(CcTest::heap());
for (HeapObject obj = iterator.next(); !obj.is_null();
obj = iterator.next()) {
CHECK(!obj.IsDebugInfo());
}
}
} // namespace internal
} // namespace v8
// Check that the debugger has been fully unloaded.
static void CheckDebuggerUnloaded() { v8::internal::CheckDebuggerUnloaded(); }
// --- D e b u g E v e n t H a n d l e r s
// ---
// --- The different tests uses a number of debug event handlers.
// ---
// Debug event handler which counts a number of events.
int break_point_hit_count = 0;
int break_point_hit_count_deoptimize = 0;
class DebugEventCounter : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(
v8::Local<v8::Context>,
const std::vector<v8::debug::BreakpointId>&) override {
break_point_hit_count++;
// Perform a full deoptimization when the specified number of
// breaks have been hit.
if (break_point_hit_count == break_point_hit_count_deoptimize) {
i::Deoptimizer::DeoptimizeAll(CcTest::i_isolate());
}
if (step_action_ != StepNone) PrepareStep(step_action_);
}
void set_step_action(i::StepAction step_action) {
step_action_ = step_action;
}
private:
i::StepAction step_action_ = StepNone;
};
// Debug event handler which performs a garbage collection.
class DebugEventBreakPointCollectGarbage : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(
v8::Local<v8::Context>,
const std::vector<v8::debug::BreakpointId>&) override {
// Perform a garbage collection when break point is hit and continue. Based
// on the number of break points hit either scavenge or mark compact
// collector is used.
break_point_hit_count++;
if (break_point_hit_count % 2 == 0) {
// Scavenge.
CcTest::CollectGarbage(v8::internal::NEW_SPACE);
} else {
// Mark sweep compact.
CcTest::CollectAllGarbage();
}
}
};
// Debug event handler which re-issues a debug break and calls the garbage
// collector to have the heap verified.
class DebugEventBreak : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(
v8::Local<v8::Context>,
const std::vector<v8::debug::BreakpointId>&) override {
// Count the number of breaks.
break_point_hit_count++;
// Run the garbage collector to enforce heap verification if option
// --verify-heap is set.
CcTest::CollectGarbage(v8::internal::NEW_SPACE);
// Set the break flag again to come back here as soon as possible.
v8::debug::SetBreakOnNextFunctionCall(CcTest::isolate());
}
};
static void BreakRightNow(v8::Isolate* isolate, void*) {
v8::debug::BreakRightNow(isolate);
}
// Debug event handler which re-issues a debug break until a limit has been
// reached.
int max_break_point_hit_count = 0;
bool terminate_after_max_break_point_hit = false;
class DebugEventBreakMax : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(
v8::Local<v8::Context>,
const std::vector<v8::debug::BreakpointId>&) override {
v8::Isolate* v8_isolate = CcTest::isolate();
v8::internal::Isolate* isolate = CcTest::i_isolate();
if (break_point_hit_count < max_break_point_hit_count) {
// Count the number of breaks.
break_point_hit_count++;
// Set the break flag again to come back here as soon as possible.
v8_isolate->RequestInterrupt(BreakRightNow, nullptr);
} else if (terminate_after_max_break_point_hit) {
// Terminate execution after the last break if requested.
v8_isolate->TerminateExecution();
}
// Perform a full deoptimization when the specified number of
// breaks have been hit.
if (break_point_hit_count == break_point_hit_count_deoptimize) {
i::Deoptimizer::DeoptimizeAll(isolate);
}
}
};
// --- T h e A c t u a l T e s t s
// Test that the debug info in the VM is in sync with the functions being
// debugged.
TEST(DebugInfo) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Create a couple of functions for the test.
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){}", "foo");
v8::Local<v8::Function> bar =
CompileFunction(&env, "function bar(){}", "bar");
// Initially no functions are debugged.
CHECK_EQ(0, v8::internal::GetDebuggedFunctions()->length());
CHECK(!HasBreakInfo(foo));
CHECK(!HasBreakInfo(bar));
EnableDebugger(env->GetIsolate());
// One function (foo) is debugged.
i::Handle<i::BreakPoint> bp1 = SetBreakPoint(foo, 0);
CHECK_EQ(1, v8::internal::GetDebuggedFunctions()->length());
CHECK(HasBreakInfo(foo));
CHECK(!HasBreakInfo(bar));
// Two functions are debugged.
i::Handle<i::BreakPoint> bp2 = SetBreakPoint(bar, 0);
CHECK_EQ(2, v8::internal::GetDebuggedFunctions()->length());
CHECK(HasBreakInfo(foo));
CHECK(HasBreakInfo(bar));
// One function (bar) is debugged.
ClearBreakPoint(bp1);
CHECK_EQ(1, v8::internal::GetDebuggedFunctions()->length());
CHECK(!HasBreakInfo(foo));
CHECK(HasBreakInfo(bar));
// No functions are debugged.
ClearBreakPoint(bp2);
DisableDebugger(env->GetIsolate());
CHECK_EQ(0, v8::internal::GetDebuggedFunctions()->length());
CHECK(!HasBreakInfo(foo));
CHECK(!HasBreakInfo(bar));
}
// Test that a break point can be set at an IC store location.
TEST(BreakPointICStore) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){bar=0;}", "foo");
// Run without breakpoints.
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at an IC store location.
TEST(BreakPointCondition) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRun("var a = false");
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo() { return 1 }", "foo");
// Run without breakpoints.
CompileRun("foo()");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0, "a == true");
CompileRun("foo()");
CHECK_EQ(0, break_point_hit_count);
CompileRun("a = true");
CompileRun("foo()");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("foo()");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at an IC load location.
TEST(BreakPointICLoad) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRunChecked(env->GetIsolate(), "bar=1");
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){var x=bar;}", "foo");
// Run without breakpoints.
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at an IC call location.
TEST(BreakPointICCall) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRunChecked(env->GetIsolate(), "function bar(){}");
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){bar();}", "foo");
// Run without breakpoints.
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
foo->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at an IC call location and survive a GC.
TEST(BreakPointICCallWithGC) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventBreakPointCollectGarbage delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRunChecked(env->GetIsolate(), "function bar(){return 1;}");
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){return bar();}", "foo");
v8::Local<v8::Context> context = env.local();
// Run without breakpoints.
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(1, break_point_hit_count);
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at an IC call location and survive a GC.
TEST(BreakPointConstructCallWithGC) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventBreakPointCollectGarbage delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRunChecked(env->GetIsolate(), "function bar(){ this.x = 1;}");
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){return new bar(1).x;}", "foo");
v8::Local<v8::Context> context = env.local();
// Run without breakpoints.
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(1, break_point_hit_count);
CHECK_EQ(1, foo->Call(context, env->Global(), 0, nullptr)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointBuiltin) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test simple builtin ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
ExpectString("'b'.repeat(10)", "bbbbbbbbbb");
CHECK_EQ(1, break_point_hit_count);
ExpectString("'b'.repeat(10)", "bbbbbbbbbb");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("'b'.repeat(10)", "bbbbbbbbbb");
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointJSBuiltin) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test JS builtin ===
break_point_hit_count = 0;
builtin = CompileRun("Array.prototype.sort").As<v8::Function>();
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("[1,2,3].sort()");
CHECK_EQ(1, break_point_hit_count);
CompileRun("[1,2,3].sort()");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("[1,2,3].sort()");
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointBoundBuiltin) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test bound function from a builtin ===
break_point_hit_count = 0;
builtin = CompileRun(
"var boundrepeat = String.prototype.repeat.bind('a');"
"String.prototype.repeat")
.As<v8::Function>();
ExpectString("boundrepeat(10)", "aaaaaaaaaa");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
ExpectString("boundrepeat(10)", "aaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("boundrepeat(10)", "aaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointConstructorBuiltin) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test Promise constructor ===
break_point_hit_count = 0;
builtin = CompileRun("Promise").As<v8::Function>();
ExpectString("(new Promise(()=>{})).toString()", "[object Promise]");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
ExpectString("(new Promise(()=>{})).toString()", "[object Promise]");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("(new Promise(()=>{})).toString()", "[object Promise]");
CHECK_EQ(1, break_point_hit_count);
// === Test Object constructor ===
break_point_hit_count = 0;
builtin = CompileRun("Object").As<v8::Function>();
CompileRun("new Object()");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("new Object()");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("new Object()");
CHECK_EQ(1, break_point_hit_count);
// === Test Number constructor ===
break_point_hit_count = 0;
builtin = CompileRun("Number").As<v8::Function>();
CompileRun("new Number()");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("new Number()");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("new Number()");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointInlinedBuiltin) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test inlined builtin ===
break_point_hit_count = 0;
builtin = CompileRun("Math.sin").As<v8::Function>();
CompileRun("function test(x) { return 1 + Math.sin(x) }");
CompileRun(
"%PrepareFunctionForOptimization(test);"
"test(0.5); test(0.6);"
"%OptimizeFunctionOnNextCall(test); test(0.7);");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("Math.sin(0.1);");
CHECK_EQ(1, break_point_hit_count);
CompileRun("test(0.2);");
CHECK_EQ(2, break_point_hit_count);
// Re-optimize.
CompileRun(
"%PrepareFunctionForOptimization(test);"
"%OptimizeFunctionOnNextCall(test);");
ExpectBoolean("test(0.3) < 2", true);
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test(0.3);");
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointInlineBoundBuiltin) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test inlined bound builtin ===
break_point_hit_count = 0;
builtin = CompileRun(
"var boundrepeat = String.prototype.repeat.bind('a');"
"String.prototype.repeat")
.As<v8::Function>();
CompileRun("function test(x) { return 'a' + boundrepeat(x) }");
CompileRun(
"%PrepareFunctionForOptimization(test);"
"test(4); test(5);"
"%OptimizeFunctionOnNextCall(test); test(6);");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("'a'.repeat(2);");
CHECK_EQ(1, break_point_hit_count);
CompileRun("test(7);");
CHECK_EQ(2, break_point_hit_count);
// Re-optimize.
CompileRun(
"%PrepareFunctionForOptimization(f);"
"%OptimizeFunctionOnNextCall(test);");
CompileRun("test(8);");
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test(9);");
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointInlinedConstructorBuiltin) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_experimental_inline_promise_constructor = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test inlined constructor builtin (regular construct builtin) ===
break_point_hit_count = 0;
builtin = CompileRun("Promise").As<v8::Function>();
CompileRun("function test(x) { return new Promise(()=>x); }");
CompileRun(
"%PrepareFunctionForOptimization(test);"
"test(4); test(5);"
"%OptimizeFunctionOnNextCall(test); test(6);");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("new Promise(()=>{});");
CHECK_EQ(1, break_point_hit_count);
CompileRun("test(7);");
CHECK_EQ(2, break_point_hit_count);
// Re-optimize.
CompileRun(
"%PrepareFunctionForOptimization(f);"
"%OptimizeFunctionOnNextCall(test);");
CompileRun("test(8);");
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test(9);");
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointBuiltinConcurrentOpt) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_block_concurrent_recompilation = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test concurrent optimization ===
break_point_hit_count = 0;
builtin = CompileRun("Math.sin").As<v8::Function>();
CompileRun("function test(x) { return 1 + Math.sin(x) }");
// Trigger concurrent compile job. It is suspended until unblock.
CompileRun(
"%PrepareFunctionForOptimization(test);"
"test(0.5); test(0.6);"
"%OptimizeFunctionOnNextCall(test, 'concurrent'); test(0.7);");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
// Have the concurrent compile job finish now.
CompileRun(
"%UnblockConcurrentRecompilation();"
"%GetOptimizationStatus(test, 'sync');");
CompileRun("test(0.2);");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test(0.3);");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointBuiltinTFOperator) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test builtin represented as operator ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.indexOf").As<v8::Function>();
CompileRun("function test(x) { return 1 + 'foo'.indexOf(x) }");
CompileRun(
"%PrepareFunctionForOptimization(f);"
"test('a'); test('b');"
"%OptimizeFunctionOnNextCall(test); test('c');");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0);
CompileRun("'bar'.indexOf('x');");
CHECK_EQ(1, break_point_hit_count);
CompileRun("test('d');");
CHECK_EQ(2, break_point_hit_count);
// Re-optimize.
CompileRun(
"%PrepareFunctionForOptimization(f);"
"%OptimizeFunctionOnNextCall(test);");
CompileRun("test('e');");
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test('f');");
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointBuiltinNewContext) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test builtin from a new context ===
// This does not work with no-snapshot build.
#ifdef V8_USE_SNAPSHOT
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
CompileRun("'a'.repeat(10)");
CHECK_EQ(0, break_point_hit_count);
// Set breakpoint.
bp = SetBreakPoint(builtin, 0);
{
// Create and use new context after breakpoint has been set.
v8::HandleScope handle_scope(env->GetIsolate());
v8::Local<v8::Context> new_context = v8::Context::New(env->GetIsolate());
v8::Context::Scope context_scope(new_context);
// Run with breakpoint.
CompileRun("'b'.repeat(10)");
CHECK_EQ(1, break_point_hit_count);
CompileRun("'b'.repeat(10)");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("'b'.repeat(10)");
CHECK_EQ(2, break_point_hit_count);
}
#endif
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
void NoOpFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(v8_num(2));
}
TEST(BreakPointApiFunction) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
i::Handle<i::BreakPoint> bp;
v8::Local<v8::FunctionTemplate> function_template =
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
v8::Local<v8::Function> function =
function_template->GetFunction(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("f"), function).ToChecked();
// === Test simple builtin ===
break_point_hit_count = 0;
// Run with breakpoint.
bp = SetBreakPoint(function, 0);
ExpectInt32("f()", 2);
CHECK_EQ(1, break_point_hit_count);
ExpectInt32("f()", 2);
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectInt32("f()", 2);
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
void GetWrapperCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(
args[0]
.As<v8::Object>()
->Get(args.GetIsolate()->GetCurrentContext(), args[1])
.ToLocalChecked());
}
TEST(BreakPointApiGetter) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
i::Handle<i::BreakPoint> bp;
v8::Local<v8::FunctionTemplate> function_template =
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
v8::Local<v8::FunctionTemplate> get_template =
v8::FunctionTemplate::New(env->GetIsolate(), GetWrapperCallback);
v8::Local<v8::Function> function =
function_template->GetFunction(env.local()).ToLocalChecked();
v8::Local<v8::Function> get =
get_template->GetFunction(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("f"), function).ToChecked();
env->Global()->Set(env.local(), v8_str("get_wrapper"), get).ToChecked();
CompileRun(
"var o = {};"
"Object.defineProperty(o, 'f', { get: f, enumerable: true });");
// === Test API builtin as getter ===
break_point_hit_count = 0;
// Run with breakpoint.
bp = SetBreakPoint(function, 0);
CompileRun("get_wrapper(o, 'f')");
CHECK_EQ(1, break_point_hit_count);
CompileRun("o.f");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("get_wrapper(o, 'f', 2)");
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
void SetWrapperCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK(args[0]
.As<v8::Object>()
->Set(args.GetIsolate()->GetCurrentContext(), args[1], args[2])
.FromJust());
}
TEST(BreakPointApiSetter) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
i::Handle<i::BreakPoint> bp;
v8::Local<v8::FunctionTemplate> function_template =
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
v8::Local<v8::FunctionTemplate> set_template =
v8::FunctionTemplate::New(env->GetIsolate(), SetWrapperCallback);
v8::Local<v8::Function> function =
function_template->GetFunction(env.local()).ToLocalChecked();
v8::Local<v8::Function> set =
set_template->GetFunction(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("f"), function).ToChecked();
env->Global()->Set(env.local(), v8_str("set_wrapper"), set).ToChecked();
CompileRun(
"var o = {};"
"Object.defineProperty(o, 'f', { set: f, enumerable: true });");
// === Test API builtin as setter ===
break_point_hit_count = 0;
// Run with breakpoint.
bp = SetBreakPoint(function, 0);
CompileRun("o.f = 3");
CHECK_EQ(1, break_point_hit_count);
CompileRun("set_wrapper(o, 'f', 2)");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("o.f = 3");
CHECK_EQ(2, break_point_hit_count);
// === Test API builtin as setter, with condition ===
break_point_hit_count = 0;
// Run with breakpoint.
bp = SetBreakPoint(function, 0, "arguments[0] == 3");
CompileRun("set_wrapper(o, 'f', 2)");
CHECK_EQ(0, break_point_hit_count);
CompileRun("set_wrapper(o, 'f', 3)");
CHECK_EQ(1, break_point_hit_count);
CompileRun("o.f = 3");
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("set_wrapper(o, 'f', 2)");
CHECK_EQ(2, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointApiAccessor) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
i::Handle<i::BreakPoint> bp;
// Create 'foo' class, with a hidden property.
v8::Local<v8::ObjectTemplate> obj_template =
v8::ObjectTemplate::New(env->GetIsolate());
v8::Local<v8::FunctionTemplate> accessor_template =
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
obj_template->SetAccessorProperty(v8_str("f"), accessor_template,
accessor_template);
v8::Local<v8::Object> obj =
obj_template->NewInstance(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("o"), obj).ToChecked();
v8::Local<v8::Function> function =
CompileRun("Object.getOwnPropertyDescriptor(o, 'f').set")
.As<v8::Function>();
// === Test API accessor ===
break_point_hit_count = 0;
CompileRun("function get_loop() { for (var i = 0; i < 10; i++) o.f }");
CompileRun("function set_loop() { for (var i = 0; i < 10; i++) o.f = 2 }");
CompileRun("get_loop(); set_loop();"); // Initialize ICs.
// Run with breakpoint.
bp = SetBreakPoint(function, 0);
CompileRun("o.f = 3");
CHECK_EQ(1, break_point_hit_count);
CompileRun("o.f");
CHECK_EQ(2, break_point_hit_count);
CompileRun("for (var i = 0; i < 10; i++) o.f");
CHECK_EQ(12, break_point_hit_count);
CompileRun("get_loop();");
CHECK_EQ(22, break_point_hit_count);
CompileRun("for (var i = 0; i < 10; i++) o.f = 2");
CHECK_EQ(32, break_point_hit_count);
CompileRun("set_loop();");
CHECK_EQ(42, break_point_hit_count);
// Test that the break point also works when we install the function
// template on a new property (with a fresh AccessorPair instance).
v8::Local<v8::ObjectTemplate> baz_template =
v8::ObjectTemplate::New(env->GetIsolate());
baz_template->SetAccessorProperty(v8_str("g"), accessor_template,
accessor_template);
v8::Local<v8::Object> baz =
baz_template->NewInstance(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("b"), baz).ToChecked();
CompileRun("b.g = 4");
CHECK_EQ(43, break_point_hit_count);
CompileRun("b.g");
CHECK_EQ(44, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("o.f = 3");
CompileRun("o.f");
CHECK_EQ(44, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointInlineApiFunction) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
i::Handle<i::BreakPoint> bp;
v8::Local<v8::FunctionTemplate> function_template =
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
v8::Local<v8::Function> function =
function_template->GetFunction(env.local()).ToLocalChecked();
env->Global()->Set(env.local(), v8_str("f"), function).ToChecked();
CompileRun(
"function g() { return 1 + f(); };"
"%PrepareFunctionForOptimization(g);");
// === Test simple builtin ===
break_point_hit_count = 0;
// Run with breakpoint.
bp = SetBreakPoint(function, 0);
ExpectInt32("g()", 3);
CHECK_EQ(1, break_point_hit_count);
ExpectInt32("g()", 3);
CHECK_EQ(2, break_point_hit_count);
CompileRun("%OptimizeFunctionOnNextCall(g)");
ExpectInt32("g()", 3);
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectInt32("g()", 3);
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that a break point can be set at a return store location.
TEST(BreakPointConditionBuiltin) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_block_concurrent_recompilation = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
i::Handle<i::BreakPoint> bp;
// === Test global variable ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
CompileRun("var condition = false");
CompileRun("'a'.repeat(10)");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0, "condition == true");
CompileRun("'b'.repeat(10)");
CHECK_EQ(0, break_point_hit_count);
CompileRun("condition = true");
CompileRun("'b'.repeat(10)");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("'b'.repeat(10)");
CHECK_EQ(1, break_point_hit_count);
// === Test arguments ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
CompileRun("function f(x) { return 'a'.repeat(x * 2); }");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0, "arguments[0] == 20");
ExpectString("f(5)", "aaaaaaaaaa");
CHECK_EQ(0, break_point_hit_count);
ExpectString("f(10)", "aaaaaaaaaaaaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("f(10)", "aaaaaaaaaaaaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// === Test adapted arguments ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
CompileRun("function f(x) { return 'a'.repeat(x * 2, x); }");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0,
"arguments[1] == 10 && arguments[2] == undefined");
ExpectString("f(5)", "aaaaaaaaaa");
CHECK_EQ(0, break_point_hit_count);
ExpectString("f(10)", "aaaaaaaaaaaaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("f(10)", "aaaaaaaaaaaaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// === Test var-arg builtins ===
break_point_hit_count = 0;
builtin = CompileRun("String.fromCharCode").As<v8::Function>();
CompileRun("function f() { return String.fromCharCode(1, 2, 3); }");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0, "arguments.length == 3 && arguments[1] == 2");
CompileRun("f(1, 2, 3)");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("f(1, 2, 3)");
CHECK_EQ(1, break_point_hit_count);
// === Test rest arguments ===
break_point_hit_count = 0;
builtin = CompileRun("String.fromCharCode").As<v8::Function>();
CompileRun("function f(...args) { return String.fromCharCode(...args); }");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0, "arguments.length == 3 && arguments[1] == 2");
CompileRun("f(1, 2, 3)");
CHECK_EQ(1, break_point_hit_count);
ClearBreakPoint(bp);
CompileRun("f(1, 3, 3)");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("f(1, 2, 3)");
CHECK_EQ(1, break_point_hit_count);
// === Test receiver ===
break_point_hit_count = 0;
builtin = CompileRun("String.prototype.repeat").As<v8::Function>();
CompileRun("function f(x) { return x.repeat(10); }");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
bp = SetBreakPoint(builtin, 0, "this == 'a'");
ExpectString("f('b')", "bbbbbbbbbb");
CHECK_EQ(0, break_point_hit_count);
ExpectString("f('a')", "aaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
ExpectString("f('a')", "aaaaaaaaaa");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(BreakPointInlining) {
i::FLAG_allow_natives_syntax = true;
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
break_point_hit_count = 0;
v8::Local<v8::Function> inlinee =
CompileRun("function f(x) { return x*2; } f").As<v8::Function>();
CompileRun("function test(x) { return 1 + f(x) }");
CompileRun(
"%PrepareFunctionForOptimization(test);"
"test(0.5); test(0.6);"
"%OptimizeFunctionOnNextCall(test); test(0.7);");
CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint.
i::Handle<i::BreakPoint> bp = SetBreakPoint(inlinee, 0);
CompileRun("f(0.1);");
CHECK_EQ(1, break_point_hit_count);
CompileRun("test(0.2);");
CHECK_EQ(2, break_point_hit_count);
// Re-optimize.
CompileRun(
"%PrepareFunctionForOptimization(test);"
"%OptimizeFunctionOnNextCall(test);");
CompileRun("test(0.3);");
CHECK_EQ(3, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("test(0.3);");
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
static void CallWithBreakPoints(v8::Local<v8::Context> context,
v8::Local<v8::Object> recv,
v8::Local<v8::Function> f,
int break_point_count, int call_count) {
break_point_hit_count = 0;
for (int i = 0; i < call_count; i++) {
f->Call(context, recv, 0, nullptr).ToLocalChecked();
CHECK_EQ((i + 1) * break_point_count, break_point_hit_count);
}
}
// Test GC during break point processing.
TEST(GCDuringBreakPointProcessing) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
DebugEventBreakPointCollectGarbage delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> foo;
// Test IC store break point with garbage collection.
foo = CompileFunction(&env, "function foo(){bar=0;}", "foo");
SetBreakPoint(foo, 0);
CallWithBreakPoints(context, env->Global(), foo, 1, 10);
// Test IC load break point with garbage collection.
foo = CompileFunction(&env, "bar=1;function foo(){var x=bar;}", "foo");
SetBreakPoint(foo, 0);
CallWithBreakPoints(context, env->Global(), foo, 1, 10);
// Test IC call break point with garbage collection.
foo = CompileFunction(&env, "function bar(){};function foo(){bar();}", "foo");
SetBreakPoint(foo, 0);
CallWithBreakPoints(context, env->Global(), foo, 1, 10);
// Test return break point with garbage collection.
foo = CompileFunction(&env, "function foo(){}", "foo");
SetBreakPoint(foo, 0);
CallWithBreakPoints(context, env->Global(), foo, 1, 25);
// Test debug break slot break point with garbage collection.
foo = CompileFunction(&env, "function foo(){var a;}", "foo");
SetBreakPoint(foo, 0);
CallWithBreakPoints(context, env->Global(), foo, 1, 25);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Call the function three times with different garbage collections in between
// and make sure that the break point survives.
static void CallAndGC(v8::Local<v8::Context> context,
v8::Local<v8::Object> recv, v8::Local<v8::Function> f) {
break_point_hit_count = 0;
for (int i = 0; i < 3; i++) {
// Call function.
f->Call(context, recv, 0, nullptr).ToLocalChecked();
CHECK_EQ(1 + i * 3, break_point_hit_count);
// Scavenge and call function.
CcTest::CollectGarbage(v8::internal::NEW_SPACE);
f->Call(context, recv, 0, nullptr).ToLocalChecked();
CHECK_EQ(2 + i * 3, break_point_hit_count);
// Mark sweep (and perhaps compact) and call function.
CcTest::CollectAllGarbage();
f->Call(context, recv, 0, nullptr).ToLocalChecked();
CHECK_EQ(3 + i * 3, break_point_hit_count);
}
}
// Test that a break point can be set at a return store location.
TEST(BreakPointSurviveGC) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> foo;
// Test IC store break point with garbage collection.
{
CompileFunction(&env, "function foo(){}", "foo");
foo = CompileFunction(&env, "function foo(){bar=0;}", "foo");
SetBreakPoint(foo, 0);
}
CallAndGC(context, env->Global(), foo);
// Test IC load break point with garbage collection.
{
CompileFunction(&env, "function foo(){}", "foo");
foo = CompileFunction(&env, "bar=1;function foo(){var x=bar;}", "foo");
SetBreakPoint(foo, 0);
}
CallAndGC(context, env->Global(), foo);
// Test IC call break point with garbage collection.
{
CompileFunction(&env, "function foo(){}", "foo");
foo = CompileFunction(&env,
"function bar(){};function foo(){bar();}",
"foo");
SetBreakPoint(foo, 0);
}
CallAndGC(context, env->Global(), foo);
// Test return break point with garbage collection.
{
CompileFunction(&env, "function foo(){}", "foo");
foo = CompileFunction(&env, "function foo(){}", "foo");
SetBreakPoint(foo, 0);
}
CallAndGC(context, env->Global(), foo);
// Test non IC break point with garbage collection.
{
CompileFunction(&env, "function foo(){}", "foo");
foo = CompileFunction(&env, "function foo(){var bar=0;}", "foo");
SetBreakPoint(foo, 0);
}
CallAndGC(context, env->Global(), foo);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that the debugger statement causes a break.
TEST(DebuggerStatement) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
v8::Script::Compile(context,
v8_str(env->GetIsolate(), "function bar(){debugger}"))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
v8::Script::Compile(
context, v8_str(env->GetIsolate(), "function foo(){debugger;debugger;}"))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "foo"))
.ToLocalChecked());
v8::Local<v8::Function> bar = v8::Local<v8::Function>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "bar"))
.ToLocalChecked());
// Run function with debugger statement
bar->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
// Run function with two debugger statement
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test setting a breakpoint on the debugger statement.
TEST(DebuggerStatementBreakpoint) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Script::Compile(context,
v8_str(env->GetIsolate(), "function foo(){debugger;}"))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "foo"))
.ToLocalChecked());
// The debugger statement triggers breakpoint hit
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
i::Handle<i::BreakPoint> bp = SetBreakPoint(foo, 0);
// Set breakpoint does not duplicate hits
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
ClearBreakPoint(bp);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that the conditional breakpoints work event if code generation from
// strings is prohibited in the debugee context.
TEST(ConditionalBreakpointWithCodeGenerationDisallowed) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::Function> foo = CompileFunction(&env,
"function foo(x) {\n"
" var s = 'String value2';\n"
" return s + x;\n"
"}",
"foo");
// Set conditional breakpoint with condition 'true'.
SetBreakPoint(foo, 4, "true");
break_point_hit_count = 0;
env->AllowCodeGenerationFromStrings(false);
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Simple test of the stepping mechanism using only store ICs.
TEST(DebugStepLinear) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Create a function for testing stepping.
v8::Local<v8::Function> foo = CompileFunction(&env,
"function foo(){a=1;b=1;c=1;}",
"foo");
// Run foo to allow it to get optimized.
CompileRun("a=0; b=0; c=0; foo();");
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
SetBreakPoint(foo, 3);
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Context> context = env.local();
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(4, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Register a debug event listener which just counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
SetBreakPoint(foo, 3);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Without stepping only active break points are hit.
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test of the stepping mechanism for keyed load in a loop.
TEST(DebugStepKeyedLoadLoop) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
// Create a function for testing stepping of keyed load. The statement 'y=1'
// is there to have more than one breakable statement in the loop, TODO(315).
v8::Local<v8::Function> foo = CompileFunction(
&env,
"function foo(a) {\n"
" var x;\n"
" var len = a.length;\n"
" for (var i = 0; i < len; i++) {\n"
" y = 1;\n"
" x = a[i];\n"
" }\n"
"}\n"
"y=0\n",
"foo");
v8::Local<v8::Context> context = env.local();
// Create array [0,1,2,3,4,5,6,7,8,9]
v8::Local<v8::Array> a = v8::Array::New(env->GetIsolate(), 10);
for (int i = 0; i < 10; i++) {
CHECK(a->Set(context, v8::Number::New(env->GetIsolate(), i),
v8::Number::New(env->GetIsolate(), i))
.FromJust());
}
// Call function without any break points to ensure inlining is in place.
const int kArgc = 1;
v8::Local<v8::Value> args[kArgc] = {a};
foo->Call(context, env->Global(), kArgc, args).ToLocalChecked();
// Set up break point and step through the function.
SetBreakPoint(foo, 3);
run_step.set_step_action(StepNext);
break_point_hit_count = 0;
foo->Call(context, env->Global(), kArgc, args).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(44, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test of the stepping mechanism for keyed store in a loop.
TEST(DebugStepKeyedStoreLoop) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
// Create a function for testing stepping of keyed store. The statement 'y=1'
// is there to have more than one breakable statement in the loop, TODO(315).
v8::Local<v8::Function> foo = CompileFunction(
&env,
"function foo(a) {\n"
" var len = a.length;\n"
" for (var i = 0; i < len; i++) {\n"
" y = 1;\n"
" a[i] = 42;\n"
" }\n"
"}\n"
"y=0\n",
"foo");
v8::Local<v8::Context> context = env.local();
// Create array [0,1,2,3,4,5,6,7,8,9]
v8::Local<v8::Array> a = v8::Array::New(env->GetIsolate(), 10);
for (int i = 0; i < 10; i++) {
CHECK(a->Set(context, v8::Number::New(env->GetIsolate(), i),
v8::Number::New(env->GetIsolate(), i))
.FromJust());
}
// Call function without any break points to ensure inlining is in place.
const int kArgc = 1;
v8::Local<v8::Value> args[kArgc] = {a};
foo->Call(context, env->Global(), kArgc, args).ToLocalChecked();
// Set up break point and step through the function.
SetBreakPoint(foo, 3);
run_step.set_step_action(StepNext);
break_point_hit_count = 0;
foo->Call(context, env->Global(), kArgc, args).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(44, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test of the stepping mechanism for named load in a loop.
TEST(DebugStepNamedLoadLoop) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping of named load.
v8::Local<v8::Function> foo = CompileFunction(
&env,
"function foo() {\n"
" var a = [];\n"
" var s = \"\";\n"
" for (var i = 0; i < 10; i++) {\n"
" var v = new V(i, i + 1);\n"
" v.y;\n"
" a.length;\n" // Special case: array length.
" s.length;\n" // Special case: string length.
" }\n"
"}\n"
"function V(x, y) {\n"
" this.x = x;\n"
" this.y = y;\n"
"}\n",
"foo");
// Call function without any break points to ensure inlining is in place.
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Set up break point and step through the function.
SetBreakPoint(foo, 4);
run_step.set_step_action(StepNext);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(65, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
static void DoDebugStepNamedStoreLoop(int expected) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
// Create a function for testing stepping of named store.
v8::Local<v8::Context> context = env.local();
v8::Local<v8::Function> foo = CompileFunction(
&env,
"function foo() {\n"
" var a = {a:1};\n"
" for (var i = 0; i < 10; i++) {\n"
" a.a = 2\n"
" }\n"
"}\n",
"foo");
// Call function without any break points to ensure inlining is in place.
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Set up break point and step through the function.
SetBreakPoint(foo, 3);
run_step.set_step_action(StepNext);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all expected break locations are hit.
CHECK_EQ(expected, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test of the stepping mechanism for named load in a loop.
TEST(DebugStepNamedStoreLoop) { DoDebugStepNamedStoreLoop(34); }
// Test the stepping mechanism with different ICs.
TEST(DebugStepLinearMixedICs) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping.
v8::Local<v8::Function> foo = CompileFunction(&env,
"function bar() {};"
"function foo() {"
" var x;"
" var index='name';"
" var y = {};"
" a=1;b=2;x=a;y[index]=3;x=y[index];bar();}", "foo");
// Run functions to allow them to get optimized.
CompileRun("a=0; b=0; bar(); foo();");
SetBreakPoint(foo, 0);
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(10, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Register a debug event listener which just counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
SetBreakPoint(foo, 0);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Without stepping only active break points are hit.
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepDeclarations) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const char* src = "function foo() { "
" var a;"
" var b = 1;"
" var c = foo;"
" var d = Math.floor;"
" var e = b + d(1.2);"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 0);
// Stepping through the declarations.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(5, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepLocals) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const char* src = "function foo() { "
" var a,b;"
" a = 1;"
" b = a + 2;"
" b = 1 + 2 + 3;"
" a = Math.floor(b);"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 0);
// Stepping through the declarations.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(5, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepIf) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" a = 1;"
" if (x) {"
" b = 1;"
" } else {"
" c = 1;"
" d = 1;"
" }"
"}"
"a=0; b=0; c=0; d=0; foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 0);
// Stepping through the true part.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_true[argc] = {v8::True(isolate)};
foo->Call(context, env->Global(), argc, argv_true).ToLocalChecked();
CHECK_EQ(4, break_point_hit_count);
// Stepping through the false part.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_false[argc] = {v8::False(isolate)};
foo->Call(context, env->Global(), argc, argv_false).ToLocalChecked();
CHECK_EQ(5, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepSwitch) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" a = 1;"
" switch (x) {"
" case 1:"
" b = 1;"
" case 2:"
" c = 1;"
" break;"
" case 3:"
" d = 1;"
" e = 1;"
" f = 1;"
" break;"
" }"
"}"
"a=0; b=0; c=0; d=0; e=0; f=0; foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 0);
// One case with fall-through.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_1[argc] = {v8::Number::New(isolate, 1)};
foo->Call(context, env->Global(), argc, argv_1).ToLocalChecked();
CHECK_EQ(6, break_point_hit_count);
// Another case.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_2[argc] = {v8::Number::New(isolate, 2)};
foo->Call(context, env->Global(), argc, argv_2).ToLocalChecked();
CHECK_EQ(5, break_point_hit_count);
// Last case.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_3[argc] = {v8::Number::New(isolate, 3)};
foo->Call(context, env->Global(), argc, argv_3).ToLocalChecked();
CHECK_EQ(7, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepWhile) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" var a = 0;"
" while (a < x) {"
" a++;"
" }"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 8); // "var a = 0;"
// Looping 0 times. We still should break at the while-condition once.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_0[argc] = {v8::Number::New(isolate, 0)};
foo->Call(context, env->Global(), argc, argv_0).ToLocalChecked();
CHECK_EQ(3, break_point_hit_count);
// Looping 10 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_10[argc] = {v8::Number::New(isolate, 10)};
foo->Call(context, env->Global(), argc, argv_10).ToLocalChecked();
CHECK_EQ(23, break_point_hit_count);
// Looping 100 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_100[argc] = {v8::Number::New(isolate, 100)};
foo->Call(context, env->Global(), argc, argv_100).ToLocalChecked();
CHECK_EQ(203, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepDoWhile) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" var a = 0;"
" do {"
" a++;"
" } while (a < x)"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 8); // "var a = 0;"
// Looping 0 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_0[argc] = {v8::Number::New(isolate, 0)};
foo->Call(context, env->Global(), argc, argv_0).ToLocalChecked();
CHECK_EQ(4, break_point_hit_count);
// Looping 10 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_10[argc] = {v8::Number::New(isolate, 10)};
foo->Call(context, env->Global(), argc, argv_10).ToLocalChecked();
CHECK_EQ(22, break_point_hit_count);
// Looping 100 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_100[argc] = {v8::Number::New(isolate, 100)};
foo->Call(context, env->Global(), argc, argv_100).ToLocalChecked();
CHECK_EQ(202, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepFor) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" a = 1;"
" for (i = 0; i < x; i++) {"
" b = 1;"
" }"
"}"
"a=0; b=0; i=0; foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 8); // "a = 1;"
// Looping 0 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_0[argc] = {v8::Number::New(isolate, 0)};
foo->Call(context, env->Global(), argc, argv_0).ToLocalChecked();
CHECK_EQ(4, break_point_hit_count);
// Looping 10 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_10[argc] = {v8::Number::New(isolate, 10)};
foo->Call(context, env->Global(), argc, argv_10).ToLocalChecked();
CHECK_EQ(34, break_point_hit_count);
// Looping 100 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_100[argc] = {v8::Number::New(isolate, 100)};
foo->Call(context, env->Global(), argc, argv_100).ToLocalChecked();
CHECK_EQ(304, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepForContinue) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" var a = 0;"
" var b = 0;"
" var c = 0;"
" for (var i = 0; i < x; i++) {"
" a++;"
" if (a % 2 == 0) continue;"
" b++;"
" c++;"
" }"
" return b;"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
v8::Local<v8::Value> result;
SetBreakPoint(foo, 8); // "var a = 0;"
// Each loop generates 4 or 5 steps depending on whether a is equal.
// Looping 10 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_10[argc] = {v8::Number::New(isolate, 10)};
result = foo->Call(context, env->Global(), argc, argv_10).ToLocalChecked();
CHECK_EQ(5, result->Int32Value(context).FromJust());
CHECK_EQ(62, break_point_hit_count);
// Looping 100 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_100[argc] = {v8::Number::New(isolate, 100)};
result = foo->Call(context, env->Global(), argc, argv_100).ToLocalChecked();
CHECK_EQ(50, result->Int32Value(context).FromJust());
CHECK_EQ(557, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepForBreak) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const int argc = 1;
const char* src = "function foo(x) { "
" var a = 0;"
" var b = 0;"
" var c = 0;"
" for (var i = 0; i < 1000; i++) {"
" a++;"
" if (a == x) break;"
" b++;"
" c++;"
" }"
" return b;"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
v8::Local<v8::Value> result;
SetBreakPoint(foo, 8); // "var a = 0;"
// Each loop generates 5 steps except for the last (when break is executed)
// which only generates 4.
// Looping 10 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_10[argc] = {v8::Number::New(isolate, 10)};
result = foo->Call(context, env->Global(), argc, argv_10).ToLocalChecked();
CHECK_EQ(9, result->Int32Value(context).FromJust());
CHECK_EQ(64, break_point_hit_count);
// Looping 100 times.
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
v8::Local<v8::Value> argv_100[argc] = {v8::Number::New(isolate, 100)};
result = foo->Call(context, env->Global(), argc, argv_100).ToLocalChecked();
CHECK_EQ(99, result->Int32Value(context).FromJust());
CHECK_EQ(604, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepForIn) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
v8::Local<v8::Function> foo;
const char* src_1 = "function foo() { "
" var a = [1, 2];"
" for (x in a) {"
" b = 0;"
" }"
"}"
"foo()";
foo = CompileFunction(&env, src_1, "foo");
SetBreakPoint(foo, 0); // "var a = ..."
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(8, break_point_hit_count);
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const char* src_2 = "function foo() { "
" var a = {a:[1, 2, 3]};"
" for (x in a.a) {"
" b = 0;"
" }"
"}"
"foo()";
foo = CompileFunction(&env, src_2, "foo");
SetBreakPoint(foo, 0); // "var a = ..."
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(10, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugStepWith) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const char* src = "function foo(x) { "
" var a = {};"
" with (a) {}"
" with (b) {}"
"}"
"foo()";
CHECK(env->Global()
->Set(context, v8_str(env->GetIsolate(), "b"),
v8::Object::New(env->GetIsolate()))
.FromJust());
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
v8::Local<v8::Value> result;
SetBreakPoint(foo, 8); // "var a = {};"
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(4, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugConditional) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping. Run it to allow it to get
// optimized.
const char* src =
"function foo(x) { "
" return x ? 1 : 2;"
"}"
"foo()";
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
SetBreakPoint(foo, 0); // "var a;"
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
const int argc = 1;
v8::Local<v8::Value> argv_true[argc] = {v8::True(isolate)};
foo->Call(context, env->Global(), argc, argv_true).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that step in does not step into native functions.
TEST(DebugStepNatives) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Create a function for testing stepping.
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){debugger;Math.sin(1);}", "foo");
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(3, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Register a debug event listener which just counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Without stepping only active break points are hit.
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that step in works with function.apply.
TEST(DebugStepFunctionApply) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Create a function for testing stepping.
v8::Local<v8::Function> foo =
CompileFunction(&env,
"function bar(x, y, z) { if (x == 1) { a = y; b = z; } }"
"function foo(){ debugger; bar.apply(this, [1,2,3]); }",
"foo");
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
v8::Local<v8::Context> context = env.local();
run_step.set_step_action(StepIn);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// With stepping all break locations are hit.
CHECK_EQ(7, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Register a debug event listener which just counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Without stepping only the debugger statement is hit.
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that step in works with function.call.
TEST(DebugStepFunctionCall) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping.
v8::Local<v8::Function> foo = CompileFunction(
&env,
"function bar(x, y, z) { if (x == 1) { a = y; b = z; } }"
"function foo(a){ debugger;"
" if (a) {"
" bar.call(this, 1, 2, 3);"
" } else {"
" bar.call(this, 0);"
" }"
"}",
"foo");
// Register a debug event listener which steps and counts.
DebugEventCounter run_step;
v8::debug::SetDebugDelegate(env->GetIsolate(), &run_step);
run_step.set_step_action(StepIn);
// Check stepping where the if condition in bar is false.
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(6, break_point_hit_count);
// Check stepping where the if condition in bar is true.
break_point_hit_count = 0;
const int argc = 1;
v8::Local<v8::Value> argv[argc] = {v8::True(isolate)};
foo->Call(context, env->Global(), argc, argv).ToLocalChecked();
CHECK_EQ(8, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Register a debug event listener which just counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Without stepping only the debugger statement is hit.
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(isolate, nullptr);