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}
377 lines
9.1 KiB
JavaScript
377 lines
9.1 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.
|
|
// Flags: --no-turbofan
|
|
|
|
// The first part of this file is copied over from debug-set-variable-value.js
|
|
// (a few tests were removed because they make no sense for modules). The second
|
|
// part is new.
|
|
|
|
var Debug = debug.Debug;
|
|
|
|
// Accepts a function/closure 'fun' that must have a debugger statement inside.
|
|
// A variable 'variable_name' must be initialized before debugger statement
|
|
// and returned after the statement. The test will alter variable value when
|
|
// on debugger statement and check that returned value reflects the change.
|
|
function RunPauseTest(scope_number, expected_old_result, variable_name,
|
|
new_value, expected_new_result, fun) {
|
|
var actual_old_result = fun();
|
|
assertEquals(expected_old_result, actual_old_result);
|
|
|
|
var listener_delegate;
|
|
var listener_called = false;
|
|
var exception = null;
|
|
|
|
function listener_delegate(exec_state) {
|
|
var scope = exec_state.frame(0).scope(scope_number);
|
|
scope.setVariableValue(variable_name, new_value);
|
|
}
|
|
|
|
function listener(event, exec_state, event_data, data) {
|
|
try {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
listener_called = true;
|
|
listener_delegate(exec_state);
|
|
}
|
|
} catch (e) {
|
|
exception = e;
|
|
}
|
|
}
|
|
|
|
// Add the debug event listener.
|
|
Debug.setListener(listener);
|
|
|
|
var actual_new_result;
|
|
try {
|
|
actual_new_result = fun();
|
|
} finally {
|
|
Debug.setListener(null);
|
|
}
|
|
|
|
if (exception != null) {
|
|
assertUnreachable("Exception in listener\n" + exception.stack);
|
|
}
|
|
assertTrue(listener_called);
|
|
|
|
assertEquals(expected_new_result, actual_new_result);
|
|
}
|
|
|
|
|
|
function ClosureTestCase(scope_index, old_result, variable_name, new_value,
|
|
new_result, success_expected, factory) {
|
|
this.scope_index_ = scope_index;
|
|
this.old_result_ = old_result;
|
|
this.variable_name_ = variable_name;
|
|
this.new_value_ = new_value;
|
|
this.new_result_ = new_result;
|
|
this.success_expected_ = success_expected;
|
|
this.factory_ = factory;
|
|
}
|
|
|
|
ClosureTestCase.prototype.run_pause_test = function() {
|
|
var th = this;
|
|
var fun = this.factory_(true);
|
|
this.run_and_catch_(function() {
|
|
RunPauseTest(th.scope_index_ + 1, th.old_result_, th.variable_name_,
|
|
th.new_value_, th.new_result_, fun);
|
|
});
|
|
}
|
|
|
|
ClosureTestCase.prototype.run_and_catch_ = function(runnable) {
|
|
if (this.success_expected_) {
|
|
runnable();
|
|
} else {
|
|
assertThrows(runnable);
|
|
}
|
|
}
|
|
|
|
|
|
// Test scopes visible from closures.
|
|
|
|
var closure_test_cases = [
|
|
new ClosureTestCase(0, 'cat', 'v1', 5, 5, true,
|
|
function Factory(debug_stop) {
|
|
var v1 = 'cat';
|
|
return function() {
|
|
if (debug_stop) debugger;
|
|
return v1;
|
|
}
|
|
}),
|
|
|
|
new ClosureTestCase(0, 4, 't', 7, 9, true, function Factory(debug_stop) {
|
|
var t = 2;
|
|
var r = eval("t");
|
|
return function() {
|
|
if (debug_stop) debugger;
|
|
return r + t;
|
|
}
|
|
}),
|
|
|
|
new ClosureTestCase(0, 6, 't', 10, 13, true, function Factory(debug_stop) {
|
|
var t = 2;
|
|
var r = eval("t = 3");
|
|
return function() {
|
|
if (debug_stop) debugger;
|
|
return r + t;
|
|
}
|
|
}),
|
|
|
|
new ClosureTestCase(2, 'capybara', 'foo', 77, 77, true,
|
|
function Factory(debug_stop) {
|
|
var foo = "capybara";
|
|
return (function() {
|
|
var bar = "fish";
|
|
try {
|
|
throw {name: "test exception"};
|
|
} catch (e) {
|
|
return function() {
|
|
if (debug_stop) debugger;
|
|
bar = "beast";
|
|
return foo;
|
|
}
|
|
}
|
|
})();
|
|
}),
|
|
|
|
new ClosureTestCase(0, 'AlphaBeta', 'eee', 5, '5Beta', true,
|
|
function Factory(debug_stop) {
|
|
var foo = "Beta";
|
|
return (function() {
|
|
var bar = "fish";
|
|
try {
|
|
throw "Alpha";
|
|
} catch (eee) {
|
|
return function() {
|
|
if (debug_stop) debugger;
|
|
return eee + foo;
|
|
}
|
|
}
|
|
})();
|
|
})
|
|
];
|
|
|
|
for (var i = 0; i < closure_test_cases.length; i++) {
|
|
closure_test_cases[i].run_pause_test();
|
|
}
|
|
|
|
|
|
// Test local scope.
|
|
|
|
RunPauseTest(0, 'HelloYou', 'u', 'We', 'HelloWe', (function Factory() {
|
|
return function() {
|
|
var u = "You";
|
|
var v = "Hello";
|
|
debugger;
|
|
return v + u;
|
|
}
|
|
})());
|
|
|
|
RunPauseTest(0, 'Helloworld', 'p', 'GoodBye', 'HelloGoodBye',
|
|
(function Factory() {
|
|
function H(p) {
|
|
var v = "Hello";
|
|
debugger;
|
|
return v + p;
|
|
}
|
|
return function() {
|
|
return H("world");
|
|
}
|
|
})());
|
|
|
|
RunPauseTest(0, 'mouse', 'v1', 'dog', 'dog', (function Factory() {
|
|
return function() {
|
|
var v1 = 'cat';
|
|
eval("v1 = 'mouse'");
|
|
debugger;
|
|
return v1;
|
|
}
|
|
})());
|
|
|
|
// Check that we correctly update local variable that
|
|
// is referenced from an inner closure.
|
|
RunPauseTest(0, 'Blue', 'v', 'Green', 'Green', (function Factory() {
|
|
return function() {
|
|
function A() {
|
|
var v = "Blue";
|
|
function Inner() {
|
|
return void v;
|
|
}
|
|
debugger;
|
|
return v;
|
|
}
|
|
return A();
|
|
}
|
|
})());
|
|
|
|
// Check that we correctly update parameter, that is known to be stored
|
|
// both on stack and in heap.
|
|
RunPauseTest(0, 5, 'p', 2012, 2012, (function Factory() {
|
|
return function() {
|
|
function A(p) {
|
|
function Inner() {
|
|
return void p;
|
|
}
|
|
debugger;
|
|
return p;
|
|
}
|
|
return A(5);
|
|
}
|
|
})());
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// From here on we test the module scope.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// Non-existing variable.
|
|
{
|
|
let exception;
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let module_scope = exec_state.frame().scope(1);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
try {
|
|
module_scope.setVariableValue('spargel', 42);
|
|
} catch(e) { exception = e; }
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
assertThrows(() => spargel, ReferenceError);
|
|
debugger;
|
|
assertThrows(() => spargel, ReferenceError);
|
|
assertTrue(exception !== undefined);
|
|
}
|
|
|
|
|
|
// Local (non-exported) variable.
|
|
let salat = 12;
|
|
{
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let module_scope = exec_state.frame().scope(1);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
module_scope.setVariableValue('salat', 42);
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
assertEquals(12, salat);
|
|
debugger;
|
|
assertEquals(42, salat);
|
|
}
|
|
|
|
|
|
// Local (non-exported) variable, nested access.
|
|
let salad = 12;
|
|
{
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let scope_count = exec_state.frame().scopeCount();
|
|
let module_scope = exec_state.frame().scope(2);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
module_scope.setVariableValue('salad', 42);
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
function foo() {
|
|
assertEquals(12, salad);
|
|
debugger;
|
|
assertEquals(42, salad);
|
|
};
|
|
foo();
|
|
}
|
|
|
|
|
|
// Exported variable.
|
|
export let salami = 1;
|
|
{
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let module_scope = exec_state.frame().scope(1);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
module_scope.setVariableValue('salami', 2);
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
assertEquals(1, salami);
|
|
debugger;
|
|
assertEquals(2, salami);
|
|
}
|
|
|
|
|
|
// Exported variable, nested access.
|
|
export let ham = 1;
|
|
{
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let scope_count = exec_state.frame().scopeCount();
|
|
let module_scope = exec_state.frame().scope(2);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
module_scope.setVariableValue('ham', 2);
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
function foo() {
|
|
assertEquals(1, ham);
|
|
debugger;
|
|
assertEquals(2, ham);
|
|
};
|
|
foo();
|
|
}
|
|
|
|
|
|
// Imported variable. Setting is currently not supported.
|
|
import { salami as wurst } from "./debug-modules-set-variable-value.mjs";
|
|
{
|
|
let exception;
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let module_scope = exec_state.frame().scope(1);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
try {
|
|
module_scope.setVariableValue('wurst', 3);
|
|
} catch(e) { exception = e; }
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
assertEquals(2, wurst);
|
|
debugger;
|
|
assertEquals(2, wurst);
|
|
assertTrue(exception !== undefined);
|
|
}
|
|
|
|
|
|
// Imported variable, nested access. Setting is currently not supported.
|
|
import { salami as wurstl } from "./debug-modules-set-variable-value.mjs";
|
|
{
|
|
let exception;
|
|
function listener(event, exec_state) {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
let scope_count = exec_state.frame().scopeCount();
|
|
let module_scope = exec_state.frame().scope(2);
|
|
assertEquals(debug.ScopeType.Module, module_scope.scopeType());
|
|
try {
|
|
module_scope.setVariableValue('wurstl', 3);
|
|
} catch(e) { exception = e; }
|
|
}
|
|
}
|
|
|
|
Debug.setListener(listener);
|
|
function foo() {
|
|
assertEquals(2, wurstl);
|
|
debugger;
|
|
assertEquals(2, wurstl);
|
|
assertTrue(exception !== undefined);
|
|
};
|
|
foo();
|
|
}
|
|
|
|
|
|
Debug.setListener(null);
|