07cc86889c
This CL implements the heavy lifting for re-using block lists: - On local debug-evaluate, we check if the paused function already has a block list. If not, we do a full re-parse, calculate the block lists and stash them in the global map. - On a context lookup, we do the lookup slightly differently. The block lists now store "outer" locals, so we need to check the block list before we advance to the next context, not before we do the lookup in the current context. The CL also duplicates the debugger test that checks most of these shadowing edge cases. While we keep working on the new feature we still want to check both configurations, but the feature is too small to warrant a separate bot. Note that the file with the flag enabled has one additional test case that fails with the old implementation. Unfortunately it's non-trivial to fix in the old implementation. This CL drastically improves performance for conditional breakpoints as they use local debug-evaluate under the hood. The worst case example (https://crbug.com/1072939#c15) improves from 6.5 seconds to 100ms. R=jarin@chromium.org Bug: chromium:1363561 Change-Id: I85f3d908d246f0d2e31ed272f4db6a852b9dbc39 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3941584 Commit-Queue: Simon Zünd <szuend@chromium.org> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#83665}
249 lines
6.7 KiB
JavaScript
249 lines
6.7 KiB
JavaScript
// Copyright 2015 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-analyze-environment-liveness --experimental-reuse-locals-blocklists
|
|
|
|
// Test that debug-evaluate only resolves variables that are used by
|
|
// the function inside which we debug-evaluate. This is to avoid
|
|
// incorrect variable resolution when a context-allocated variable is
|
|
// shadowed by a stack-allocated variable.
|
|
//
|
|
// This test is an exact copy of `debug-evaluate-shadowed-context` modulo an
|
|
// experimental flag. While the feature is in developement, we want to test both
|
|
// configurations without having to introduce a separate bot.
|
|
|
|
Debug = debug.Debug
|
|
|
|
let test_name;
|
|
let listener_delegate;
|
|
let listener_called;
|
|
let exception;
|
|
let begin_test_count = 0;
|
|
let end_test_count = 0;
|
|
let break_count = 0;
|
|
|
|
// Debug event listener which delegates.
|
|
function listener(event, exec_state, event_data, data) {
|
|
try {
|
|
if (event == Debug.DebugEvent.Break) {
|
|
break_count++;
|
|
listener_called = true;
|
|
listener_delegate(exec_state);
|
|
}
|
|
} catch (e) {
|
|
exception = e;
|
|
print(e, e.stack);
|
|
}
|
|
}
|
|
Debug.setListener(listener);
|
|
|
|
function BeginTest(name) {
|
|
test_name = name;
|
|
listener_called = false;
|
|
exception = null;
|
|
begin_test_count++;
|
|
}
|
|
|
|
function EndTest() {
|
|
assertTrue(listener_called, "listener not called for " + test_name);
|
|
assertNull(exception, test_name + " / " + exception);
|
|
end_test_count++;
|
|
}
|
|
|
|
BeginTest("Check that 'x' resolves correctly and 'a' is written correctly");
|
|
var a = "a";
|
|
function f1() {
|
|
var x = 1; // context allocate x
|
|
(() => x);
|
|
var y = "y";
|
|
var z = "z";
|
|
(function () {
|
|
var x = 2; // stack allocate shadowing x
|
|
(function () {
|
|
y; // access y
|
|
debugger; // ReferenceError
|
|
})(); // 2
|
|
})(); // 1
|
|
return y;
|
|
}
|
|
|
|
listener_delegate = function(exec_state) {
|
|
for (var i = 0; i < exec_state.frameCount() - 1; i++) {
|
|
var frame = exec_state.frame(i);
|
|
var value;
|
|
try {
|
|
value = frame.evaluate("x").value();
|
|
} catch (e) {
|
|
value = e.name;
|
|
}
|
|
print(frame.sourceLineText());
|
|
var expected = frame.sourceLineText().match(/\/\/ (.*$)/)[1];
|
|
assertEquals(String(expected), String(value));
|
|
}
|
|
assertEquals("[object global]",
|
|
String(exec_state.frame(0).evaluate("this").value()));
|
|
assertEquals("y", exec_state.frame(0).evaluate("y").value());
|
|
assertEquals("a", exec_state.frame(0).evaluate("a").value());
|
|
exec_state.frame(0).evaluate("a = 'A'");
|
|
assertThrows(() => exec_state.frame(0).evaluate("z"), ReferenceError);
|
|
}
|
|
f1();
|
|
assertEquals("A", a);
|
|
a = "a";
|
|
EndTest();
|
|
|
|
BeginTest("Check that a context-allocated 'this' works")
|
|
function f2() {
|
|
var x = 1; // context allocate x
|
|
(() => x);
|
|
var y = "y";
|
|
var z = "z";
|
|
(function() {
|
|
var x = 2; // stack allocate shadowing x
|
|
(() => {
|
|
y;
|
|
a;
|
|
this; // context allocate receiver
|
|
debugger; // ReferenceError
|
|
})(); // 2
|
|
})(); // 1
|
|
return y;
|
|
};
|
|
|
|
// Uses the same listener delgate as for `f1`.
|
|
f2();
|
|
assertEquals("A", a);
|
|
EndTest();
|
|
|
|
BeginTest("Check that we don't get confused with nested scopes");
|
|
function f3() {
|
|
var x = 1; // context allocate x
|
|
(() => x);
|
|
(function() {
|
|
var x = 2; // stack allocate shadowing x
|
|
(function() {
|
|
{ // context allocate x in a nested scope
|
|
let x = 3;
|
|
(() => x);
|
|
}
|
|
debugger;
|
|
})();
|
|
})();
|
|
}
|
|
|
|
listener_delegate = function(exec_state) {
|
|
assertThrows(() => exec_state.frame(0).evaluate("x").value());
|
|
}
|
|
f3();
|
|
EndTest();
|
|
|
|
BeginTest("Check that stack-allocated variable is unavailable");
|
|
function f4() {
|
|
let a = 1;
|
|
let b = 2;
|
|
let c = 3;
|
|
() => a + c; // a and c are context-allocated
|
|
return function g() {
|
|
let a = 2; // a is stack-allocated
|
|
return function h() {
|
|
b; // b is allocated onto f's context.
|
|
debugger;
|
|
}
|
|
}
|
|
}
|
|
|
|
listener_delegate = function(exec_state) {
|
|
assertEquals(2, exec_state.frame(0).evaluate("b").value());
|
|
assertEquals(3, exec_state.frame(0).evaluate("c").value())
|
|
assertThrows(() => exec_state.frame(0).evaluate("a").value());
|
|
};
|
|
(f4())()();
|
|
EndTest();
|
|
|
|
BeginTest("Check that block lists on the closure boundary work as expected");
|
|
function f5() {
|
|
let a = 1;
|
|
() => a; // a is context-allocated
|
|
return function g() {
|
|
let a = 2; // a is stack-allocated
|
|
{
|
|
let b = 3;
|
|
return function h() {
|
|
debugger;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
listener_delegate = function(exec_state) {
|
|
assertThrows(() => exec_state.frame(0).evaluate("a").value());
|
|
};
|
|
(f5())()();
|
|
EndTest();
|
|
|
|
BeginTest("Check that outer functions also get the correct block list calculated");
|
|
// This test is important once we re-use block list info. The block list for `g`
|
|
// needs to be correctly calculated already when we stop on break_position 1.
|
|
|
|
let break_position;
|
|
function f6() {
|
|
let a = 1; // stack-allocated
|
|
return function g() { // g itself doesn't require a context.
|
|
if (break_position === 2) debugger;
|
|
let a = 2; (() => a); // context-allocated
|
|
return function h() {
|
|
if (break_position === 1) debugger;
|
|
}
|
|
}
|
|
}
|
|
|
|
listener_delegate = function (exec_state) {
|
|
assertEquals(2, exec_state.frame(0).evaluate("a").value());
|
|
}
|
|
break_position = 1;
|
|
(f6())()();
|
|
EndTest();
|
|
|
|
BeginTest("Check that outer functions also get the correct block list calculated (continued)");
|
|
listener_delegate = function (exec_state) {
|
|
assertThrows(() => exec_state.frame(0).evaluate("a").value());
|
|
}
|
|
break_position = 2;
|
|
(f6())()();
|
|
EndTest();
|
|
|
|
BeginTest("Check that 'inner' block lists, calculated on a previous pause, don't block the lookup");
|
|
|
|
function f7(o) {
|
|
let a = 1; // stack-allocated.
|
|
with (o) { // create a with-scope whos block-list has 'a' in it.
|
|
if (break_position === 2) debugger;
|
|
(function g() {
|
|
if (break_position === 1) debugger; // Trigger block-list calculation for the with-scope.
|
|
})();
|
|
}
|
|
}
|
|
|
|
listener_delegate = function (exec_state) {
|
|
assertThrows(() => exec_state.frame(0).evaluate("a").value());
|
|
}
|
|
break_position = 1;
|
|
f7({});
|
|
EndTest();
|
|
|
|
BeginTest("Check that 'inner' block lists, calculated on a previous pause, don't block the lookup (continued)");
|
|
// The second time we pause the with-scope already has a block-list, but 'a' should be accessible as a
|
|
// materialized stack-local.
|
|
listener_delegate = function (exec_state) {
|
|
assertEquals(1, exec_state.frame(0).evaluate("a").value());
|
|
}
|
|
break_position = 2;
|
|
f7({});
|
|
EndTest();
|
|
|
|
assertEquals(begin_test_count, break_count,
|
|
'one or more tests did not enter the debugger');
|
|
assertEquals(begin_test_count, end_test_count,
|
|
'one or more tests did not have its result checked');
|