X

Sundararajan's Weblog

More JavaScript debugging tips (Mustang context)

Guest Author

This is continuation of my earlier post on the same topic.


  1. objects have prototypes

    This is just to remind that objects have a prototype object. JavaScript is prototype-delegation based language. If a property or method is not found in an object, it's prototype is searched. So, when you access obj.x, the property "x" may actually be in the prototype of object "obj" (or prototype of prototype and so on). An object's prototype is accessible by a special property of the name __proto__. Note that this is non-standard Rhino specific extension. Rhino allows you to change prototype by assigning to __proto__ property!

  2. scopes are objects

    One more similarity to Lisp! In Lisp, scopes are known as "environments". Scopes are nothing but a map of variable names to variable values. In JavaScript, scopes are objects whose property names are variable names. For example, this keyword in global/toplevel scope refers to the global object. You can walk all global variables by this function:


    function printGlobals() {
    for (var i in this) {
    println(i + ' = ' + this[i]);
    }
    }

    Scopes are organized in a parent-child chain. The local scope of a function has the global scope as it's parent. Local variable scope of a nested function has enclosing function's local scope as it's parent and so on. Scope objects are also referred as "variable objects" (i.e., object that keeps variables). The "next" link in this chain is accessible by the special property called __parent__. Note that this is non-standard Rhino specific extension. Rhino allows you to change parent by assigning to __parent__ property! In addition to functions and local functions, with statements also add an innermost scope to scope-chains.

    How can we get hold of the "local scope" object of a function? Note that a nested function's parent scope is the local scope of the enclosing function. So, we can introduce an anonymous local function (with no code) to get it's __parent__.


    function f() {
    var x = 10;
    // introduce an anonymous nested function just to get
    // it's parent scope -- which is nothing but scope of
    // this function.
    var localScope = (function() {}).__parent__;
    println(x); // prints 10
    println(localScope.x); // prints 10 again!
    }

    With this knowledge we can write generic code to print all local variables of
    a function:

    var printLocals = "{ var _locals = (function(){}).__parent__; " +
    " for (var _i in _locals) { " +
    " if (/_locals/(_i) || /_i/(_i)) continue; " +
    " println(_i + '=' + _locals[_i].toSource()); " +
    " } " +
    "}";
    function h() {
    var x = 32;
    var y = { x : 3 };
    // prints all locals (x, y in this function)eval(printLocals);
    }

    Note that I've used eval - I cannot define printLocals as a function -- because to access local scope object of given function (like h() above), I've to define an anonymous function inside the same function! So, I'm eval'ing a string to get hold of the same. But, printLocals is generic -- in the sense that you can eval it any of your function to print all locals of it. Note that although JavaScript is similar to Lisp, macros are not supported. I had to use eval to simulate macro expansion.

    If you combine the fact that scopes are objects and objects have prototypes, we have the following lookup rule for variables:

    1. get the innermost scope object
    2. look for property with the same name as that of the variable
    3. if not found - chase the prototype chain (till null __proto__ is found)
      and look for it
    4. if not found, then get parent scope and look for property there. (till null __parent__ is found)

    So, each variable lookup is potentially a 2-dimensional search!
  3. Handling Java Exceptions

    JavaScript exception handling involves try..catch statement. But, exceptions typically are JavaScript Error object or subtypes of it (but other types like string can also be thrown!). If you are getting a Java exception, that is also wrapped as a JavaScript Error object. Sometimes, you may want to get the exact Java exception thrown -- may be the Java method you called throws multiple exceptions and you may want to handle those differently. Rhino adds javaException property to Error objects -- this property is initialized if an Error object wraps around a Java exception.


    try {
    var obj = new java.lang.Object();
    obj.wait();
    } catch (e) {
    var exp = e.javaException;
    if (exp instanceof java.lang.IllegalMonitorStateException) {
    exp.printStackTrace();
    } else if (exp instanceof java.lang.InterruptedException) {
    // do something differently..
    } else {
    // something else!
    }
    }
  4. printStackTrace

    In Java debugging, we often use Throwable.printStackTrace()
    or Thread.dumpStack() to print stack trace. How about similar stack trace for JavaScript? It turns out that Rhino does support stack trace - with script file name, function name and line number!. Rhino adds rhinoException property to Error object. The printStackTrace method on this rhinoException include script functions, line numbers etc.


    File: test.js

    function f() {
    g();
    }
    function g() {
    try {
    var c = undefined;
    c.toString();
    } catch (e) {
    e.rhinoException.printStackTrace();
    }
    }
    f();

    The above code prints something like:

    D:\\>jrunscript test.ps
    sun.org.mozilla.javascript.internal.EcmaError: TypeError: Cannot call method "toString" of undefined (p.ps#19)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.constructError(ScriptRuntime.java:3234)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.constructError(ScriptRuntime.java:3224)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.typeError(ScriptRuntime.java:3240)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.typeError2(ScriptRuntime.java:3259)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.undefCallError(ScriptRuntime.java:3278)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.getPropFunctionAndThis(ScriptRuntime.java:1968)
    at sun.org.mozilla.javascript.internal.Interpreter.interpretLoop(Interpreter.java:2931)at script.g(test.ps:19)at script.f(test.ps:2)at script(test.ps:27)
    at sun.org.mozilla.javascript.internal.Interpreter.interpret(Interpreter.java:2250)
    at sun.org.mozilla.javascript.internal.InterpretedFunction.call(InterpretedFunction.java:149)
    at sun.org.mozilla.javascript.internal.ContextFactory.doTopCall(ContextFactory.java:337)
    at sun.org.mozilla.javascript.internal.ScriptRuntime.doTopCall(ScriptRuntime.java:2757)
    at sun.org.mozilla.javascript.internal.InterpretedFunction.exec(InterpretedFunction.java:160)
    at sun.org.mozilla.javascript.internal.Context.evaluateReader(Context.java:1163)
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:106)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:230)
    at com.sun.tools.script.shell.Main.evaluateReader(Main.java:314)
    at com.sun.tools.script.shell.Main.evaluateStream(Main.java:350)
    at com.sun.tools.script.shell.Main.processSource(Main.java:267)
    at com.sun.tools.script.shell.Main.access$100(Main.java:19)
    at com.sun.tools.script.shell.Main$2.run(Main.java:182)
    at com.sun.tools.script.shell.Main.main(Main.java:30)


    Note that the red lines above include script function, file name and line number. With this you can write a generic printStackTrace function as

    function printStackTrace(exp) {
    if (exp == undefined) {
    try {
    exp.toString();
    } catch (e) {
    exp = e;
    }
    }
    // note that user could have caught some other
    // "exception"- may be even a string or number -
    // and passed the same as argument. Also, check for
    // rhinoException property before using it
    if (exp instanceof Error &&
    exp.rhinoException != undefined) {
    exp.rhinoException.printStackTrace();
    }
    }

    You can call above function whereever you want script stack trace - much like java.lang.Thread.dumpStack() call. You can get fancier output by getting the stack trace into a string and then filtering it to print only the lines that begin with at script -- so that you can view only the script frames in the trace. That is left an exercise to the reader :-) [hint: use printStackTrace that accepts java.io.PrintWriter]


Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.