JavaScript REPL for Windows: Part 3–Dynamic Breakpoints

This is part three of three part post about JavaScript REPL console for Windows. In part one I have explained motivation and some initial steps, in part two I have added debug REPL and static breakpoints. In part three I am going to add the ability to set breakpoints dynamically from within JSREPL.

As mentioned previously, full source code of JSREPL is freely available under GPL 2 license on github.

Dynamic Breakpoints

While static breakpoints offer good benefits of being flexible, they are, well, static. When I am debugging a script it is hard to predict where all the breakpoints would have to be placed. So, if I decide that I need another breakpoint in the middle of the debugging session, I need to edit the source code and restart the session.

It would be nice if I could set the breakpoints on the fly without the need to restart the session. It turns out, there is an easy way to do that for functions declared at the global scope; either global functions or anonymous functions referenced by global variables. Furthermore, it is even possible to do this for anonymous functions declared within such global functions, for example, object methods. There are some caveats to the latter part, though.

Well then, my JSREPL is getting three new commands:

  • bp – for adding new breakpoints
  • bd – for deleting breakpoints
  • bl – for listing breakpoints 

    The logic of adding a new breakpoint as well as removing one is quite simple – just figure out all the lines that the function needs to have breakpoints and generate a new string with source code for the function. Eval that at the global scope and subsequent invocations of the function will trigger breakpoints.

    There is one fine point in all of this: whether function is anonymous or not. But that is easily determined by the parseFunction  method from part two. If the function is anonymous, then the eval should be an assignment to a global variable. Otherwise, it is just a function declaration.

    Figure 14. Method to update breakpoints.

    1     updateFnBreaks: function (bpInfo, bpLine) {
    2         var lineNos = Arr(bpInfo.breaks).map(function(eBr) { return eBr.line; }).a;
    3         if (typeof(bpLine) != “undefined”) {
    4             lineNos.push(bpLine);
    5         }
    6         lineNos.sort();
    7
    8         var fnLines = bpInfo.savedFunc.slice(0);    // copy the array
    9         var bpLabelPrefix = bpInfo.name + “(“;
    10         for (iLN in lineNos) {
    11             var offset = lineNos[iLN] + parseInt(iLN);    // compensating for lines inserted already
    12             if (offset < fnLines.length) {
    13                 var bpLabel = bpInfo.name + “(“ + lineNos[iLN].toString() + “)”;
    14                 fnLines.splice(offset, 0 /*=toDelete*/, “eval(dbgBreak(\” + bpLabel + \”)) // <dbg_break>”);
    15             } else {
    16                 println(“ERROR: line “, lineNos[iLN], ” is outside of the function”);
    17                 return null;
    18             }
    19         }
    20         return fnLines.join(\n\r);
    21     }

     

    To illustrate the dynamic nature of the breakpoints let’s look at another example. This time we are dealing with class method.

    Figure 15. Source code for the example of dynamic breakpoints.

    1 classA = function (n) {
    2     return {
    3         name:  n,
    4         who:  function () {
    5             println(“Name=”, this.name);
    6             return this.name;
    7         }
    8     };
    9 };

     

    In this case I would like to set a breakpoint on line 6, but that is in the anonymous function, which happens to be a class method. Here is how that worked out for me:

    Figure 16. Session log of dynamic breakpoint demo.

    1 # list classA
    2    0 function (n) {
    3    1     return {
    4    2         name:  n,
    5    3         who:  function () {
    6    4             println(“Name=”, this.name);
    7    5             return this.name;
    8    6         }
    9    7     };
    10    8 }
    11 # bp classA 5
    12 # var a1 = new classA(“John”)
    13
    14 # a1.who()
    15 Name=John
    16
    17 dbg break – “classA(5)”  ? – displays list of debugger commands.
    18 dbg> w
    19 Call stack:
    20
    21 Level Function
    22 ~~~~~ ~~~~~~~~~~~~
    23     0 <anonymous>()
    24 dbg> a1.name
    25 John
    26 dbg> g
    27 John
    28 # bl
    29 Active breakpoints:
    30
    31 Id  Function(line)
    32 ~~~~ ~~~~~~~~~~~~~~
    33    0 classA(5)
    34 # bd 0
    35 # bl
    36 Active breakpoints:
    37
    38 Id  Function(line)
    39 ~~~~ ~~~~~~~~~~~~~~
    40 # a1.who()
    41 Name=John
    42
    43 dbg break – “classA(5)”  ? – displays list of debugger commands.
    44 dbg> g
    45 John
    46 # var a2 = new classA(“Peter”)
    47
    48 # a2.who()
    49 Name=Peter
    50 Peter
    51 #

    Setting the breakpoint (listing line 7) triggers the break into debug REPL (listing line 17). However, removing the breakpoint (listing line 43) does not remove the breakpoint from the method of object a1 (listing lines 40 – 44). However, creating a new instance of classA after removing the breakpoint acts as expected (listing line 48).

    This is it. While there are some caveats to dynamic breakpoints, it is still a very useful addition to JSREPL.

    Next Steps

    JSREPL is a work in progress and will be for quite some time. I am refining the functionality and addressing more corner cases as I run into those. If you use JSREPL and run into issues, please report them on the github issues page.

    I already have plans for the future – I would like to add enabled/disabled flag to the breakpoint, such that the  breakpoints that cannot be deleted, could at least be disabled. I would like to add an indicator of the current breakpoint in the function source listing. The wiki on github needs updating as well.

    Thank you for reading and happy debugging.

Leave a comment