// Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Flags: --expose-debug-as debug // Get the Debug object exposed from the debug context global object. var Debug = debug.Debug; var DebugCommandProcessor = debug.DebugCommandProcessor; // 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_value; 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); } // Accepts a closure 'fun' that returns a variable from it's outer scope. // The test changes the value of variable via the handle to function and checks // that the return value changed accordingly. function RunClosureTest(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 fun_mirror = Debug.MakeMirror(fun); var scope = fun_mirror.scope(scope_number); scope.setVariableValue(variable_name, new_value); var actual_new_result = fun(); 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_closure_test = function() { var th = this; var fun = this.factory_(false); this.run_and_catch_(function() { RunClosureTest(th.scope_index_, 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(0, 17, 's', 'Bird', 'Bird', true, function Factory(debug_stop) { eval("var s = 17"); return function() { if (debug_stop) debugger; return s; } }), 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(); } for (var i = 0; i < closure_test_cases.length; i++) { closure_test_cases[i].run_closure_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; } })()); RunPauseTest(0, 'mouse', 'v1', 'dog', 'dog', (function Factory() { return function() { eval("var 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); } })()); // Test value description protocol JSON assertEquals(true, DebugCommandProcessor.resolveValue_({value: true})); assertSame(null, DebugCommandProcessor.resolveValue_({type: "null"})); assertSame(undefined, DebugCommandProcessor.resolveValue_({type: "undefined"})); assertSame("123", DebugCommandProcessor.resolveValue_( {type: "string", stringDescription: "123"})); assertSame(123, DebugCommandProcessor.resolveValue_( {type: "number", stringDescription: "123"})); assertSame(Number, DebugCommandProcessor.resolveValue_( {handle: Debug.MakeMirror(Number).handle()})); assertSame(RunClosureTest, DebugCommandProcessor.resolveValue_( {handle: Debug.MakeMirror(RunClosureTest).handle()}));