diff --git a/common.gypi b/common.gypi index a6896876d45e4c..479fd0e6d7d60f 100644 --- a/common.gypi +++ b/common.gypi @@ -38,7 +38,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.31', + 'v8_embedder_string': '-node.32', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/debug/debug-scopes.cc b/deps/v8/src/debug/debug-scopes.cc index 1d6ceb72a0a108..d9595c2372b7e0 100644 --- a/deps/v8/src/debug/debug-scopes.cc +++ b/deps/v8/src/debug/debug-scopes.cc @@ -431,6 +431,22 @@ bool ScopeIterator::DeclaresLocals(Mode mode) const { } bool ScopeIterator::HasContext() const { + // In rare cases we pause in a scope that doesn't have its context pushed yet. + // E.g. when pausing in for-of loop headers (see https://crbug.com/399002824). + // + // We can detect this by comparing the scope ID of the parsed scope and the + // runtime scope. + // We can skip this check for function scopes, those will have their context + // always pushed. Also, there is an oddity where parsing::ParseFunction + // produces function scopes with (-1, -1) as the start/end position, + // which messes up the unique ID. + if (current_scope_ && !current_scope_->is_function_scope() && + NeedsContext() && + current_scope_->UniqueIdInScript() != + context_->scope_info()->UniqueIdInScript()) { + return false; + } + return !InInnerScope() || NeedsContext(); } @@ -475,7 +491,7 @@ void ScopeIterator::AdvanceScope() { DCHECK(InInnerScope()); do { - if (NeedsContext()) { + if (NeedsAndHasContext()) { // current_scope_ needs a context so moving one scope up requires us to // also move up one context. AdvanceOneContext(); @@ -538,6 +554,11 @@ void ScopeIterator::Next() { MaybeCollectAndStoreLocalBlocklists(); UnwrapEvaluationContext(); + DCHECK_IMPLIES(current_scope_ && !current_scope_->is_function_scope() && + NeedsAndHasContext(), + current_scope_->UniqueIdInScript() == + context_->scope_info()->UniqueIdInScript()); + if (leaving_closure) function_ = Handle(); } @@ -547,32 +568,33 @@ ScopeIterator::ScopeType ScopeIterator::Type() const { if (InInnerScope()) { switch (current_scope_->scope_type()) { case FUNCTION_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsFunctionContext() || - context_->IsDebugEvaluateContext()); + DCHECK_IMPLIES(NeedsAndHasContext(), + context_->IsFunctionContext() || + context_->IsDebugEvaluateContext()); return ScopeTypeLocal; case MODULE_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsModuleContext()); + DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsModuleContext()); return ScopeTypeModule; case SCRIPT_SCOPE: case REPL_MODE_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsScriptContext() || - IsNativeContext(*context_)); + DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsScriptContext() || + IsNativeContext(*context_)); return ScopeTypeScript; case WITH_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsWithContext()); + DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsWithContext()); return ScopeTypeWith; case CATCH_SCOPE: DCHECK(context_->IsCatchContext()); return ScopeTypeCatch; case BLOCK_SCOPE: case CLASS_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsBlockContext()); + DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsBlockContext()); return ScopeTypeBlock; case EVAL_SCOPE: - DCHECK_IMPLIES(NeedsContext(), context_->IsEvalContext()); + DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsEvalContext()); return ScopeTypeEval; case SHADOW_REALM_SCOPE: - DCHECK_IMPLIES(NeedsContext(), IsNativeContext(*context_)); + DCHECK_IMPLIES(NeedsAndHasContext(), IsNativeContext(*context_)); // TODO(v8:11989): New ScopeType for ShadowRealms? return ScopeTypeScript; } @@ -962,6 +984,12 @@ bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode, case VariableLocation::CONTEXT: if (mode == Mode::STACK) continue; + if (!HasContext()) { + // If the context was not yet pushed we report the variable as + // unavailable. + value = isolate_->factory()->the_hole_value(); + break; + } DCHECK(var->IsContextSlot()); DCHECK_EQ(context_->scope_info()->ContextSlotIndex(var->name()), index); diff --git a/deps/v8/src/debug/debug-scopes.h b/deps/v8/src/debug/debug-scopes.h index e02f1b73702e2b..f5736220f6bc0e 100644 --- a/deps/v8/src/debug/debug-scopes.h +++ b/deps/v8/src/debug/debug-scopes.h @@ -106,6 +106,7 @@ class V8_EXPORT_PRIVATE ScopeIterator { bool InInnerScope() const { return !function_.is_null(); } bool HasContext() const; bool NeedsContext() const; + bool NeedsAndHasContext() const { return NeedsContext() && HasContext(); } Handle CurrentContext() const { DCHECK(HasContext()); return context_; diff --git a/deps/v8/test/inspector/regress/regress-crbug-399002824-expected.txt b/deps/v8/test/inspector/regress/regress-crbug-399002824-expected.txt new file mode 100644 index 00000000000000..f94e3c7abebffc --- /dev/null +++ b/deps/v8/test/inspector/regress/regress-crbug-399002824-expected.txt @@ -0,0 +1,5 @@ +Don't crash when pausing on the iterator ".next" call + function crashMe() { + for (const #e of iter()) { + () => e; // Context allocate e. + diff --git a/deps/v8/test/inspector/regress/regress-crbug-399002824.js b/deps/v8/test/inspector/regress/regress-crbug-399002824.js new file mode 100644 index 00000000000000..45c06e7d5d8174 --- /dev/null +++ b/deps/v8/test/inspector/regress/regress-crbug-399002824.js @@ -0,0 +1,35 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const { session, Protocol, contextGroup } = + InspectorTest.start('Don\'t crash when pausing on the iterator ".next" call'); + +session.setupScriptMap(); +contextGroup.addScript(` + function *iter() { + yield 1; + debugger; + yield 2; + } + + function crashMe() { + for (const e of iter()) { + () => e; // Context allocate e. + } + } +`); + +Protocol.Debugger.enable(); + +(async () => { + let pausedPromise = Protocol.Debugger.oncePaused(); + Protocol.Runtime.evaluate({ expression: 'crashMe()' }); + + let { params: { callFrames } } = await pausedPromise; + await session.logSourceLocation(callFrames[1].location); + + Protocol.Debugger.resume(); + + InspectorTest.completeTest(); +})();