diff options
author | Ben Newman <ben@meteor.com> | 2018-08-04 11:28:29 -0400 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2018-09-06 08:54:44 +0200 |
commit | 5e9ed6d924db97462fb208e7ad8f32acce2a9bf3 (patch) | |
tree | 230b3207b1a72c4dfddfbee59e1dcca7f245f94c /deps/v8/test | |
parent | 4c34302b7a2ea5bfdcfe9313a27c214c181491f3 (diff) | |
download | node-new-5e9ed6d924db97462fb208e7ad8f32acce2a9bf3.tar.gz |
deps: backport a8f6869 from upstream V8
Original commit message:
[debug] Fully implement Debug::ArchiveDebug and Debug::RestoreDebug.
I have a project that embeds V8 and uses a single `Isolate` from multiple
threads. The program runs just fine, but sometimes the inspector doesn't
stop on the correct line after stepping over a statement that switches
threads behind the scenes, even though the original thread is restored by
the time the next statement is executed.
After some digging, I discovered that the `Debug::ArchiveDebug` and
`Debug::RestoreDebug` methods, which should be responsible for
saving/restoring this `ThreadLocal` information when switching threads,
currently don't do anything.
This commit implements those methods using MemCopy, in the style of other
Archive/Restore methods in the V8 codebase.
Related: https://groups.google.com/forum/#!topic/v8-users/_Qf2rwljRk8
R=yangguo@chromium.org,jgruber@chromium.org
CC=info@bnoordhuis.nl
Bug: v8:7230
Change-Id: Id517c873eb81cd53f7216c7efd441b956cf7f943
Reviewed-on: https://chromium-review.googlesource.com/833260
Commit-Queue: Yang Guo <yangguo@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54902}
Refs: https://github.com/v8/v8/commit/a8f6869177685cfb9c199c454a86f4698c260515
Fix build errors by matching older V8 APIs used by Node.
It looks like
SetDebugDelegate(debug::DebugDelegate* delegate, bool pass_ownership)
was simplified to just
SetDebugDelegate(debug::DebugDelegate* delegate)
in https://github.com/v8/v8/commit/37dcd837dbafa7f1175be5f01f0def013437c7e7,
but the extra `pass_ownership` parameter is still there in the current
version of `node/deps/v8`. I should be able to fix those tests by passing
`false` for `pass_ownership`.
Also, the `DebugDelegate::BreakProgramRequested` method lost a parameter
in https://github.com/v8/v8/commit/e404670696b4c49d7f8adcdb075b98acab9967dd,
but it's not a parameter I was using in my test, so there shouldn't be any
harm in adding the `exec_state` parameter back to `BreakProgramRequested`
(and continuing to ignore it).
Skip restoring debug state unless thread previously in DebugScope.
A simpler version of the changes I proposed upstream in this V8 change
request: https://chromium-review.googlesource.com/c/v8/v8/+/1168449
In this version, Debug::RestoreDebug never attempts to enter a new
DebugScope, but merely reuses the previous one, if we're returning to a
thread that was previously in a DebugScope. If the thread was not
previously in a DebugScope, I believe it does not need to have any
debugging state restored with ClearOneShot and PrepareStep.
The tests from https://chromium-review.googlesource.com/c/v8/v8/+/833260
still pass, and the failing V8-CI tests are now passing locally for me.
PR-URL: https://github.com/nodejs/node/pull/22122
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Diffstat (limited to 'deps/v8/test')
-rw-r--r-- | deps/v8/test/cctest/test-debug.cc | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/deps/v8/test/cctest/test-debug.cc b/deps/v8/test/cctest/test-debug.cc index f3daf05a9e..92abcae16e 100644 --- a/deps/v8/test/cctest/test-debug.cc +++ b/deps/v8/test/cctest/test-debug.cc @@ -6221,6 +6221,135 @@ TEST(DebugBreakOffThreadTerminate) { } +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) {} + + virtual void Run() { + v8::Locker locker(isolate_); + isolate_->Enter(); + + v8::HandleScope scope(isolate_); + v8::Local<v8::Context> context = v8::Context::New(isolate_); + v8::Context::Scope context_scope(context); + + v8::Local<v8::Function> test = CompileFunction(isolate_, + "function test(n) {\n" + " debugger;\n" + " return n + 1;\n" + "}\n", + "test"); + + debug_->SetDebugDelegate(this, false); + 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); + + isolate_->Exit(); + } + + void BreakProgramRequested(v8::Local<v8::Context> context, + v8::Local<v8::Object> exec_state, + const std::vector<v8::debug::BreakpointId>&) { + 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()); + + switch (location.GetLineNumber()) { + case 1: // debugger; + CHECK_EQ(break_count_, 0); + + // Attempt to stop on the next line after the first debugger + // statement. If debug->{Archive,Restore}Debug() improperly reset + // thread-local debug information, the debugger will fail to stop + // before the test function returns. + debug_->PrepareStep(StepNext); + + // Spawning threads while handling the current breakpoint verifies + // that the parent thread correctly archived and restored the + // state necessary to stop on the next line. If not, then control + // will simply continue past the `return n + 1` statement. + MaybeSpawnChildThread(); + + break; + + case 2: // return n + 1; + CHECK_EQ(break_count_, 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); + 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, false); + + // This is the most important check in this test, since + // child.GetBreakCount() will return 1 if the debugger fails to stop + // on the `return n + 1` line after the grandchild thread returns. + CHECK_EQ(child.GetBreakCount(), 2); + } + } + + int GetBreakCount() { return break_count_; } + + private: + v8::Isolate* isolate_; + v8::internal::Debug* debug_; + const int spawn_count_; + int break_count_; +}; + +TEST(DebugArchiveRestore) { + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); + v8::Isolate* isolate = v8::Isolate::New(create_params); + + ArchiveRestoreThread thread(isolate, 5); + // 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(), 2); + + isolate->Dispose(); +} + + static void DebugEventExpectNoException( const v8::Debug::EventDetails& event_details) { v8::DebugEvent event = event_details.GetEvent(); |