blob: 2c0329a8f59ad9447ec0f04760ae368717b5749d [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/execution/microtask-queue.h"
#include "src/objects/objects-inl.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.
HeapObjectIterator 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, "this != 1");
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(BreakPointApiIntrinsics) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Function> builtin;
// === Test that using API-exposed functions won't trigger breakpoints ===
{
v8::Local<v8::Function> weakmap_get =
CompileRun("WeakMap.prototype.get").As<v8::Function>();
SetBreakPoint(weakmap_get, 0);
v8::Local<v8::Function> weakmap_set =
CompileRun("WeakMap.prototype.set").As<v8::Function>();
SetBreakPoint(weakmap_set, 0);
// Run with breakpoint.
break_point_hit_count = 0;
CompileRun("var w = new WeakMap(); w.set(w, 1); w.get(w);");
CHECK_EQ(2, break_point_hit_count);
break_point_hit_count = 0;
v8::Local<v8::debug::WeakMap> weakmap =
v8::debug::WeakMap::New(env->GetIsolate());
CHECK(!weakmap->Set(env.local(), weakmap, v8_num(1)).IsEmpty());
CHECK(!weakmap->Get(env.local(), weakmap).IsEmpty());
CHECK_EQ(0, break_point_hit_count);
}
{
v8::Local<v8::Function> object_to_string =
CompileRun("Object.prototype.toString").As<v8::Function>();
SetBreakPoint(object_to_string, 0);
// Run with breakpoint.
break_point_hit_count = 0;
CompileRun("var o = {}; o.toString();");
CHECK_EQ(1, break_point_hit_count);
break_point_hit_count = 0;
v8::Local<v8::Object> object = v8::Object::New(env->GetIsolate());
CHECK(!object->ObjectProtoToString(env.local()).IsEmpty());
CHECK_EQ(0, break_point_hit_count);
}
{
v8::Local<v8::Function> map_set =
CompileRun("Map.prototype.set").As<v8::Function>();
v8::Local<v8::Function> map_get =
CompileRun("Map.prototype.get").As<v8::Function>();
v8::Local<v8::Function> map_has =
CompileRun("Map.prototype.has").As<v8::Function>();
v8::Local<v8::Function> map_delete =
CompileRun("Map.prototype.delete").As<v8::Function>();
SetBreakPoint(map_set, 0);
SetBreakPoint(map_get, 0);
SetBreakPoint(map_has, 0);
SetBreakPoint(map_delete, 0);
// Run with breakpoint.
break_point_hit_count = 0;
CompileRun(
"var m = new Map(); m.set(m, 1); m.get(m); m.has(m); m.delete(m);");
CHECK_EQ(4, break_point_hit_count);
break_point_hit_count = 0;
v8::Local<v8::Map> map = v8::Map::New(env->GetIsolate());
CHECK(!map->Set(env.local(), map, v8_num(1)).IsEmpty());
CHECK(!map->Get(env.local(), map).IsEmpty());
CHECK(map->Has(env.local(), map).FromJust());
CHECK(map->Delete(env.local(), map).FromJust());
CHECK_EQ(0, break_point_hit_count);
}
{
v8::Local<v8::Function> set_add =
CompileRun("Set.prototype.add").As<v8::Function>();
v8::Local<v8::Function> set_get =
CompileRun("Set.prototype.has").As<v8::Function>();
v8::Local<v8::Function> set_delete =
CompileRun("Set.prototype.delete").As<v8::Function>();
SetBreakPoint(set_add, 0);
SetBreakPoint(set_get, 0);
SetBreakPoint(set_delete, 0);
// Run with breakpoint.
break_point_hit_count = 0;
CompileRun("var s = new Set(); s.add(s); s.has(s); s.delete(s);");
CHECK_EQ(3, break_point_hit_count);
break_point_hit_count = 0;
v8::Local<v8::Set> set = v8::Set::New(env->GetIsolate());
CHECK(!set->Add(env.local(), set).IsEmpty());
CHECK(set->Has(env.local(), set).FromJust());
CHECK(set->Delete(env.local(), set).FromJust());
CHECK_EQ(0, 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, "this != 1");
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, "this != 1");
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, "this != 1");
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;
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, "this != 1");
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 ===
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);
}
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, "this != 1");
ExpectInt32("f()", 2);
CHECK_EQ(1, break_point_hit_count);
ExpectInt32("f()", 2);
CHECK_EQ(2, break_point_hit_count);
// Direct call through API does not trigger breakpoint.
function->Call(env.local(), v8::Undefined(env->GetIsolate()), 0, nullptr)
.ToLocalChecked();
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();
}
TEST(BreakPointApiConstructor) {
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, "this != 1");
CompileRun("new f()");
CHECK_EQ(1, break_point_hit_count);
CompileRun("new f()");
CHECK_EQ(2, break_point_hit_count);
// Direct call through API does not trigger breakpoint.
function->NewInstance(env.local()).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("new f()");
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(0, break_point_hit_count);
CompileRun("o.f");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("get_wrapper(o, 'f', 2)");
CompileRun("o.f");
CHECK_EQ(1, 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(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("o.f = 3");
CHECK_EQ(1, 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(0, break_point_hit_count);
CompileRun("o.f = 3");
CHECK_EQ(1, break_point_hit_count);
// Run without breakpoints.
ClearBreakPoint(bp);
CompileRun("set_wrapper(o, 'f', 2)");
CompileRun("o.f = 3");
CHECK_EQ(1, 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);
CheckDebuggerUnloaded();
}
// Test that step in works with Function.call.apply.
TEST(DebugStepFunctionCallApply) {
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() { }"
"function foo(){ debugger;"
" Function.call.apply(bar);"
" Function.call.apply(Function.call, "
"[Function.call, bar]);"
"}",
"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);
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(6, 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);
CheckDebuggerUnloaded();
}
// Tests that breakpoint will be hit if it's set in script.
TEST(PauseInScript) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate());
// Register a debug event listener which counts.
DebugEventCounter event_counter;
v8::debug::SetDebugDelegate(env->GetIsolate(), &event_counter);
v8::Local<v8::Context> context = env.local();
// Create a script that returns a function.
const char* src = "(function (evt) {})";
const char* script_name = "StepInHandlerTest";
v8::ScriptOrigin origin(v8_str(env->GetIsolate(), script_name),
v8::Integer::New(env->GetIsolate(), 0));
v8::Local<v8::Script> script =
v8::Script::Compile(context, v8_str(env->GetIsolate(), src), &origin)
.ToLocalChecked();
// Set breakpoint in the script.
i::Handle<i::Script> i_script(
i::Script::cast(v8::Utils::OpenHandle(*script)->shared().script()),
isolate);
i::Handle<i::String> condition = isolate->factory()->empty_string();
int position = 0;
int id;
isolate->debug()->SetBreakPointForScript(i_script, condition, &position, &id);
break_point_hit_count = 0;
v8::Local<v8::Value> r = script->Run(context).ToLocalChecked();
CHECK(r->IsFunction());
CHECK_EQ(1, break_point_hit_count);
// Get rid of the debug delegate.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
int message_callback_count = 0;
TEST(DebugBreak) {
i::FLAG_stress_compaction = false;
#ifdef VERIFY_HEAP
i::FLAG_verify_heap = true;
#endif
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which sets the break flag and counts.
DebugEventBreak delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping.
const char* src = "function f0() {}"
"function f1(x1) {}"
"function f2(x1,x2) {}"
"function f3(x1,x2,x3) {}";
v8::Local<v8::Function> f0 = CompileFunction(&env, src, "f0");
v8::Local<v8::Function> f1 = CompileFunction(&env, src, "f1");
v8::Local<v8::Function> f2 = CompileFunction(&env, src, "f2");
v8::Local<v8::Function> f3 = CompileFunction(&env, src, "f3");
// Call the function to make sure it is compiled.
v8::Local<v8::Value> argv[] = {
v8::Number::New(isolate, 1), v8::Number::New(isolate, 1),
v8::Number::New(isolate, 1), v8::Number::New(isolate, 1)};
// Call all functions to make sure that they are compiled.
f0->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
f1->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
f2->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
f3->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Set the debug break flag.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
// Call all functions with different argument count.
break_point_hit_count = 0;
for (unsigned int i = 0; i < arraysize(argv); i++) {
f0->Call(context, env->Global(), i, argv).ToLocalChecked();
f1->Call(context, env->Global(), i, argv).ToLocalChecked();
f2->Call(context, env->Global(), i, argv).ToLocalChecked();
f3->Call(context, env->Global(), i, argv).ToLocalChecked();
}
// One break for each function called.
CHECK_EQ(4 * arraysize(argv), break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
class DebugScopingListener : public v8::debug::DebugDelegate {
public:
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType exception_type) override {
break_count_++;
auto stack_traces =
v8::debug::StackTraceIterator::Create(CcTest::isolate());
v8::debug::Location location = stack_traces->GetSourceLocation();
CHECK_EQ(26, location.GetColumnNumber());
CHECK_EQ(0, location.GetLineNumber());
auto scopes = stack_traces->GetScopeIterator();
CHECK_EQ(v8::debug::ScopeIterator::ScopeTypeWith, scopes->GetType());
CHECK_EQ(20, scopes->GetStartLocation().GetColumnNumber());
CHECK_EQ(31, scopes->GetEndLocation().GetColumnNumber());
scopes->Advance();
CHECK_EQ(v8::debug::ScopeIterator::ScopeTypeLocal, scopes->GetType());
CHECK_EQ(0, scopes->GetStartLocation().GetColumnNumber());
CHECK_EQ(68, scopes->GetEndLocation().GetColumnNumber());
scopes->Advance();
CHECK_EQ(v8::debug::ScopeIterator::ScopeTypeGlobal, scopes->GetType());
scopes->Advance();
CHECK(scopes->Done());
}
unsigned break_count() const { return break_count_; }
private:
unsigned break_count_ = 0;
};
TEST(DebugBreakInWrappedScript) {
i::FLAG_stress_compaction = false;
#ifdef VERIFY_HEAP
i::FLAG_verify_heap = true;
#endif
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which sets the break flag and counts.
DebugScopingListener delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
static const char* source =
// 0 1 2 3 4 5 6 7
"try { with({o : []}){ o[0](); } } catch (e) { return e.toString(); }";
static const char* expect = "TypeError: o[0] is not a function";
// For this test, we want to break on uncaught exceptions:
ChangeBreakOnException(true, true);
{
v8::ScriptCompiler::Source script_source(v8_str(source));
v8::Local<v8::Function> fun =
v8::ScriptCompiler::CompileFunctionInContext(
env.local(), &script_source, 0, nullptr, 0, nullptr)
.ToLocalChecked();
v8::Local<v8::Value> result =
fun->Call(env.local(), env->Global(), 0, nullptr).ToLocalChecked();
CHECK(result->IsString());
CHECK(v8::Local<v8::String>::Cast(result)
->Equals(env.local(), v8_str(expect))
.FromJust());
}
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CHECK_EQ(1, delegate.break_count());
CheckDebuggerUnloaded();
}
static void EmptyHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {}
TEST(DebugScopeIteratorWithFunctionTemplate) {
LocalContext env;
v8::HandleScope handle_scope(env->GetIsolate());
v8::Isolate* isolate = env->GetIsolate();
EnableDebugger(isolate);
v8::Local<v8::Function> func =
v8::Function::New(env.local(), EmptyHandler).ToLocalChecked();
std::unique_ptr<v8::debug::ScopeIterator> iterator =
v8::debug::ScopeIterator::CreateForFunction(isolate, func);
CHECK(iterator->Done());
DisableDebugger(isolate);
}
TEST(DebugBreakWithoutJS) {
i::FLAG_stress_compaction = false;
#ifdef VERIFY_HEAP
i::FLAG_verify_heap = true;
#endif
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = env.local();
// Register a debug event listener which sets the break flag and counts.
DebugEventBreak delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
// Set the debug break flag.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
v8::Local<v8::String> json = v8_str("[1]");
v8::Local<v8::Value> parsed = v8::JSON::Parse(context, json).ToLocalChecked();
CHECK(v8::JSON::Stringify(context, parsed)
.ToLocalChecked()
->Equals(context, json)
.FromJust());
CHECK_EQ(0, break_point_hit_count);
CompileRun("");
CHECK_EQ(1, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test to ensure that JavaScript code keeps running while the debug break
// through the stack limit flag is set but breaks are disabled.
TEST(DisableBreak) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which sets the break flag and counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
// Create a function for testing stepping.
const char* src = "function f() {g()};function g(){i=0; while(i<10){i++}}";
v8::Local<v8::Function> f = CompileFunction(&env, src, "f");
// Set, test and cancel debug break.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
v8::debug::ClearBreakOnNextFunctionCall(env->GetIsolate());
// Set the debug break flag.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
// Call all functions with different argument count.
break_point_hit_count = 0;
f->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
{
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate());
v8::internal::DisableBreak disable_break(isolate->debug());
f->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(1, break_point_hit_count);
}
f->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DisableDebuggerStatement) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug event listener which sets the break flag and counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRun("debugger;");
CHECK_EQ(1, break_point_hit_count);
// Check that we ignore debugger statement when breakpoints aren't active.
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate());
isolate->debug()->set_break_points_active(false);
CompileRun("debugger;");
CHECK_EQ(1, break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
}
static const char* kSimpleExtensionSource =
"(function Foo() {"
" return 4;"
"})() ";
// http://crbug.com/28933
// Test that debug break is disabled when bootstrapper is active.
TEST(NoBreakWhenBootstrapping) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
// Register a debug event listener which sets the break flag and counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
// Set the debug break flag.
v8::debug::SetBreakOnNextFunctionCall(isolate);
break_point_hit_count = 0;
{
// Create a context with an extension to make sure that some JavaScript
// code is executed during bootstrapping.
v8::RegisterExtension(
std::make_unique<v8::Extension>("simpletest", kSimpleExtensionSource));
const char* extension_names[] = { "simpletest" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::HandleScope handle_scope(isolate);
v8::Context::New(isolate, &extensions);
}
// Check that no DebugBreak events occurred during the context creation.
CHECK_EQ(0, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(isolate, nullptr);
CheckDebuggerUnloaded();
}
TEST(SetDebugEventListenerOnUninitializedVM) {
v8::HandleScope scope(CcTest::isolate());
EnableDebugger(CcTest::isolate());
}
// Test that clearing the debug event listener actually clears all break points
// and related information.
TEST(DebuggerUnload) {
LocalContext env;
v8::HandleScope handle_scope(env->GetIsolate());
// Check debugger is unloaded before it is used.
CheckDebuggerUnloaded();
// Set a debug event listener.
break_point_hit_count = 0;
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::HandleScope scope(env->GetIsolate());
// Create a couple of functions for the test.
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(){x=1}", "foo");
v8::Local<v8::Function> bar =
CompileFunction(&env, "function bar(){y=2}", "bar");
// Set some break points.
SetBreakPoint(foo, 0);
SetBreakPoint(foo, 4);
SetBreakPoint(bar, 0);
SetBreakPoint(bar, 4);
// Make sure that the break points are there.
break_point_hit_count = 0;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(2, break_point_hit_count);
bar->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
CHECK_EQ(4, break_point_hit_count);
}
// Remove the debug event listener without clearing breakpoints. Do this
// outside a handle scope.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
int event_listener_hit_count = 0;
// Test for issue http://code.google.com/p/v8/issues/detail?id=289.
// Make sure that DebugGetLoadedScripts doesn't return scripts
// with disposed external source.
class EmptyExternalStringResource : public v8::String::ExternalStringResource {
public:
EmptyExternalStringResource() { empty_[0] = 0; }
~EmptyExternalStringResource() override = default;
size_t length() const override { return empty_.length(); }
const uint16_t* data() const override { return empty_.begin(); }
private:
::v8::internal::EmbeddedVector<uint16_t, 1> empty_;
};
TEST(DebugScriptLineEndsAreAscending) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
// Compile a test script.
v8::Local<v8::String> script = v8_str(isolate,
"function f() {\n"
" debugger;\n"
"}\n");
v8::ScriptOrigin origin1 = v8::ScriptOrigin(v8_str(isolate, "name"));
v8::Local<v8::Script> script1 =
v8::Script::Compile(env.local(), script, &origin1).ToLocalChecked();
USE(script1);
Handle<v8::internal::FixedArray> instances;
{
v8::internal::Debug* debug = CcTest::i_isolate()->debug();
instances = debug->GetLoadedScripts();
}
CHECK_GT(instances->length(), 0);
for (int i = 0; i < instances->length(); i++) {
Handle<v8::internal::Script> script = Handle<v8::internal::Script>(
v8::internal::Script::cast(instances->get(i)), CcTest::i_isolate());
v8::internal::Script::InitLineEnds(CcTest::i_isolate(), script);
v8::internal::FixedArray ends =
v8::internal::FixedArray::cast(script->line_ends());
CHECK_GT(ends.length(), 0);
int prev_end = -1;
for (int j = 0; j < ends.length(); j++) {
const int curr_end = v8::internal::Smi::ToInt(ends.get(j));
CHECK_GT(curr_end, prev_end);
prev_end = curr_end;
}
}
}
static v8::Local<v8::Context> expected_context;
static v8::Local<v8::Value> expected_context_data;
class ContextCheckEventListener : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
CheckContext();
}
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override {
CheckContext();
}
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType) override {
CheckContext();
}
bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) override {
CheckContext();
return false;
}
private:
void CheckContext() {
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
CHECK(context == expected_context);
CHECK(context->GetEmbedderData(0)->StrictEquals(expected_context_data));
event_listener_hit_count++;
}
};
// Test which creates two contexts and sets different embedder data on each.
// Checks that this data is set correctly and that when the debug event
// listener is called the expected context is the one active.
TEST(ContextData) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
// Create two contexts.
v8::Local<v8::Context> context_1;
v8::Local<v8::Context> context_2;
v8::Local<v8::ObjectTemplate> global_template =
v8::Local<v8::ObjectTemplate>();
v8::Local<v8::Value> global_object = v8::Local<v8::Value>();
context_1 =
v8::Context::New(isolate, nullptr, global_template, global_object);
context_2 =
v8::Context::New(isolate, nullptr, global_template, global_object);
ContextCheckEventListener delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
// Default data value is undefined.
CHECK_EQ(0, context_1->GetNumberOfEmbedderDataFields());
CHECK_EQ(0, context_2->GetNumberOfEmbedderDataFields());
// Set and check different data values.
v8::Local<v8::String> data_1 = v8_str(isolate, "1");
v8::Local<v8::String> data_2 = v8_str(isolate, "2");
context_1->SetEmbedderData(0, data_1);
context_2->SetEmbedderData(0, data_2);
CHECK(context_1->GetEmbedderData(0)->StrictEquals(data_1));
CHECK(context_2->GetEmbedderData(0)->StrictEquals(data_2));
// Simple test function which causes a break.
const char* source = "function f() { debugger; }";
// Enter and run function in the first context.
{
v8::Context::Scope context_scope(context_1);
expected_context = context_1;
expected_context_data = data_1;
v8::Local<v8::Function> f = CompileFunction(isolate, source, "f");
f->Call(context_1, context_1->Global(), 0, nullptr).ToLocalChecked();
}
// Enter and run function in the second context.
{
v8::Context::Scope context_scope(context_2);
expected_context = context_2;
expected_context_data = data_2;
v8::Local<v8::Function> f = CompileFunction(isolate, source, "f");
f->Call(context_2, context_2->Global(), 0, nullptr).ToLocalChecked();
}
// Two times compile event and two times break event.
CHECK_GT(event_listener_hit_count, 3);
v8::debug::SetDebugDelegate(isolate, nullptr);
CheckDebuggerUnloaded();
}
// Test which creates a context and sets embedder data on it. Checks that this
// data is set correctly and that when the debug event listener is called for
// break event in an eval statement the expected context is the one returned by
// Message.GetEventContext.
TEST(EvalContextData) {
v8::HandleScope scope(CcTest::isolate());
v8::Local<v8::Context> context_1;
v8::Local<v8::ObjectTemplate> global_template =
v8::Local<v8::ObjectTemplate>();
context_1 = v8::Context::New(CcTest::isolate(), nullptr, global_template);
ContextCheckEventListener delegate;
v8::debug::SetDebugDelegate(CcTest::isolate(), &delegate);
// Contexts initially do not have embedder data fields.
CHECK_EQ(0, context_1->GetNumberOfEmbedderDataFields());
// Set and check a data value.
v8::Local<v8::String> data_1 = v8_str(CcTest::isolate(), "1");
context_1->SetEmbedderData(0, data_1);
CHECK(context_1->GetEmbedderData(0)->StrictEquals(data_1));
// Simple test function with eval that causes a break.
const char* source = "function f() { eval('debugger;'); }";
// Enter and run function in the context.
{
v8::Context::Scope context_scope(context_1);
expected_context = context_1;
expected_context_data = data_1;
v8::Local<v8::Function> f = CompileFunction(CcTest::isolate(), source, "f");
f->Call(context_1, context_1->Global(), 0, nullptr).ToLocalChecked();
}
v8::debug::SetDebugDelegate(CcTest::isolate(), nullptr);
// One time compile event and one time break event.
CHECK_GT(event_listener_hit_count, 2);
CheckDebuggerUnloaded();
}
// Debug event listener which counts script compiled events.
class ScriptCompiledDelegate : public v8::debug::DebugDelegate {
public:
void ScriptCompiled(v8::Local<v8::debug::Script>, bool,
bool has_compile_error) override {
if (!has_compile_error) {
after_compile_event_count++;
} else {
compile_error_event_count++;
}
}
int after_compile_event_count = 0;
int compile_error_event_count = 0;
};
// Tests that after compile event is sent as many times as there are scripts
// compiled.
TEST(AfterCompileEventWhenEventListenerIsReset) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
const char* script = "var a=1";
ScriptCompiledDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Script::Compile(context, v8_str(env->GetIsolate(), script))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
v8::Script::Compile(context, v8_str(env->GetIsolate(), script))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
// Setting listener to nullptr should cause debugger unload.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Compilation cache should be disabled when debugger is active.
CHECK_EQ(2, delegate.after_compile_event_count);
}
// Tests that syntax error event is sent as many times as there are scripts
// with syntax error compiled.
TEST(SyntaxErrorEventOnSyntaxException) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// For this test, we want to break on uncaught exceptions:
ChangeBreakOnException(false, true);
ScriptCompiledDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
// Check initial state.
CHECK_EQ(0, delegate.compile_error_event_count);
// Throws SyntaxError: Unexpected end of input
CHECK(
v8::Script::Compile(context, v8_str(env->GetIsolate(), "+++")).IsEmpty());
CHECK_EQ(1, delegate.compile_error_event_count);
CHECK(v8::Script::Compile(context, v8_str(env->GetIsolate(), "/sel\\/: \\"))
.IsEmpty());
CHECK_EQ(2, delegate.compile_error_event_count);
v8::Local<v8::Script> script =
v8::Script::Compile(context,
v8_str(env->GetIsolate(), "JSON.parse('1234:')"))
.ToLocalChecked();
CHECK_EQ(2, delegate.compile_error_event_count);
CHECK(script->Run(context).IsEmpty());
CHECK_EQ(3, delegate.compile_error_event_count);
v8::Script::Compile(context,
v8_str(env->GetIsolate(), "new RegExp('/\\/\\\\');"))
.ToLocalChecked();
CHECK_EQ(3, delegate.compile_error_event_count);
v8::Script::Compile(context, v8_str(env->GetIsolate(), "throw 1;"))
.ToLocalChecked();
CHECK_EQ(3, delegate.compile_error_event_count);
}
class ExceptionEventCounter : public v8::debug::DebugDelegate {
public:
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType) override {
exception_event_count++;
}
int exception_event_count = 0;
};
TEST(NoBreakOnStackOverflow) {
i::FLAG_stack_size = 100;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
ExceptionEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CHECK_EQ(0, delegate.exception_event_count);
CompileRun(
"function f() { return f(); }"
"try { f() } catch {}");
CHECK_EQ(0, delegate.exception_event_count);
}
// Tests that break event is sent when event listener is reset.
TEST(BreakEventWhenEventListenerIsReset) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
const char* script = "function f() {};";
ScriptCompiledDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Script::Compile(context, v8_str(env->GetIsolate(), script))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "f"))
.ToLocalChecked());
f->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// Setting event listener to nullptr should cause debugger unload.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
// Compilation cache should be disabled when debugger is active.
CHECK_EQ(1, delegate.after_compile_event_count);
}
// Tests that script is reported as compiled when bound to context.
TEST(AfterCompileEventOnBindToContext) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
ScriptCompiledDelegate delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
v8::ScriptCompiler::Source script_source(
v8::String::NewFromUtf8Literal(isolate, "var a=1"));
v8::Local<v8::UnboundScript> unbound =
v8::ScriptCompiler::CompileUnboundScript(isolate, &script_source)
.ToLocalChecked();
CHECK_EQ(delegate.after_compile_event_count, 0);
unbound->BindToCurrentContext();
CHECK_EQ(delegate.after_compile_event_count, 1);
v8::debug::SetDebugDelegate(isolate, nullptr);
}
// Test that if DebugBreak is forced it is ignored when code from
// debug-delay.js is executed.
TEST(NoDebugBreakInAfterCompileEventListener) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
// Register a debug event listener which sets the break flag and counts.
DebugEventCounter delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
// Set the debug break flag.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
// Create a function for testing stepping.
const char* src = "function f() { eval('var x = 10;'); } ";
v8::Local<v8::Function> f = CompileFunction(&env, src, "f");
// There should be only one break event.
CHECK_EQ(1, break_point_hit_count);
// Set the debug break flag again.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
f->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// There should be one more break event when the script is evaluated in 'f'.
CHECK_EQ(2, break_point_hit_count);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that the debug break flag works with function.apply.
TEST(RepeatDebugBreak) {
// Test that we can repeatedly set a break without JS execution continuing.
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
// Create a function for testing breaking in apply.
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo() {}", "foo");
// Register a debug delegate which repeatedly sets a break and counts.
DebugEventBreakMax delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
// Set the debug break flag before calling the code using function.apply.
v8::debug::SetBreakOnNextFunctionCall(env->GetIsolate());
// Trigger a break by calling into foo().
break_point_hit_count = 0;
max_break_point_hit_count = 10000;
foo->Call(context, env->Global(), 0, nullptr).ToLocalChecked();
// When keeping the debug break several break will happen.
CHECK_EQ(break_point_hit_count, max_break_point_hit_count);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
// Test that setting the terminate execution flag during debug break processing.
static void TestDebugBreakInLoop(const char* loop_head,
const char** loop_bodies,
const char* loop_tail) {
// Receive 10 breaks for each test and then terminate JavaScript execution.
static const int kBreaksPerTest = 10;
for (int i = 0; loop_bodies[i] != nullptr; i++) {
// Perform a lazy deoptimization after various numbers of breaks
// have been hit.
i::EmbeddedVector<char, 1024> buffer;
SNPrintF(buffer, "function f() {%s%s%s}", loop_head, loop_bodies[i],
loop_tail);
i::PrintF("%s\n", buffer.begin());
for (int j = 0; j < 3; j++) {
break_point_hit_count_deoptimize = j;
if (j == 2) {
break_point_hit_count_deoptimize = kBreaksPerTest;
}
break_point_hit_count = 0;
max_break_point_hit_count = kBreaksPerTest;
terminate_after_max_break_point_hit = true;
// Function with infinite loop.
CompileRun(buffer.begin());
// Set the debug break to enter the debugger as soon as possible.
v8::debug::SetBreakOnNextFunctionCall(CcTest::isolate());
// Call function with infinite loop.
CompileRun("f();");
CHECK_EQ(kBreaksPerTest, break_point_hit_count);
CHECK(!CcTest::isolate()->IsExecutionTerminating());
}
}
}
static const char* loop_bodies_1[] = {"",
"g()",
"if (a == 0) { g() }",
"if (a == 1) { g() }",
"if (a == 0) { g() } else { h() }",
"if (a == 0) { continue }",
nullptr};
static const char* loop_bodies_2[] = {
"if (a == 1) { continue }",
"switch (a) { case 1: g(); }",
"switch (a) { case 1: continue; }",
"switch (a) { case 1: g(); break; default: h() }",
"switch (a) { case 1: continue; break; default: h() }",
nullptr};
void DebugBreakLoop(const char* loop_header, const char** loop_bodies,
const char* loop_footer) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
// Register a debug delegate which repeatedly sets the break flag and counts.
DebugEventBreakMax delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRun(
"var a = 1;\n"
"function g() { }\n"
"function h() { }");
TestDebugBreakInLoop(loop_header, loop_bodies, loop_footer);
// Get rid of the debug event listener.
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(DebugBreakInWhileTrue1) {
DebugBreakLoop("while (true) {", loop_bodies_1, "}");
}
TEST(DebugBreakInWhileTrue2) {
DebugBreakLoop("while (true) {", loop_bodies_2, "}");
}
TEST(DebugBreakInWhileCondition1) {
DebugBreakLoop("while (a == 1) {", loop_bodies_1, "}");
}
TEST(DebugBreakInWhileCondition2) {
DebugBreakLoop("while (a == 1) {", loop_bodies_2, "}");
}
TEST(DebugBreakInDoWhileTrue1) {
DebugBreakLoop("do {", loop_bodies_1, "} while (true)");
}
TEST(DebugBreakInDoWhileTrue2) {
DebugBreakLoop("do {", loop_bodies_2, "} while (true)");
}
TEST(DebugBreakInDoWhileCondition1) {
DebugBreakLoop("do {", loop_bodies_1, "} while (a == 1)");
}
TEST(DebugBreakInDoWhileCondition2) {
DebugBreakLoop("do {", loop_bodies_2, "} while (a == 1)");
}
TEST(DebugBreakInFor1) { DebugBreakLoop("for (;;) {", loop_bodies_1, "}"); }
TEST(DebugBreakInFor2) { DebugBreakLoop("for (;;) {", loop_bodies_2, "}"); }
TEST(DebugBreakInForCondition1) {
DebugBreakLoop("for (;a == 1;) {", loop_bodies_1, "}");
}
TEST(DebugBreakInForCondition2) {
DebugBreakLoop("for (;a == 1;) {", loop_bodies_2, "}");
}
class DebugBreakInlineListener : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
int expected_frame_count = 4;
int expected_line_number[] = {1, 4, 7, 13};
int frame_count = 0;
auto iterator = v8::debug::StackTraceIterator::Create(CcTest::isolate());
for (; !iterator->Done(); iterator->Advance(), ++frame_count) {
v8::debug::Location loc = iterator->GetSourceLocation();
CHECK_EQ(expected_line_number[frame_count], loc.GetLineNumber());
}
CHECK_EQ(frame_count, expected_frame_count);
}
};
TEST(DebugBreakInline) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
const char* source =
"function debug(b) { \n"
" if (b) debugger; \n"
"} \n"
"function f(b) { \n"
" debug(b) \n"
"}; \n"
"function g(b) { \n"
" f(b); \n"
"}; \n"
"%PrepareFunctionForOptimization(g); \n"
"g(false); \n"
"g(false); \n"
"%OptimizeFunctionOnNextCall(g); \n"
"g(true);";
DebugBreakInlineListener delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Script> inline_script =
v8::Script::Compile(context, v8_str(env->GetIsolate(), source))
.ToLocalChecked();
inline_script->Run(context).ToLocalChecked();
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
}
static void RunScriptInANewCFrame(const char* source) {
v8::TryCatch try_catch(CcTest::isolate());
CompileRun(source);
CHECK(try_catch.HasCaught());
}
TEST(Regress131642) {
// Bug description:
// When doing StepNext through the first script, the debugger is not reset
// after exiting through exception. A flawed implementation enabling the
// debugger to step into Array.prototype.forEach breaks inside the callback
// for forEach in the second script under the assumption that we are in a
// recursive call. In an attempt to step out, we crawl the stack using the
// recorded frame pointer from the first script and fail when not finding it
// on the stack.
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugEventCounter delegate;
delegate.set_step_action(StepNext);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
// We step through the first script. It exits through an exception. We run
// this inside a new frame to record a different FP than the second script
// would expect.
const char* script_1 = "debugger; throw new Error();";
RunScriptInANewCFrame(script_1);
// The second script uses forEach.
const char* script_2 = "[0].forEach(function() { });";
CompileRun(script_2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
}
class DebugBreakStackTraceListener : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
v8::StackTrace::CurrentStackTrace(CcTest::isolate(), 10);
}
};
static void AddDebugBreak(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::debug::SetBreakOnNextFunctionCall(args.GetIsolate());
}
TEST(DebugBreakStackTrace) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
DebugBreakStackTraceListener delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::FunctionTemplate> add_debug_break_template =
v8::FunctionTemplate::New(env->GetIsolate(), AddDebugBreak);
v8::Local<v8::Function> add_debug_break =
add_debug_break_template->GetFunction(context).ToLocalChecked();
CHECK(env->Global()
->Set(context, v8_str("add_debug_break"), add_debug_break)
.FromJust());
CompileRun("(function loop() {"
" for (var j = 0; j < 1000; j++) {"
" for (var i = 0; i < 1000; i++) {"
" if (i == 999) add_debug_break();"
" }"
" }"
"})()");
}
v8::base::Semaphore terminate_requested_semaphore(0);
v8::base::Semaphore terminate_fired_semaphore(0);
class DebugBreakTriggerTerminate : public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
if (terminate_already_fired_) return;
terminate_requested_semaphore.Signal();
// Wait for at most 2 seconds for the terminate request.
CHECK(
terminate_fired_semaphore.WaitFor(v8::base::TimeDelta::FromSeconds(2)));
terminate_already_fired_ = true;
}
private:
bool terminate_already_fired_ = false;
};
class TerminationThread : public v8::base::Thread {
public:
explicit TerminationThread(v8::Isolate* isolate)
: Thread(Options("terminator")), isolate_(isolate) {}
void Run() override {
terminate_requested_semaphore.Wait();
isolate_->TerminateExecution();
terminate_fired_semaphore.Signal();
}
private:
v8::Isolate* isolate_;
};
TEST(DebugBreakOffThreadTerminate) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
DebugBreakTriggerTerminate delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
TerminationThread terminator(isolate);
CHECK(terminator.Start());
v8::TryCatch try_catch(env->GetIsolate());
env->GetIsolate()->RequestInterrupt(BreakRightNow, nullptr);
CompileRun("while (true);");
CHECK(try_catch.HasTerminated());
}
class ArchiveRestoreThread : public v8::base::Thread,
public v8::debug::DebugDelegate {
public:
ArchiveRestoreThread(v8::Isolate* isolate, int spawn_count)
: Thread(Options("ArchiveRestoreThread")),
isolate_(isolate),
debug_(reinterpret_cast<i::Isolate*>(isolate_)->debug()),
spawn_count_(spawn_count),
break_count_(0) {}
void Run() override {
{
v8::Locker locker(isolate_);
v8::Isolate::Scope i_scope(isolate_);
v8::HandleScope scope(isolate_);
v8::Local<v8::Context> context = v8::Context::New(isolate_);
v8::Context::Scope context_scope(context);
auto callback = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Value> value = info.Data();
CHECK(value->IsExternal());
auto art = static_cast<ArchiveRestoreThread*>(
v8::Local<v8::External>::Cast(value)->Value());
art->MaybeSpawnChildThread();
};
v8::Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(
isolate_, callback, v8::External::New(isolate_, this));
CHECK(context->Global()
->Set(context, v8_str("maybeSpawnChildThread"),
fun->GetFunction(context).ToLocalChecked())
.FromJust());
v8::Local<v8::Function> test =
CompileFunction(isolate_,
"function test(n) {\n"
" debugger;\n"
" nest();\n"
" middle();\n"
" return n + 1;\n"
" function middle() {\n"
" debugger;\n"
" nest();\n"
" Date.now();\n"
" }\n"
" function nest() {\n"
" maybeSpawnChildThread();\n"
" }\n"
"}\n",
"test");
debug_->SetDebugDelegate(this);
v8::internal::DisableBreak enable_break(debug_, false);
v8::Local<v8::Value> args[1] = {v8::Integer::New(isolate_, spawn_count_)};
int result = test->Call(context, context->Global(), 1, args)
.ToLocalChecked()
->Int32Value(context)
.FromJust();
// Verify that test(spawn_count_) returned spawn_count_ + 1.
CHECK_EQ(spawn_count_ + 1, result);
}
}
void BreakProgramRequested(
v8::Local<v8::Context> context,
const std::vector<v8::debug::BreakpointId>&) override {
auto stack_traces = v8::debug::StackTraceIterator::Create(isolate_);
if (!stack_traces->Done()) {
v8::debug::Location location = stack_traces->GetSourceLocation();
i::PrintF("ArchiveRestoreThread #%d hit breakpoint at line %d\n",
spawn_count_, location.GetLineNumber());
const int expectedLineNumber[] = {1, 2, 3, 6, 4};
CHECK_EQ(expectedLineNumber[break_count_], location.GetLineNumber());
switch (break_count_) {
case 0: // debugger;
case 1: // nest();
case 2: // middle();
// Attempt to stop on the next line after the first debugger
// statement. If debug->{Archive,Restore}Debug() improperly reset
// thread-local debug information, the debugger will fail to stop
// before the test function returns.
debug_->PrepareStep(StepNext);
// Spawning threads while handling the current breakpoint verifies
// that the parent thread correctly archived and restored the
// state necessary to stop on the next line. If not, then control
// will simply continue past the `return n + 1` statement.
//
// A real world multi-threading app would probably never unlock the
// Isolate at a break point as that adds a thread switch point while
// debugging where none existed in the application and a
// multi-threaded should be able to count on not thread switching
// over a certain range of instructions.
MaybeSpawnChildThread();
break;
case 3: // debugger; in middle();
// Attempt to stop on the next line after the first debugger
// statement. If debug->{Archive,Restore}Debug() improperly reset
// thread-local debug information, the debugger will fail to stop
// before the test function returns.
debug_->PrepareStep(StepOut);
break;
case 4: // return n + 1;
break;
default:
CHECK(false);
}
}
++break_count_;
}
void MaybeSpawnChildThread() {
if (spawn_count_ > 1) {
v8::Unlocker unlocker(isolate_);
// Spawn a thread that spawns a thread that spawns a thread (and so
// on) so that the ThreadManager is forced to archive and restore
// the current thread.
ArchiveRestoreThread child(isolate_, spawn_count_ - 1);
CHECK(child.Start());
child.Join();
// The child thread sets itself as the debug delegate, so we need to
// usurp it after the child finishes, or else future breakpoints
// will be delegated to a destroyed ArchiveRestoreThread object.
debug_->SetDebugDelegate(this);
// This is the most important check in this test, since
// child.GetBreakCount() will return 1 if the debugger fails to stop
// on the `next()` line after the grandchild thread returns.
CHECK_EQ(child.GetBreakCount(), 5);
}
}
int GetBreakCount() { return break_count_; }
private:
v8::Isolate* isolate_;
v8::internal::Debug* debug_;
const int spawn_count_;
int break_count_;
};
TEST(DebugArchiveRestore) {
v8::Isolate* isolate = CcTest::isolate();
ArchiveRestoreThread thread(isolate, 4);
// Instead of calling thread.Start() and thread.Join() here, we call
// thread.Run() directly, to make sure we exercise archive/restore
// logic on the *current* thread as well as other threads.
thread.Run();
CHECK_EQ(thread.GetBreakCount(), 5);
}
class DebugEventExpectNoException : public v8::debug::DebugDelegate {
public:
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType) override {
CHECK(false);
}
};
static void TryCatchWrappedThrowCallback(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::TryCatch try_catch(args.GetIsolate());
CompileRun("throw 'rejection';");
CHECK(try_catch.HasCaught());
}
TEST(DebugPromiseInterceptedByTryCatch) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
DebugEventExpectNoException delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
v8::Local<v8::Context> context = env.local();
ChangeBreakOnException(false, true);
v8::Local<v8::FunctionTemplate> fun =
v8::FunctionTemplate::New(isolate, TryCatchWrappedThrowCallback);
CHECK(env->Global()
->Set(context, v8_str("fun"),
fun->GetFunction(context).ToLocalChecked())
.FromJust());
CompileRun("var p = new Promise(function(res, rej) { fun(); res(); });");
CompileRun(
"var r;"
"p.then(function() { r = 'resolved'; },"
" function() { r = 'rejected'; });");
CHECK(CompileRun("r")->Equals(context, v8_str("resolved")).FromJust());
}
class NoInterruptsOnDebugEvent : public v8::debug::DebugDelegate {
public:
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override {
++after_compile_handler_depth_;
// Do not allow nested AfterCompile events.
CHECK_LE(after_compile_handler_depth_, 1);
v8::Isolate* isolate = CcTest::isolate();
v8::Isolate::AllowJavascriptExecutionScope allow_script(isolate);
isolate->RequestInterrupt(&HandleInterrupt, this);
CompileRun("function foo() {}; foo();");
--after_compile_handler_depth_;
}
private:
static void HandleInterrupt(v8::Isolate* isolate, void* data) {
NoInterruptsOnDebugEvent* d = static_cast<NoInterruptsOnDebugEvent*>(data);
CHECK_EQ(0, d->after_compile_handler_depth_);
}
int after_compile_handler_depth_ = 0;
};
TEST(NoInterruptsInDebugListener) {
LocalContext env;
v8::HandleScope handle_scope(env->GetIsolate());
NoInterruptsOnDebugEvent delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRun("void(0);");
}
TEST(BreakLocationIterator) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Value> result = CompileRun(
"function f() {\n"
" debugger; \n"
" f(); \n"
" debugger; \n"
"} \n"
"f");
Handle<i::Object> function_obj = v8::Utils::OpenHandle(*result);
Handle<i::JSFunction> function = Handle<i::JSFunction>::cast(function_obj);
Handle<i::SharedFunctionInfo> shared(function->shared(), i_isolate);
EnableDebugger(isolate);
CHECK(i_isolate->debug()->EnsureBreakInfo(shared));
i_isolate->debug()->PrepareFunctionForDebugExecution(shared);
Handle<i::DebugInfo> debug_info(shared->GetDebugInfo(), i_isolate);
{
i::BreakIterator iterator(debug_info);
CHECK(iterator.GetBreakLocation().IsDebuggerStatement());
CHECK_EQ(17, iterator.GetBreakLocation().position());
iterator.Next();
CHECK(iterator.GetBreakLocation().IsDebugBreakSlot());
CHECK_EQ(32, iterator.GetBreakLocation().position());
iterator.Next();
CHECK(iterator.GetBreakLocation().IsCall());
CHECK_EQ(32, iterator.GetBreakLocation().position());
iterator.Next();
CHECK(iterator.GetBreakLocation().IsDebuggerStatement());
CHECK_EQ(47, iterator.GetBreakLocation().position());
iterator.Next();
CHECK(iterator.GetBreakLocation().IsReturn());
CHECK_EQ(60, iterator.GetBreakLocation().position());
iterator.Next();
CHECK(iterator.Done());
}
DisableDebugger(isolate);
}
class DebugStepOverFunctionWithCaughtExceptionListener
: public v8::debug::DebugDelegate {
public:
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
++break_point_hit_count;
if (break_point_hit_count >= 3) return;
PrepareStep(StepNext);
}
int break_point_hit_count = 0;
};
TEST(DebugStepOverFunctionWithCaughtException) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
DebugStepOverFunctionWithCaughtExceptionListener delegate;
v8::debug::SetDebugDelegate(isolate, &delegate);
CompileRun(
"function foo() {\n"
" try { throw new Error(); } catch (e) {}\n"
"}\n"
"debugger;\n"
"foo();\n"
"foo();\n");
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CHECK_EQ(3, delegate.break_point_hit_count);
}
bool near_heap_limit_callback_called = false;
size_t NearHeapLimitCallback(void* data, size_t current_heap_limit,
size_t initial_heap_limit) {
near_heap_limit_callback_called = true;
return initial_heap_limit + 10u * i::MB;
}
UNINITIALIZED_TEST(DebugSetOutOfMemoryListener) {
i::FLAG_stress_concurrent_allocation = false;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
create_params.constraints.set_max_old_generation_size_in_bytes(10 * i::MB);
v8::Isolate* isolate = v8::Isolate::New(create_params);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
{
v8::Isolate::Scope i_scope(isolate);
v8::HandleScope scope(isolate);
LocalContext context(isolate);
isolate->AddNearHeapLimitCallback(NearHeapLimitCallback, nullptr);
CHECK(!near_heap_limit_callback_called);
// The following allocation fails unless the out-of-memory callback
// increases the heap limit.
int length = 10 * i::MB / i::kTaggedSize;
i_isolate->factory()->NewFixedArray(length, i::AllocationType::kOld);
CHECK(near_heap_limit_callback_called);
isolate->RemoveNearHeapLimitCallback(NearHeapLimitCallback, 0);
}
isolate->Dispose();
}
TEST(DebugCoverage) {
i::FLAG_always_opt = false;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::debug::Coverage::SelectMode(isolate,
v8::debug::CoverageMode::kPreciseCount);
v8::Local<v8::String> source = v8_str(
"function f() {\n"
"}\n"
"f();\n"
"f();");
CompileRun(source);
v8::debug::Coverage coverage = v8::debug::Coverage::CollectPrecise(isolate);
CHECK_EQ(1u, coverage.ScriptCount());
v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(0);
v8::Local<v8::debug::Script> script = script_data.GetScript();
CHECK(script->Source()
.ToLocalChecked()
->Equals(env.local(), source)
.FromMaybe(false));
CHECK_EQ(2u, script_data.FunctionCount());
v8::debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(0);
v8::debug::Location start =
script->GetSourceLocation(function_data.StartOffset());
v8::debug::Location end =
script->GetSourceLocation(function_data.EndOffset());
CHECK_EQ(0, start.GetLineNumber());
CHECK_EQ(0, start.GetColumnNumber());
CHECK_EQ(3, end.GetLineNumber());
CHECK_EQ(4, end.GetColumnNumber());
CHECK_EQ(1, function_data.Count());
function_data = script_data.GetFunctionData(1);
start = script->GetSourceLocation(function_data.StartOffset());
end = script->GetSourceLocation(function_data.EndOffset());
CHECK_EQ(0, start.GetLineNumber());
CHECK_EQ(0, start.GetColumnNumber());
CHECK_EQ(1, end.GetLineNumber());
CHECK_EQ(1, end.GetColumnNumber());
CHECK_EQ(2, function_data.Count());
}
namespace {
v8::debug::Coverage::ScriptData GetScriptDataAndDeleteCoverage(
v8::Isolate* isolate) {
v8::debug::Coverage coverage = v8::debug::Coverage::CollectPrecise(isolate);
CHECK_EQ(1u, coverage.ScriptCount());
return coverage.GetScriptData(0);
}
} // namespace
TEST(DebugCoverageWithCoverageOutOfScope) {
i::FLAG_always_opt = false;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::debug::Coverage::SelectMode(isolate,
v8::debug::CoverageMode::kPreciseCount);
v8::Local<v8::String> source = v8_str(
"function f() {\n"
"}\n"
"f();\n"
"f();");
CompileRun(source);
v8::debug::Coverage::ScriptData script_data =
GetScriptDataAndDeleteCoverage(isolate);
v8::Local<v8::debug::Script> script = script_data.GetScript();
CHECK(script->Source()
.ToLocalChecked()
->Equals(env.local(), source)
.FromMaybe(false));
CHECK_EQ(2u, script_data.FunctionCount());
v8::debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(0);
CHECK_EQ(0, function_data.StartOffset());
CHECK_EQ(26, function_data.EndOffset());
v8::debug::Location start =
script->GetSourceLocation(function_data.StartOffset());
v8::debug::Location end =
script->GetSourceLocation(function_data.EndOffset());
CHECK_EQ(0, start.GetLineNumber());
CHECK_EQ(0, start.GetColumnNumber());
CHECK_EQ(3, end.GetLineNumber());
CHECK_EQ(4, end.GetColumnNumber());
CHECK_EQ(1, function_data.Count());
function_data = script_data.GetFunctionData(1);
start = script->GetSourceLocation(function_data.StartOffset());
end = script->GetSourceLocation(function_data.EndOffset());
CHECK_EQ(0, function_data.StartOffset());
CHECK_EQ(16, function_data.EndOffset());
CHECK_EQ(0, start.GetLineNumber());
CHECK_EQ(0, start.GetColumnNumber());
CHECK_EQ(1, end.GetLineNumber());
CHECK_EQ(1, end.GetColumnNumber());
CHECK_EQ(2, function_data.Count());
}
namespace {
v8::debug::Coverage::FunctionData GetFunctionDataAndDeleteCoverage(
v8::Isolate* isolate) {
v8::debug::Coverage coverage = v8::debug::Coverage::CollectPrecise(isolate);
CHECK_EQ(1u, coverage.ScriptCount());
v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(0);
CHECK_EQ(2u, script_data.FunctionCount());
v8::debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(0);
CHECK_EQ(1, function_data.Count());
CHECK_EQ(0, function_data.StartOffset());
CHECK_EQ(26, function_data.EndOffset());
return function_data;
}
} // namespace
TEST(DebugCoverageWithScriptDataOutOfScope) {
i::FLAG_always_opt = false;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::debug::Coverage::SelectMode(isolate,
v8::debug::CoverageMode::kPreciseCount);
v8::Local<v8::String> source = v8_str(
"function f() {\n"
"}\n"
"f();\n"
"f();");
CompileRun(source);
v8::debug::Coverage::FunctionData function_data =
GetFunctionDataAndDeleteCoverage(isolate);
CHECK_EQ(1, function_data.Count());
CHECK_EQ(0, function_data.StartOffset());
CHECK_EQ(26, function_data.EndOffset());
}
TEST(BuiltinsExceptionPrediction) {
v8::Isolate* isolate = CcTest::isolate();
i::Isolate* iisolate = CcTest::i_isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::New(isolate);
i::Builtins* builtins = iisolate->builtins();
bool fail = false;
for (int i = 0; i < i::Builtins::builtin_count; i++) {
i::Code builtin = builtins->builtin(i);
if (builtin.kind() != i::CodeKind::BUILTIN) continue;
auto prediction = builtin.GetBuiltinCatchPrediction();
USE(prediction);
}
CHECK(!fail);
}
TEST(DebugGetPossibleBreakpointsReturnLocations) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::String> source = v8_str(
"function fib(x) {\n"
" if (x < 0) return;\n"
" if (x === 0) return 1;\n"
" if (x === 1) return fib(0);\n"
" return x > 2 ? fib(x - 1) + fib(x - 2) : fib(1) + fib(0);\n"
"}");
CompileRun(source);
v8::PersistentValueVector<v8::debug::Script> scripts(isolate);
v8::debug::GetLoadedScripts(isolate, scripts);
CHECK_EQ(scripts.Size(), 1);
std::vector<v8::debug::BreakLocation> locations;
CHECK(scripts.Get(0)->GetPossibleBreakpoints(
v8::debug::Location(0, 17), v8::debug::Location(), true, &locations));
int returns_count = 0;
for (size_t i = 0; i < locations.size(); ++i) {
if (locations[i].type() == v8::debug::kReturnBreakLocation) {
++returns_count;
}
}
// With Ignition we generate one return location per return statement,
// each has line = 5, column = 0 as statement position.
CHECK_EQ(returns_count, 4);
}
TEST(DebugEvaluateNoSideEffect) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
EnableDebugger(env->GetIsolate());
i::Isolate* isolate = CcTest::i_isolate();
std::vector<i::Handle<i::JSFunction>> all_functions;
{
i::HeapObjectIterator iterator(isolate->heap());
for (i::HeapObject obj = iterator.Next(); !obj.is_null();
obj = iterator.Next()) {
if (!obj.IsJSFunction()) continue;
i::JSFunction fun = i::JSFunction::cast(obj);
all_functions.emplace_back(fun, isolate);
}
}
// Perform side effect check on all built-in functions. The side effect check
// itself contains additional sanity checks.
for (i::Handle<i::JSFunction> fun : all_functions) {
bool failed = false;
isolate->debug()->StartSideEffectCheckMode();
failed = !isolate->debug()->PerformSideEffectCheck(
fun, v8::Utils::OpenHandle(*env->Global()));
isolate->debug()->StopSideEffectCheckMode();
if (failed) isolate->clear_pending_exception();
}
DisableDebugger(env->GetIsolate());
}
namespace {
i::MaybeHandle<i::Script> FindScript(
i::Isolate* isolate, const std::vector<i::Handle<i::Script>>& scripts,
const char* name) {
Handle<i::String> i_name =
isolate->factory()->NewStringFromAsciiChecked(name);
for (const auto& script : scripts) {
if (!script->name().IsString()) continue;
if (i_name->Equals(i::String::cast(script->name()))) return script;
}
return i::MaybeHandle<i::Script>();
}
} // anonymous namespace
UNINITIALIZED_TEST(LoadedAtStartupScripts) {
i::FLAG_expose_gc = true;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
{
v8::Isolate::Scope i_scope(isolate);
v8::HandleScope scope(isolate);
LocalContext context(isolate);
std::vector<i::Handle<i::Script>> scripts;
CompileWithOrigin(v8_str("function foo(){}"), v8_str("normal.js"),
v8_bool(false));
std::unordered_map<int, int> count_by_type;
{
i::DisallowHeapAllocation no_gc;
i::Script::Iterator iterator(i_isolate);
for (i::Script script = iterator.Next(); !script.is_null();
script = iterator.Next()) {
if (script.type() == i::Script::TYPE_NATIVE &&
script.name().IsUndefined(i_isolate)) {
continue;
}
++count_by_type[script.type()];
scripts.emplace_back(script, i_isolate);
}
}
CHECK_EQ(count_by_type[i::Script::TYPE_NATIVE], 0);
CHECK_EQ(count_by_type[i::Script::TYPE_EXTENSION], 1);
CHECK_EQ(count_by_type[i::Script::TYPE_NORMAL], 1);
CHECK_EQ(count_by_type[i::Script::TYPE_WASM], 0);
CHECK_EQ(count_by_type[i::Script::TYPE_INSPECTOR], 0);
i::Handle<i::Script> gc_script =
FindScript(i_isolate, scripts, "v8/gc").ToHandleChecked();
CHECK_EQ(gc_script->type(), i::Script::TYPE_EXTENSION);
i::Handle<i::Script> normal_script =
FindScript(i_isolate, scripts, "normal.js").ToHandleChecked();
CHECK_EQ(normal_script->type(), i::Script::TYPE_NORMAL);
}
isolate->Dispose();
}
TEST(SourceInfo) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
const char* source =
"//\n"
"function a() { b(); };\n"
"function b() {\n"
" c(true);\n"
"};\n"
" function c(x) {\n"
" if (x) {\n"
" return 1;\n"
" } else {\n"
" return 1;\n"
" }\n"
" };\n"
"function d(x) {\n"
" x = 1 ;\n"
" x = 2 ;\n"
" x = 3 ;\n"
" x = 4 ;\n"
" x = 5 ;\n"
" x = 6 ;\n"
" x = 7 ;\n"
" x = 8 ;\n"
" x = 9 ;\n"
" x = 10;\n"
" x = 11;\n"
" x = 12;\n"
" x = 13;\n"
" x = 14;\n"
" x = 15;\n"
"}\n";
v8::Local<v8::Script> v8_script =
v8::Script::Compile(env.local(), v8_str(source)).ToLocalChecked();
i::Handle<i::Script> i_script(
i::Script::cast(v8::Utils::OpenHandle(*v8_script)->shared().script()),
CcTest::i_isolate());
v8::Local<v8::debug::Script> script =
v8::ToApiHandle<v8::debug::Script>(i_script);
// Test that when running through source positions the position, line and
// column progresses as expected.
v8::debug::Location prev_location = script->GetSourceLocation(0);
CHECK_EQ(prev_location.GetLineNumber(), 0);
CHECK_EQ(prev_location.GetColumnNumber(), 0);
for (int offset = 1; offset < 100; ++offset) {
v8::debug::Location location = script->GetSourceLocation(offset);
if (prev_location.GetLineNumber() == location.GetLineNumber()) {
CHECK_EQ(location.GetColumnNumber(), prev_location.GetColumnNumber() + 1);
} else {
CHECK_EQ(location.GetLineNumber(), prev_location.GetLineNumber() + 1);
CHECK_EQ(location.GetColumnNumber(), 0);
}
prev_location = location;
}
// Every line of d() is the same length. Verify we can loop through all
// positions and find the right line # for each.
// The position of the first line of d(), i.e. "x = 1 ;".
const int start_line_d = 13;
const int start_code_d =
static_cast<int>(strstr(source, " x = 1 ;") - source);
const int num_lines_d = 15;
const int line_length_d = 10;
int p = start_code_d;
for (int line = 0; line < num_lines_d; ++line) {
for (int column = 0; column < line_length_d; ++column) {
v8::debug::Location location = script->GetSourceLocation(p);
CHECK_EQ(location.GetLineNumber(), start_line_d + line);
CHECK_EQ(location.GetColumnNumber(), column);
++p;
}
}
// Test first positon.
CHECK_EQ(script->GetSourceLocation(0).GetLineNumber(), 0);
CHECK_EQ(script->GetSourceLocation(0).GetColumnNumber(), 0);
// Test second positon.
CHECK_EQ(script->GetSourceLocation(1).GetLineNumber(), 0);
CHECK_EQ(script->GetSourceLocation(1).GetColumnNumber(), 1);
// Test first positin in function a().
const int start_a =
static_cast<int>(strstr(source, "function a") - source) + 10;
CHECK_EQ(script->GetSourceLocation(start_a).GetLineNumber(), 1);
CHECK_EQ(script->GetSourceLocation(start_a).GetColumnNumber(), 10);
// Test first positin in function b().
const int start_b =
static_cast<int>(strstr(source, "function b") - source) + 13;
CHECK_EQ(script->GetSourceLocation(start_b).GetLineNumber(), 2);
CHECK_EQ(script->GetSourceLocation(start_b).GetColumnNumber(), 13);
// Test first positin in function c().
const int start_c =
static_cast<int>(strstr(source, "function c") - source) + 10;
CHECK_EQ(script->GetSourceLocation(start_c).GetLineNumber(), 5);
CHECK_EQ(script->GetSourceLocation(start_c).GetColumnNumber(), 12);
// Test first positin in function d().
const int start_d =
static_cast<int>(strstr(source, "function d") - source) + 10;
CHECK_EQ(script->GetSourceLocation(start_d).GetLineNumber(), 12);
CHECK_EQ(script->GetSourceLocation(start_d).GetColumnNumber(), 10);
// Test offsets.
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(1, 10)), start_a);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(2, 13)), start_b);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 0)), start_b + 5);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 2)), start_b + 7);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(4, 0)), start_b + 16);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(5, 12)), start_c);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(6, 0)), start_c + 6);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(7, 0)), start_c + 19);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(8, 0)), start_c + 35);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(9, 0)), start_c + 48);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(10, 0)), start_c + 64);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(11, 0)), start_c + 70);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(12, 10)), start_d);
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(13, 0)), start_d + 6);
for (int i = 1; i <= num_lines_d; ++i) {
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(start_line_d + i, 0)),
6 + (i * line_length_d) + start_d);
}
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(start_line_d + 17, 0)),
start_d + 158);
// Make sure invalid inputs work properly.
const int last_position = static_cast<int>(strlen(source)) - 1;
CHECK_EQ(script->GetSourceLocation(-1).GetLineNumber(), 0);
CHECK_EQ(script->GetSourceLocation(last_position + 2).GetLineNumber(),
i::kNoSourcePosition);
// Test last position.
CHECK_EQ(script->GetSourceLocation(last_position).GetLineNumber(), 28);
CHECK_EQ(script->GetSourceLocation(last_position).GetColumnNumber(), 1);
CHECK_EQ(script->GetSourceLocation(last_position + 1).GetLineNumber(), 29);
CHECK_EQ(script->GetSourceLocation(last_position + 1).GetColumnNumber(), 0);
}
namespace {
class SetBreakpointOnScriptCompiled : public v8::debug::DebugDelegate {
public:
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override {
v8::Local<v8::String> name;
if (!script->SourceURL().ToLocal(&name)) return;
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
if (!name->Equals(context, v8_str("test")).FromJust()) return;
CHECK(!has_compile_error);
v8::debug::Location loc(1, 2);
CHECK(script->SetBreakpoint(v8_str(""), &loc, &id_));
CHECK_EQ(loc.GetLineNumber(), 1);
CHECK_EQ(loc.GetColumnNumber(), 10);
}
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
++break_count_;
CHECK_EQ(inspector_break_points_hit[0], id_);
}
int break_count() const { return break_count_; }
private:
int break_count_ = 0;
v8::debug::BreakpointId id_;
};
} // anonymous namespace
TEST(Regress517592) {
LocalContext env;
v8::HandleScope handle_scope(env->GetIsolate());
SetBreakpointOnScriptCompiled delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
CompileRun(
v8_str("eval('var foo = function foo() {\\n' +\n"
"' var a = 1;\\n' +\n"
"'}\\n' +\n"
"'//@ sourceURL=test')"));
CHECK_EQ(delegate.break_count(), 0);
CompileRun(v8_str("foo()"));
CHECK_EQ(delegate.break_count(), 1);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
}
namespace {
std::string FromString(v8::Isolate* isolate, v8::Local<v8::String> str) {
v8::String::Utf8Value utf8(isolate, str);
return std::string(*utf8);
}
} // namespace
TEST(GetPrivateFields) {
LocalContext env;
v8::Isolate* v8_isolate = CcTest::isolate();
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::String> source = v8_str(
"var X = class {\n"
" #field_number = 1;\n"
" #field_function = function() {};\n"
"}\n"
"var x = new X()");
CompileRun(source);
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
std::vector<v8::Local<v8::Value>> names;
std::vector<v8::Local<v8::Value>> values;
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 2);
for (int i = 0; i < 2; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#field_number") {
CHECK(value->Equals(context, v8_num(1)).FromJust());
} else {
CHECK_EQ(name_str, "#field_function");
CHECK(value->IsFunction());
}
}
source = v8_str(
"var Y = class {\n"
" #base_field_number = 2;\n"
"}\n"
"var X = class extends Y{\n"
" #field_number = 1;\n"
" #field_function = function() {};\n"
"}\n"
"var x = new X()");
CompileRun(source);
names.clear();
values.clear();
object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 3);
for (int i = 0; i < 3; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#base_field_number") {
CHECK(value->Equals(context, v8_num(2)).FromJust());
} else if (name_str == "#field_number") {
CHECK(value->Equals(context, v8_num(1)).FromJust());
} else {
CHECK_EQ(name_str, "#field_function");
CHECK(value->IsFunction());
}
}
source = v8_str(
"var Y = class {\n"
" constructor() {"
" return new Proxy({}, {});"
" }"
"}\n"
"var X = class extends Y{\n"
" #field_number = 1;\n"
" #field_function = function() {};\n"
"}\n"
"var x = new X()");
CompileRun(source);
names.clear();
values.clear();
object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 2);
for (int i = 0; i < 2; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#field_number") {
CHECK(value->Equals(context, v8_num(1)).FromJust());
} else {
CHECK_EQ(name_str, "#field_function");
CHECK(value->IsFunction());
}
}
}
TEST(GetPrivateMethodsAndAccessors) {
i::FLAG_harmony_private_methods = true;
LocalContext env;
v8::Isolate* v8_isolate = CcTest::isolate();
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::String> source = v8_str(
"var X = class {\n"
" #method() { }\n"
" get #accessor() { }\n"
" set #accessor(val) { }\n"
" get #readOnly() { }\n"
" set #writeOnly(val) { }\n"
"}\n"
"var x = new X()");
CompileRun(source);
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
std::vector<v8::Local<v8::Value>> names;
std::vector<v8::Local<v8::Value>> values;
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 4);
for (int i = 0; i < 4; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#method") {
CHECK(value->IsFunction());
} else {
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
if (name_str == "#accessor") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
} else if (name_str == "#readOnly") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsNull());
} else {
CHECK_EQ(name_str, "#writeOnly");
CHECK(accessors->getter()->IsNull());
CHECK(accessors->setter()->IsFunction());
}
}
}
source = v8_str(
"var Y = class {\n"
" #method() {}\n"
" get #accessor() {}\n"
" set #accessor(val) {};\n"
"}\n"
"var X = class extends Y{\n"
" get #readOnly() {}\n"
" set #writeOnly(val) {};\n"
"}\n"
"var x = new X()");
CompileRun(source);
names.clear();
values.clear();
object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 4);
for (int i = 0; i < 4; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#method") {
CHECK(value->IsFunction());
} else {
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
if (name_str == "#accessor") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
} else if (name_str == "#readOnly") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsNull());
} else {
CHECK_EQ(name_str, "#writeOnly");
CHECK(accessors->getter()->IsNull());
CHECK(accessors->setter()->IsFunction());
}
}
}
source = v8_str(
"var Y = class {\n"
" constructor() {"
" return new Proxy({}, {});"
" }"
"}\n"
"var X = class extends Y{\n"
" #method() {}\n"
" get #accessor() {}\n"
" set #accessor(val) {};\n"
"}\n"
"var x = new X()");
CompileRun(source);
names.clear();
values.clear();
object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 2);
for (int i = 0; i < 2; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#method") {
CHECK(value->IsFunction());
} else {
CHECK_EQ(name_str, "#accessor");
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
}
}
}
TEST(GetPrivateStaticMethodsAndAccessors) {
i::FLAG_harmony_private_methods = true;
LocalContext env;
v8::Isolate* v8_isolate = CcTest::isolate();
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::String> source = v8_str(
"var X = class {\n"
" static #staticMethod() { }\n"
" static get #staticAccessor() { }\n"
" static set #staticAccessor(val) { }\n"
" static get #staticReadOnly() { }\n"
" static set #staticWriteOnly(val) { }\n"
"}\n");
CompileRun(source);
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "X"))
.ToLocalChecked());
std::vector<v8::Local<v8::Value>> names;
std::vector<v8::Local<v8::Value>> values;
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 4);
for (int i = 0; i < 4; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#staticMethod") {
CHECK(value->IsFunction());
} else {
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
if (name_str == "#staticAccessor") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
} else if (name_str == "#staticReadOnly") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsNull());
} else {
CHECK_EQ(name_str, "#staticWriteOnly");
CHECK(accessors->getter()->IsNull());
CHECK(accessors->setter()->IsFunction());
}
}
}
}
TEST(GetPrivateStaticAndInstanceMethodsAndAccessors) {
i::FLAG_harmony_private_methods = true;
LocalContext env;
v8::Isolate* v8_isolate = CcTest::isolate();
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Context> context = env.local();
v8::Local<v8::String> source = v8_str(
"var X = class {\n"
" static #staticMethod() { }\n"
" static get #staticAccessor() { }\n"
" static set #staticAccessor(val) { }\n"
" static get #staticReadOnly() { }\n"
" static set #staticWriteOnly(val) { }\n"
" #method() { }\n"
" get #accessor() { }\n"
" set #accessor(val) { }\n"
" get #readOnly() { }\n"
" set #writeOnly(val) { }\n"
"}\n"
"var x = new X()\n");
CompileRun(source);
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "X"))
.ToLocalChecked());
std::vector<v8::Local<v8::Value>> names;
std::vector<v8::Local<v8::Value>> values;
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 4);
for (int i = 0; i < 4; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#staticMethod") {
CHECK(value->IsFunction());
} else {
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
if (name_str == "#staticAccessor") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
} else if (name_str == "#staticReadOnly") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsNull());
} else {
CHECK_EQ(name_str, "#staticWriteOnly");
CHECK(accessors->getter()->IsNull());
CHECK(accessors->setter()->IsFunction());
}
}
}
names.clear();
values.clear();
object = v8::Local<v8::Object>::Cast(
env->Global()
->Get(context, v8_str(env->GetIsolate(), "x"))
.ToLocalChecked());
CHECK(v8::debug::GetPrivateMembers(context, object, &names, &values));
CHECK_EQ(names.size(), 4);
for (int i = 0; i < 4; i++) {
v8::Local<v8::Value> name = names[i];
v8::Local<v8::Value> value = values[i];
CHECK(name->IsString());
std::string name_str = FromString(v8_isolate, name.As<v8::String>());
if (name_str == "#method") {
CHECK(value->IsFunction());
} else {
CHECK(v8::debug::AccessorPair::IsAccessorPair(value));
v8::Local<v8::debug::AccessorPair> accessors =
value.As<v8::debug::AccessorPair>();
if (name_str == "#accessor") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsFunction());
} else if (name_str == "#readOnly") {
CHECK(accessors->getter()->IsFunction());
CHECK(accessors->setter()->IsNull());
} else {
CHECK_EQ(name_str, "#writeOnly");
CHECK(accessors->getter()->IsNull());
CHECK(accessors->setter()->IsFunction());
}
}
}
}
namespace {
class SetTerminateOnResumeDelegate : public v8::debug::DebugDelegate {
public:
enum Options {
kNone,
kPerformMicrotaskCheckpointAtBreakpoint,
kRunJavaScriptAtBreakpoint
};
explicit SetTerminateOnResumeDelegate(Options options = kNone)
: options_(options) {}
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
break_count_++;
v8::Isolate* isolate = paused_context->GetIsolate();
v8::debug::SetTerminateOnResume(isolate);
if (options_ == kPerformMicrotaskCheckpointAtBreakpoint) {
v8::MicrotasksScope::PerformCheckpoint(isolate);
}
if (options_ == kRunJavaScriptAtBreakpoint) {
CompileRun("globalVariable = globalVariable + 1");
}
}
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType exception_type) override {
exception_thrown_count_++;
v8::debug::SetTerminateOnResume(paused_context->GetIsolate());
}
int break_count() const { return break_count_; }
int exception_thrown_count() const { return exception_thrown_count_; }
private:
int break_count_ = 0;
int exception_thrown_count_ = 0;
Options options_;
};
} // anonymous namespace
TEST(TerminateOnResumeAtBreakpoint) {
break_point_hit_count = 0;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
// If the delegate doesn't request termination on resume from breakpoint,
// foo diverges.
v8::Script::Compile(
context,
v8_str(env->GetIsolate(), "function foo(){debugger; while(true){}}"))
.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::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
namespace {
bool microtask_one_ran = false;
static void MicrotaskOne(const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(v8::MicrotasksScope::IsRunningMicrotasks(info.GetIsolate()));
v8::HandleScope scope(info.GetIsolate());
v8::MicrotasksScope microtasks(info.GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
ExpectInt32("1 + 1", 2);
microtask_one_ran = true;
}
} // namespace
TEST(TerminateOnResumeRunMicrotaskAtBreakpoint) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
SetTerminateOnResumeDelegate delegate(
SetTerminateOnResumeDelegate::kPerformMicrotaskCheckpointAtBreakpoint);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
// Enqueue a microtask that gets run while we are paused at the breakpoint.
env->GetIsolate()->EnqueueMicrotask(
v8::Function::New(env.local(), MicrotaskOne).ToLocalChecked());
// If the delegate doesn't request termination on resume from breakpoint,
// foo diverges.
v8::Script::Compile(
context,
v8_str(env->GetIsolate(), "function foo(){debugger; while(true){}}"))
.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::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 1);
CHECK(microtask_one_ran);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(TerminateOnResumeRunJavaScriptAtBreakpoint) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
CompileRun("var globalVariable = 0;");
SetTerminateOnResumeDelegate delegate(
SetTerminateOnResumeDelegate::kRunJavaScriptAtBreakpoint);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
// If the delegate doesn't request termination on resume from breakpoint,
// foo diverges.
v8::Script::Compile(
context,
v8_str(env->GetIsolate(), "function foo(){debugger; while(true){}}"))
.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::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
ExpectInt32("globalVariable", 1);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(TerminateOnResumeAtException) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
const char* source = "throw new Error(); while(true){};";
v8::ScriptCompiler::Source script_source(v8_str(source));
v8::Local<v8::Function> foo =
v8::ScriptCompiler::CompileFunctionInContext(
env.local(), &script_source, 0, nullptr, 0, nullptr)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 0);
CHECK_EQ(delegate.exception_thrown_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(TerminateOnResumeAtBreakOnEntry) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
{
v8::TryCatch try_catch(env->GetIsolate());
v8::Local<v8::Function> builtin =
CompileRun("String.prototype.repeat").As<v8::Function>();
SetBreakPoint(builtin, 0);
v8::Local<v8::Value> val = CompileRun("'b'.repeat(10)");
CHECK_EQ(delegate.break_count(), 1);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.exception_thrown_count(), 0);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(TerminateOnResumeAtBreakOnEntryUserDefinedFunction) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
{
v8::TryCatch try_catch(env->GetIsolate());
v8::Local<v8::Function> foo =
CompileFunction(&env, "function foo(b) { while (b > 0) {} }", "foo");
// Run without breakpoints to compile source to bytecode.
CompileRun("foo(-1)");
CHECK_EQ(delegate.break_count(), 0);
SetBreakPoint(foo, 0);
v8::Local<v8::Value> val = CompileRun("foo(1)");
CHECK_EQ(delegate.break_count(), 1);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.exception_thrown_count(), 0);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
TEST(TerminateOnResumeAtUnhandledRejection) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
v8::Local<v8::Function> foo = CompileFunction(
&env, "async function foo() { Promise.reject(); while(true) {} }",
"foo");
v8::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 0);
CHECK_EQ(delegate.exception_thrown_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
namespace {
void RejectPromiseThroughCpp(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto data = reinterpret_cast<std::pair<v8::Isolate*, LocalContext*>*>(
info.Data().As<v8::External>()->Value());
v8::Local<v8::String> value1 =
v8::String::NewFromUtf8Literal(data->first, "foo");
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(data->second->local()).ToLocalChecked();
v8::Local<v8::Promise> promise = resolver->GetPromise();
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kPending);
resolver->Reject(data->second->local(), value1).ToChecked();
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kRejected);
// CHECK_EQ(*v8::Utils::OpenHandle(*promise->Result()),
// i::ReadOnlyRoots(CcTest::i_isolate()).exception());
}
} // namespace
TEST(TerminateOnResumeAtUnhandledRejectionCppImpl) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
SetTerminateOnResumeDelegate delegate;
auto data = std::make_pair(isolate, &env);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
{
// We want to trigger a breapoint upon Promise rejection, but we will only
// get the callback if there is at least one JavaScript frame in the stack.
v8::Local<v8::Function> func =
v8::Function::New(env.local(), RejectPromiseThroughCpp,
v8::External::New(isolate, &data))
.ToLocalChecked();
CHECK(env->Global()
->Set(env.local(), v8_str("RejectPromiseThroughCpp"), func)
.FromJust());
CompileRun("RejectPromiseThroughCpp(); while (true) {}");
CHECK_EQ(delegate.break_count(), 0);
CHECK_EQ(delegate.exception_thrown_count(), 1);
}
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
namespace {
static void UnreachableMicrotask(
const v8::FunctionCallbackInfo<v8::Value>& info) {
UNREACHABLE();
}
} // namespace
TEST(TerminateOnResumeFromMicrotask) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
SetTerminateOnResumeDelegate delegate(
SetTerminateOnResumeDelegate::kPerformMicrotaskCheckpointAtBreakpoint);
ChangeBreakOnException(true, true);
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
{
v8::TryCatch try_catch(env->GetIsolate());
// Enqueue a microtask that gets run while we are paused at the breakpoint.
v8::Local<v8::Function> foo = CompileFunction(
&env, "function foo(){ Promise.reject(); while (true) {} }", "foo");
env->GetIsolate()->EnqueueMicrotask(foo);
env->GetIsolate()->EnqueueMicrotask(
v8::Function::New(env.local(), UnreachableMicrotask).ToLocalChecked());
CHECK_EQ(2,
CcTest::i_isolate()->native_context()->microtask_queue()->size());
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
CHECK_EQ(0,
CcTest::i_isolate()->native_context()->microtask_queue()->size());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 0);
CHECK_EQ(delegate.exception_thrown_count(), 1);
}
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
class FutexInterruptionThread : public v8::base::Thread {
public:
FutexInterruptionThread(v8::Isolate* isolate, v8::base::Semaphore* sem)
: Thread(Options("FutexInterruptionThread")),
isolate_(isolate),
sem_(sem) {}
void Run() override {
// Wait a bit before terminating.
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
sem_->Wait();
v8::debug::SetTerminateOnResume(isolate_);
}
private:
v8::Isolate* isolate_;
v8::base::Semaphore* sem_;
};
namespace {
class SemaphoreTriggerOnBreak : public v8::debug::DebugDelegate {
public:
SemaphoreTriggerOnBreak() : sem_(0) {}
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>&
inspector_break_points_hit) override {
break_count_++;
sem_.Signal();
}
v8::base::Semaphore* semaphore() { return &sem_; }
int break_count() const { return break_count_; }
private:
v8::base::Semaphore sem_;
int break_count_ = 0;
};
} // anonymous namespace
TEST(TerminateOnResumeFromOtherThread) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
SemaphoreTriggerOnBreak delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
FutexInterruptionThread timeout_thread(env->GetIsolate(),
delegate.semaphore());
CHECK(timeout_thread.Start());
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
const char* source = "debugger; while(true){};";
v8::ScriptCompiler::Source script_source(v8_str(source));
v8::Local<v8::Function> foo =
v8::ScriptCompiler::CompileFunctionInContext(
env.local(), &script_source, 0, nullptr, 0, nullptr)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}
namespace {
class InterruptionBreakRightNow : public v8::base::Thread {
public:
explicit InterruptionBreakRightNow(v8::Isolate* isolate)
: Thread(Options("FutexInterruptionThread")), isolate_(isolate) {}
void Run() override {
// Wait a bit before terminating.
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
isolate_->RequestInterrupt(BreakRightNow, nullptr);
}
private:
static void BreakRightNow(v8::Isolate* isolate, void* data) {
v8::debug::BreakRightNow(isolate);
}
v8::Isolate* isolate_;
};
} // anonymous namespace
TEST(TerminateOnResumeAtInterruptFromOtherThread) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
ChangeBreakOnException(true, true);
SetTerminateOnResumeDelegate delegate;
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
InterruptionBreakRightNow timeout_thread(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
{
v8::TryCatch try_catch(env->GetIsolate());
const char* source = "while(true){}";
v8::ScriptCompiler::Source script_source(v8_str(source));
v8::Local<v8::Function> foo =
v8::ScriptCompiler::CompileFunctionInContext(
env.local(), &script_source, 0, nullptr, 0, nullptr)
.ToLocalChecked();
CHECK(timeout_thread.Start());
v8::MaybeLocal<v8::Value> val =
foo->Call(context, env->Global(), 0, nullptr);
CHECK(val.IsEmpty());
CHECK(try_catch.HasTerminated());
CHECK_EQ(delegate.break_count(), 1);
}
// Exiting the TryCatch brought the isolate back to a state where JavaScript
// can be executed.
ExpectInt32("1 + 1", 2);
v8::debug::SetDebugDelegate(env->GetIsolate(), nullptr);
CheckDebuggerUnloaded();
}