// 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');