7994004493
EphemeronHashTable does not trigger interrupts when accessed (as opposed to calling the WeakMapGet builtin), so it avoids the use-after-free problem when reading exception metadata triggers session disconnect while holding a reference to the session. Bug: chromium:1241860 Change-Id: I29264b04b8daf682e7c33a97faedf50e323d57c4 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3158326 Commit-Queue: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/main@{#76864}
5694 lines
189 KiB
C++
5694 lines
189 KiB
C++
// 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 "include/v8-extension.h"
|
|
#include "include/v8-function.h"
|
|
#include "include/v8-json.h"
|
|
#include "include/v8-locker.h"
|
|
#include "src/api/api-inl.h"
|
|
#include "src/base/strings.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/init/v8.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::StepInto; // From StepAction enum
|
|
using ::v8::internal::StepNone; // From StepAction enum
|
|
using ::v8::internal::StepOut; // From StepAction enum
|
|
using ::v8::internal::StepOver; // 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(v8::Isolate* isolate, bool caught,
|
|
bool uncaught) {
|
|
v8::internal::Debug* debug =
|
|
reinterpret_cast<v8::internal::Isolate*>(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::EphemeronTable> weakmap =
|
|
v8::debug::EphemeronTable::New(env->GetIsolate());
|
|
v8::Local<v8::Object> key = v8::Object::New(env->GetIsolate());
|
|
CHECK(!weakmap->Set(env->GetIsolate(), key, v8_num(1)).IsEmpty());
|
|
CHECK(!weakmap->Get(env->GetIsolate(), key).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;
|
|
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) }");
|
|
CompileRun(
|
|
"%PrepareFunctionForOptimization(test);"
|
|
"test(0.5); test(0.6);"
|
|
"%DisableOptimizationFinalization();"
|
|
"%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(
|
|
"%FinalizeOptimization();"
|
|
"%GetOptimizationStatus(test);");
|
|
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(Regress1163547) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
DebugEventCounter delegate;
|
|
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
|
|
|
|
i::Handle<i::BreakPoint> bp;
|
|
|
|
auto constructor_tmpl = v8::FunctionTemplate::New(env->GetIsolate());
|
|
auto prototype_tmpl = constructor_tmpl->PrototypeTemplate();
|
|
auto accessor_tmpl =
|
|
v8::FunctionTemplate::New(env->GetIsolate(), NoOpFunctionCallback);
|
|
prototype_tmpl->SetAccessorProperty(v8_str("f"), accessor_tmpl);
|
|
|
|
auto constructor =
|
|
constructor_tmpl->GetFunction(env.local()).ToLocalChecked();
|
|
env->Global()->Set(env.local(), v8_str("C"), constructor).ToChecked();
|
|
|
|
CompileRun("o = new C();");
|
|
v8::Local<v8::Function> function =
|
|
CompileRun("Object.getOwnPropertyDescriptor(C.prototype, 'f').get")
|
|
.As<v8::Function>();
|
|
|
|
// === Test API accessor ===
|
|
break_point_hit_count = 0;
|
|
|
|
// At this point, the C.prototype - which holds the "f" accessor - is in
|
|
// dictionary mode.
|
|
auto constructor_fun =
|
|
Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*constructor));
|
|
CHECK(!i::JSObject::cast(constructor_fun->prototype()).HasFastProperties());
|
|
|
|
// Run with breakpoint.
|
|
bp = SetBreakPoint(function, 0);
|
|
|
|
CompileRun("o.f");
|
|
CHECK_EQ(1, 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;
|
|
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(StepInto);
|
|
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(StepOver);
|
|
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(StepOver);
|
|
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(StepOver);
|
|
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(StepOver);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
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(StepInto);
|
|
|
|
// 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(StepInto);
|
|
|
|
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(env->GetIsolate(),
|
|
v8_str(env->GetIsolate(), script_name));
|
|
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(isolate, 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::base::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(isolate, 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(env->GetIsolate(), 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;
|
|
};
|
|
|
|
UNINITIALIZED_TEST(NoBreakOnStackOverflow) {
|
|
// We must set FLAG_stack_size before initializing the isolate.
|
|
i::FLAG_stack_size = 100;
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
{
|
|
LocalContext env(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
|
|
ChangeBreakOnException(isolate, true, true);
|
|
|
|
ExceptionEventCounter delegate;
|
|
v8::debug::SetDebugDelegate(isolate, &delegate);
|
|
CHECK_EQ(0, delegate.exception_event_count);
|
|
|
|
CompileRun(
|
|
"function f() { return f(); }"
|
|
"try { f() } catch {}");
|
|
|
|
CHECK_EQ(0, delegate.exception_event_count);
|
|
}
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
}
|
|
|
|
// 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.
|
|
|
|
v8::base::EmbeddedVector<char, 1024> buffer;
|
|
v8::base::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 StepOver 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(StepOver);
|
|
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(StepOver);
|
|
|
|
// 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(isolate, 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(StepOver);
|
|
}
|
|
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 (i::Builtin builtin = i::Builtins::kFirst; builtin <= i::Builtins::kLast;
|
|
++builtin) {
|
|
i::Code code = builtins->code(builtin);
|
|
if (code.kind() != i::CodeKind::BUILTIN) continue;
|
|
auto prediction = code.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"), false);
|
|
std::unordered_map<int, int> count_by_type;
|
|
{
|
|
i::DisallowGarbageCollection 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);
|
|
#if V8_ENABLE_WEBASSEMBLY
|
|
CHECK_EQ(count_by_type[i::Script::TYPE_WASM], 0);
|
|
#endif // V8_ENABLE_WEBASSEMBLY
|
|
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) {
|
|
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) {
|
|
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) {
|
|
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(env->GetIsolate(), 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(env->GetIsolate(), 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(isolate, 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(env->GetIsolate(), 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* enter,
|
|
v8::base::Semaphore* exit)
|
|
: Thread(Options("FutexInterruptionThread")),
|
|
isolate_(isolate),
|
|
enter_(enter),
|
|
exit_(exit) {}
|
|
|
|
void Run() override {
|
|
enter_->Wait();
|
|
v8::debug::SetTerminateOnResume(isolate_);
|
|
exit_->Signal();
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* isolate_;
|
|
v8::base::Semaphore* enter_;
|
|
v8::base::Semaphore* exit_;
|
|
};
|
|
|
|
namespace {
|
|
class SemaphoreTriggerOnBreak : public v8::debug::DebugDelegate {
|
|
public:
|
|
SemaphoreTriggerOnBreak() : enter_(0), exit_(0) {}
|
|
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
|
|
const std::vector<v8::debug::BreakpointId>&
|
|
inspector_break_points_hit) override {
|
|
break_count_++;
|
|
enter_.Signal();
|
|
exit_.Wait();
|
|
}
|
|
|
|
v8::base::Semaphore* enter() { return &enter_; }
|
|
v8::base::Semaphore* exit() { return &exit_; }
|
|
int break_count() const { return break_count_; }
|
|
|
|
private:
|
|
v8::base::Semaphore enter_;
|
|
v8::base::Semaphore exit_;
|
|
int break_count_ = 0;
|
|
};
|
|
} // anonymous namespace
|
|
|
|
TEST(TerminateOnResumeFromOtherThread) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
ChangeBreakOnException(env->GetIsolate(), true, true);
|
|
|
|
SemaphoreTriggerOnBreak delegate;
|
|
v8::debug::SetDebugDelegate(env->GetIsolate(), &delegate);
|
|
|
|
FutexInterruptionThread timeout_thread(env->GetIsolate(), delegate.enter(),
|
|
delegate.exit());
|
|
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("InterruptionBreakRightNow")), 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(env->GetIsolate(), 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();
|
|
}
|