Files
v8/test/debugger/debug/debug-eval-scope.js
Simon Zünd e58e145c8c [debug] Walk outer lexical scopes via DebugScriptScope
Now that DebugScriptScopeInfo provides the full lexical scope tree for
every script, ScopeIterator no longer switches to context-only
iteration when it leaves the paused closure. Instead it keeps walking
DebugScriptScope into outer lexical scopes, including outer scopes
that don't need a context. Stack-allocated variables of such scopes
are reported as unavailable. Iteration falls back to the context chain
once it leaves the root of the scope tree (e.g. for eval and wrapped
functions).

Empty outer scopes are still skipped for now; a follow-up changes that.
[[Scopes]] is unaffected, since the inspector builds it from the runtime
context chain.

- GetFunctionDebugName() returns undefined for outer scopes whose
  enclosing function has no context, instead of using the name of an
  unrelated closure context.
- DebugEvaluate uses the closest runtime context when targeting an
  outer scope without a context (EvaluationContext()), and always wraps
  it for contextless target scopes so the scope's blocklist applies,
  even if the closest context is the native context. Otherwise
  evaluating `x` in a contextless scope that declares `x` would find a
  global `x`.

R=pfaffe@chromium.org

Bug: 545412300
Change-Id: Iaee6cfb7e1bee13c0bd6c3ff61d7e9c82a9b8491
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8460005
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110376}
2026-10-06 21:52:25 -07:00

146 lines
5.4 KiB
JavaScript

// Copyright 2016 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.
// Test that the (strict) eval scope is visible to the debugger.
var Debug = debug.Debug;
var exception = null;
var delegate = null;
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
delegate(exec_state);
} catch (e) {
exception = e;
}
}
Debug.setListener(listener);
// Current function is the top-level eval.
// We can access stack- and context-allocated values in the eval-scope.
delegate = function(exec_state) {
assertEquals([ debug.ScopeType.Eval,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(0).allScopes().map(s => s.scopeType()));
var scope = exec_state.frame(0).scope(0);
assertEquals(1, scope.scopeObject().property("a").value().value());
assertEquals(1, exec_state.frame(0).evaluate("a").value());
scope.setVariableValue("a", 2);
assertEquals(2, exec_state.frame(0).evaluate("a++").value());
}
eval("'use strict'; \n" +
"var a = 1; \n" +
"debugger; \n" +
"assertEquals(3, a);\n");
eval("'use strict'; \n" +
"var a = 1; \n" +
"(x=>a); \n" + // Force context-allocation.
"debugger; \n" +
"assertEquals(3, a);\n");
// Current function is an inner function.
// We cannot access stack-allocated values in the eval-scope.
delegate = function(exec_state) {
assertEquals([ debug.ScopeType.Local,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(0).allScopes().map(s => s.scopeType()));
assertEquals([ debug.ScopeType.Eval,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(1).allScopes().map(s => s.scopeType()));
var scope = exec_state.frame(0).scope(0);
assertThrows(() => exec_state.frame(0).evaluate("a"), ReferenceError);
assertTrue(scope.scopeObject().property("a").isUndefined());
}
eval("'use strict'; \n" +
"var a = 1; \n" +
"(() => {debugger})()\n");
// Current function is an escaped inner function.
delegate = function(exec_state) {
assertEquals([ debug.ScopeType.Local,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(0).allScopes().map(s => s.scopeType()));
assertEquals([ debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(1).allScopes().map(s => s.scopeType()));
var scope = exec_state.frame(0).scope(0);
assertThrows(() => exec_state.frame(0).evaluate("a"), ReferenceError);
assertTrue(scope.scopeObject().property("a").isUndefined());
}
var f = eval("'use strict'; \n" +
"var a = 1; \n" +
"() => {debugger}\n");
f();
// Current function is an inner function.
// We can access context-allocated values in the eval-scope.
delegate = function(exec_state) {
assertEquals([ debug.ScopeType.Local,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(0).allScopes().map(s => s.scopeType()));
assertEquals([ debug.ScopeType.Eval,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(1).allScopes().map(s => s.scopeType()));
var scope = exec_state.frame(1).scope(0);
assertEquals(1, scope.scopeObject().property("a").value().value());
assertEquals(1, exec_state.frame(1).evaluate("a").value());
assertEquals(1, exec_state.frame(0).evaluate("a").value());
scope.setVariableValue("a", 2);
assertEquals(2, exec_state.frame(0).evaluate("a++").value());
assertEquals(3, exec_state.frame(1).evaluate("a++").value());
}
eval("'use strict'; \n" +
"var a = 1; \n" +
"(() => { a; \n" + // Force context-allocation.
" debugger; \n" +
" assertEquals(4, a);\n" +
" })(); \n"
);
// Current function is an escaped inner function.
// We can access context-allocated values in the eval-scope.
delegate = function(exec_state) {
assertEquals([ debug.ScopeType.Local,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(0).allScopes().map(s => s.scopeType()));
assertEquals([ debug.ScopeType.Script,
debug.ScopeType.Global ],
exec_state.frame(1).allScopes().map(s => s.scopeType()));
var scope = exec_state.frame(0).scope(1);
assertEquals(1, scope.scopeObject().property("a").value().value());
assertEquals(1, exec_state.frame(0).evaluate("a").value());
scope.setVariableValue("a", 2);
assertEquals(2, exec_state.frame(0).evaluate("a++").value());
}
var g = eval("'use strict'; \n" +
"var a = 1; \n" +
"() => { a; \n" +
" debugger; \n" +
" assertEquals(3, a);\n" +
" } \n");
g();
Debug.setListener(null);
assertNull(exception);