JavaScript debugging tips (for Mustang context)

Mustang (Java SE 6) is co-bundled with Mozilla Rhino based JavaScript engine as an example implementation for javax.script API.

There are 3 use cases within JDK

There is already an experimental web application environment that uses JavaScript - http://phobos.dev.java.net.

So, we may expect that JavaScript will be employed within the JDK context more often. How about debugging support for JavaScript engine in JDK context? For Firefox, there are atleast two debuggers (that I know of!):

  1. Venkman
  2. Firebug

Rhino comes with it's own debugger -- but in Mustang, Rhino's tools are not included. Also, javax.script API does not include debugger API (yet?). For Rhino in Mustang, we have to resort to other ways to debug scripts including good old println/printf style. Few debugging tips and tricks are here...

  1. Use jrunscript in interactive mode for debugging.

    You can use load function to load your script files (or URLs). In the interactive mode, you can call specific script functions or evaluate various expressions.

  2. print or alert debugging!

    Use good old printf/System.out.println/alert/echo style debugging! In Mustang Java, there are built-in print and println functions always defined. If you have scripts that use alert, you can consider defining something like this:

    
       // define alert to be same as println function
       var alert = println;
    
    
    Note that 'print' and 'println' functions print the message to output writer configured in the script context (recall that "context" is a pre-defined variable initialized with the current ScriptContext object). If you configure different output writer (may be, the one that writes messages to a file or socket etc.), you can make all print/println messages to go there.

  3. Use Function.toSource() to view source code of various functions.

    
    // viewing function sources in jrunscript prompt.
    
    // built-in functions are shown as "native code"
    // But, you can get arity (number of arguments)
    // of the function
    js> eval.toSource()js> eval.toSource()
    function eval() { [native code for eval, arity=1] }
    
    // for script functions toSource() shows full source
    js> function add(x, y) { return x + y }
    js> add.toSource()
    function add(x, y) {return x + y;}
    
    // for Java methods we get signature as part of "source"
    js> var v = new java.util.Date()
    js> v.getDay.toSource()
    function getDay() {/\*
    int getDay()
    \*/}
    
    // for overloaded Java methods, toSource() shows signatures
    // for all overloads
    js> var out = java.lang.System.out
    js> out.println.toSource()
    function println() {/\*
    void println(boolean)
    void println(char)
    void println(int)
    void println()
    void println(long)
    void println(java.lang.Object)
    void println(java.lang.String)
    void println(char[])
    void println(double)
    void println(float)
    \*/}
    
    

  4. JavaScript object fields and array elements can be walked by
    
       for(var i in obj) { print(obj[i]); }
    
    
    like loop.

    I frequently print all fields of an object (or elements of array) this way. Note that you will get object's methods (which are function valued properties) as well. To filter these, you can use typeof operator. You can also filter properties using property name pattern/index.

  5. Use JavaScript's support for higher-order functions.

    JavaScript functions can be passed as arguments and returned as values. Functions can be called by apply or call functions. Functions are first-class values that can be stored in variables. You can use these to implement JavaScript AOP. You can use "interceptors" to print debug trace output. For eg. function entry/exit can be printed (along with arguments, if desired). For example, the following is a variation of JavaScript AOP referred in above link..

    
    
    var Aspects = new Object();
    
    // calls "before" interceptor function before calling function
    // name specified by fname. The function is expected to be a property
    // of object 'obj'
    Aspects.addBefore = function(obj, fname, before) {
        var oldFunc = obj[fname];
        var newFunc = function() {        
            return oldFunc.apply(this, before(arguments, oldFunc, this));
        };
        // store oldFunc for restore purpose...
        newFunc.oldFunc = oldFunc;
        obj[fname] = newFunc;
    };
    
    Aspects.restore = function(obj, fname) {
        obj[fname] = obj[fname].oldFunc;
    }
    
    // tracing function entry
    function traceEntry(args, oldFunc, thiz) {
        // print the name of the function
        print("entering " + oldFunc.name);
        // print arguments
        var str = "";
        for (var i = 0; i < args.length; i++) {
            str += args[i] + ", ";
        }
        print("arguments " + str);
        // return argument array - used by oldFunc.
        return args;
    }
    
    
    With the above code in a debug library (say "debug.js"), the client code can write something like:
    
    function add(x, y) { return x + y; }
    
    // just add two arguments
    add(3, 4);
    
    Aspect.addBefore(this, "add", traceEntry);
    // call add -- prints debug output on entry
    add(3, 4);
    
    // restore old "add" function without debug output
    Aspect.restore(this, "add");
    
    
  6. Tracking global variables: It is better to avoid globals as much as possible.

    Use objects instead. It is easy to accidentally create globals in JavaScript. For example,

    
    
    function f() {
       x = m(); // forgot 'var' before x, x is global!
       // rest of the code...
    }
    
    
    
    While it may be possible to spot "unwanted" globals by code inspection, we can do better than that. It is possible to check global variable assignments and accesses using javax.script.Bindings used for global variable storage.
    
    import javax.script.\*;
    import java.io.\*;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // create a new ScriptEngineManager
            ScriptEngineManager m = new ScriptEngineManager();
            // get JavaScript engine instance
            ScriptEngine jsEngine = m.getEngineByName("javascript");
            // set a "debug" bindings for global variables
            jsEngine.setBindings(new DebugBindings(),  ScriptContext.ENGINE_SCOPE);
            // eval code from a java.io.Reader object.
            jsEngine.eval(new FileReader(args[0]));
        }
    }
    
    
    The DebugBindings class could look like
    
    
    // a simple Bindings implementation that prints debug output
    // whenever a variable is accessed or assigned.
    import javax.script.\*;
    
    public class DebugBindings extends SimpleBindings {
      @Override public Object put(String name, Object value) {
          Object res = super.put(name, value);
          System.out.println("Global assign: " + name);
          return res;
      }
    
      @Override public Object get(Object key) {
          Object res = super.get(key);
          System.out.println("Global access: " + key);
          return res;
      }
    }
    
    
    When running the Test class with the following t.js script file,
    function h() {
       // global variable assign
       x = 32;
    }
    
    h();
    
    we get the following output..
    Global assign: context
    Global assign: print
    Global assign: println
    Global access: javax.script.filename
    Global assign: h
    Global access: h
    Global assign: x
    
    Note that debug output is printed for function "assignments" as well. Note that global functions are global variables with "function" value.

  7. Debugging JavaScript objects

    Because JavaScript is dynamically typed language, we can replace an object with any other object that "looks" like the "original". i.e., the replacement objects should just support same methods, properties -- but can do anything. (If it walks like a duck and quacks like a duck, it must be a duck). You can wrap actual object with a "debuggable" object (whose methods print debug/trace output) -- so long as debug wrapper objects supports same methods, properties. Usually, it is very easy to wrap an object with another object in JavaScript. But, if your object supports properties (a.k.a fields) it becomes tricky to wrap the same - for example, you may want to wrap XMLHttpRequest that has properties. But, you can use JSAdapter and "hook" all property or method access or assignments.

Comments:

Just a question related to this blog, do you know why javax.script.Bindings inherits from java.util.Map.

This prevent any other implementation than a map.

By example there is no way to add infinite identifiers like a1, b3, etc. (letters+numerics) to represent a cell in a spreadsheet.

Because bindinds are a map, bindinds must have a size and a way to iterate on it.

RĂ©mi Forax

Posted by guest on July 05, 2006 at 06:09 AM IST #

Hi Remi Forax: I think there are two ways to handle this: 1. Override only get, put methods of Map. i.e., return "incorrect size" (may be even 0) and empty-iterator. Script engines would not call iterator or size in most instances. Variable accesses will result in Bindings.get() call and variable assignments/updates will result in Bindings.put() call. 2. Expose cell access as a built-in function for your scripting language. For example, in JavaScript you can expose spreadsheet access a built-in function.

Posted by A. Sundararajan on July 06, 2006 at 12:55 PM IST #

Post a Comment:
Comments are closed for this entry.
About

sundararajan

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks
Links

No bookmarks in folder

Blogroll

No bookmarks in folder

News

No bookmarks in folder