@@ -3717,6 +3717,133 @@ TEST(DebugBreakOffThreadTerminate) {
37173717 CHECK (try_catch.HasTerminated ());
37183718}
37193719
3720+ class ArchiveRestoreThread : public v8 ::base::Thread,
3721+ public v8::debug::DebugDelegate {
3722+ public:
3723+ ArchiveRestoreThread (v8::Isolate* isolate, int spawn_count)
3724+ : Thread(Options(" ArchiveRestoreThread" )),
3725+ isolate_ (isolate),
3726+ debug_(reinterpret_cast <i::Isolate*>(isolate_)->debug()),
3727+ spawn_count_(spawn_count),
3728+ break_count_(0 ) {}
3729+
3730+ virtual void Run () {
3731+ v8::Locker locker (isolate_);
3732+ isolate_->Enter ();
3733+
3734+ v8::HandleScope scope (isolate_);
3735+ v8::Local<v8::Context> context = v8::Context::New (isolate_);
3736+ v8::Context::Scope context_scope (context);
3737+
3738+ v8::Local<v8::Function> test = CompileFunction (isolate_,
3739+ " function test(n) {\n "
3740+ " debugger;\n "
3741+ " return n + 1;\n "
3742+ " }\n " ,
3743+ " test" );
3744+
3745+ debug_->SetDebugDelegate (this );
3746+ v8::internal::DisableBreak enable_break (debug_, false );
3747+
3748+ v8::Local<v8::Value> args[1 ] = {v8::Integer::New (isolate_, spawn_count_)};
3749+
3750+ int result = test->Call (context, context->Global (), 1 , args)
3751+ .ToLocalChecked ()
3752+ ->Int32Value (context)
3753+ .FromJust ();
3754+
3755+ // Verify that test(spawn_count_) returned spawn_count_ + 1.
3756+ CHECK_EQ (spawn_count_ + 1 , result);
3757+
3758+ isolate_->Exit ();
3759+ }
3760+
3761+ void BreakProgramRequested (v8::Local<v8::Context> context,
3762+ const std::vector<v8::debug::BreakpointId>&) {
3763+ auto stack_traces = v8::debug::StackTraceIterator::Create (isolate_);
3764+ if (!stack_traces->Done ()) {
3765+ v8::debug::Location location = stack_traces->GetSourceLocation ();
3766+
3767+ i::PrintF (" ArchiveRestoreThread #%d hit breakpoint at line %d\n " ,
3768+ spawn_count_, location.GetLineNumber ());
3769+
3770+ switch (location.GetLineNumber ()) {
3771+ case 1 : // debugger;
3772+ CHECK_EQ (break_count_, 0 );
3773+
3774+ // Attempt to stop on the next line after the first debugger
3775+ // statement. If debug->{Archive,Restore}Debug() improperly reset
3776+ // thread-local debug information, the debugger will fail to stop
3777+ // before the test function returns.
3778+ debug_->PrepareStep (StepNext);
3779+
3780+ // Spawning threads while handling the current breakpoint verifies
3781+ // that the parent thread correctly archived and restored the
3782+ // state necessary to stop on the next line. If not, then control
3783+ // will simply continue past the `return n + 1` statement.
3784+ MaybeSpawnChildThread ();
3785+
3786+ break ;
3787+
3788+ case 2 : // return n + 1;
3789+ CHECK_EQ (break_count_, 1 );
3790+ break ;
3791+
3792+ default :
3793+ CHECK (false );
3794+ }
3795+ }
3796+
3797+ ++break_count_;
3798+ }
3799+
3800+ void MaybeSpawnChildThread () {
3801+ if (spawn_count_ > 1 ) {
3802+ v8::Unlocker unlocker (isolate_);
3803+
3804+ // Spawn a thread that spawns a thread that spawns a thread (and so
3805+ // on) so that the ThreadManager is forced to archive and restore
3806+ // the current thread.
3807+ ArchiveRestoreThread child (isolate_, spawn_count_ - 1 );
3808+ child.Start ();
3809+ child.Join ();
3810+
3811+ // The child thread sets itself as the debug delegate, so we need to
3812+ // usurp it after the child finishes, or else future breakpoints
3813+ // will be delegated to a destroyed ArchiveRestoreThread object.
3814+ debug_->SetDebugDelegate (this );
3815+
3816+ // This is the most important check in this test, since
3817+ // child.GetBreakCount() will return 1 if the debugger fails to stop
3818+ // on the `return n + 1` line after the grandchild thread returns.
3819+ CHECK_EQ (child.GetBreakCount (), 2 );
3820+ }
3821+ }
3822+
3823+ int GetBreakCount () { return break_count_; }
3824+
3825+ private:
3826+ v8::Isolate* isolate_;
3827+ v8::internal::Debug* debug_;
3828+ const int spawn_count_;
3829+ int break_count_;
3830+ };
3831+
3832+ TEST (DebugArchiveRestore) {
3833+ v8::Isolate::CreateParams create_params;
3834+ create_params.array_buffer_allocator = CcTest::array_buffer_allocator ();
3835+ v8::Isolate* isolate = v8::Isolate::New (create_params);
3836+
3837+ ArchiveRestoreThread thread (isolate, 5 );
3838+ // Instead of calling thread.Start() and thread.Join() here, we call
3839+ // thread.Run() directly, to make sure we exercise archive/restore
3840+ // logic on the *current* thread as well as other threads.
3841+ thread.Run ();
3842+ CHECK_EQ (thread.GetBreakCount (), 2 );
3843+
3844+ isolate->Dispose ();
3845+ }
3846+
37203847class DebugEventExpectNoException : public v8 ::debug::DebugDelegate {
37213848 public:
37223849 void ExceptionThrown (v8::Local<v8::Context> paused_context,
0 commit comments