// 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 --no-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. 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 evaluating on outer closure scopes with shadowing"); function test_scopes_shadowing() { let x = 'outer_x'; let y = 'outer_y'; () => [x, y]; // context allocate x and y in f7 return function g() { let x = 'middle_x'; () => x; // context allocate x in g return function h() { let x = 'inner_x_stack'; // stack allocate x in h debugger; }; }; } listener_delegate = function(exec_state) { assertEquals('inner_x_stack', exec_state.frame(0).scope(0).evaluate("x").value()); assertEquals('outer_y', exec_state.frame(0).scope(0).evaluate("y").value()); assertEquals('middle_x', exec_state.frame(0).scope(1).evaluate("x").value()); assertEquals('outer_y', exec_state.frame(0).scope(1).evaluate("y").value()); assertEquals('outer_x', exec_state.frame(0).scope(2).evaluate("x").value()); assertEquals('outer_y', exec_state.frame(0).scope(2).evaluate("y").value()); }; (test_scopes_shadowing())()(); EndTest(); BeginTest("Check that outer functions also get the correct block list calculated"); // This test is important once we reuse 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 evaluating on outer closure scope with stack-allocated shadowed variable"); var x = 'global_x'; function test_outer_shadow() { let x = 'outer_stack'; let closure_y = 'closure_y'; () => closure_y; // force context allocation for closure_y return function inner() { debugger; }; } listener_delegate = function(exec_state) { assertThrows(() => exec_state.frame(0).scope(1).evaluate("x").value(), ReferenceError); assertEquals('closure_y', exec_state.frame(0).scope(1).evaluate("closure_y").value()); }; (test_outer_shadow())(); EndTest(); BeginTest("Check evaluating on outer closure scopes when inner scope also has a context"); function test_outer_shadow_with_inner_context() { let x = 'outer_stack'; let closure_y = 'closure_y'; () => closure_y; // force context allocation for test_outer_shadow_with_inner_context return function inner() { let inner_ctx = 'inner_ctx'; () => inner_ctx; // force context allocation for inner as well debugger; }; } listener_delegate = function(exec_state) { assertThrows(() => exec_state.frame(0).scope(1).evaluate("x").value(), ReferenceError); assertEquals('closure_y', exec_state.frame(0).scope(1).evaluate("closure_y").value()); }; (test_outer_shadow_with_inner_context())(); EndTest(); BeginTest("Check that inner block locals do not leak into outer function block list"); var a_block_test = 'global_a'; var c_block_test = 'global_c'; function test_inner_block_isolation() { let a_block_test = 42; // stack-allocated in f let b_block_test = 21; () => b_block_test; // force context-allocation so f has a context { const c_block_test = 'foo'; // stack-allocated in inner block (function g() { if (break_position === 1) debugger; })(); } return function h() { if (break_position === 2) debugger; }; } listener_delegate = function(exec_state) { // In `g` (inside the inner block), both `a_block_test` and `c_block_test` are shadowed. assertThrows(() => exec_state.frame(0).scope(0).evaluate("a_block_test").value(), ReferenceError); assertThrows(() => exec_state.frame(0).scope(0).evaluate("c_block_test").value(), ReferenceError); assertEquals(21, exec_state.frame(0).scope(0).evaluate("b_block_test").value()); // The inner block (`scope(1)`) has no context, but still shadows both. assertThrows(() => exec_state.frame(0).scope(1).evaluate("a_block_test").value(), ReferenceError); assertThrows(() => exec_state.frame(0).scope(1).evaluate("c_block_test").value(), ReferenceError); assertEquals(21, exec_state.frame(0).scope(1).evaluate("b_block_test").value()); // In `f` (`scope(2)`), `a_block_test` is shadowed, but `c_block_test` (from the inner block) is not. assertThrows(() => exec_state.frame(0).scope(2).evaluate("a_block_test").value(), ReferenceError); assertEquals('global_c', exec_state.frame(0).scope(2).evaluate("c_block_test").value()); assertEquals(21, exec_state.frame(0).scope(2).evaluate("b_block_test").value()); }; break_position = 1; (test_inner_block_isolation())(); EndTest(); BeginTest("Check that inner block locals do not leak into sibling closure after reuse"); listener_delegate = function(exec_state) { // In `h` (outside the inner block), `a_block_test` is shadowed, but `c_block_test` is not. assertThrows(() => exec_state.frame(0).scope(0).evaluate("a_block_test").value(), ReferenceError); assertEquals('global_c', exec_state.frame(0).scope(0).evaluate("c_block_test").value()); assertEquals(21, exec_state.frame(0).scope(0).evaluate("b_block_test").value()); }; break_position = 2; (test_inner_block_isolation())(); 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');