Reification of Execution State in JavaScript Building a Debugger for Lively Kernel
Hasso-Plattner-Institut Potsdam Software Architecture Group 25/1/2012
Motivation (1/2)
■ Cross-browser development tools for Lively Kernel - System Code Browser - Object Inspector - Trace Debugger
Squeak Debugger
Motivation (2/2)
■ JavaScript limited inside the browser □ one thread of execution*
□ one event queue
□ (mostly) asynchronous, non-blocking APIs
■ How can you ... ? □ halt the currently executing thread
□ access the stack incl. local variables
*with the exception of web workers
□ resume execution
□ step through the code
without relying on browser support
Definitions
Execution State at each point in time, the program currently in execution has a certain state, e.g. the current stack, the current program counter, etc. Reification treating something as a subject in order to be able to talk about it, to comprehend it and to change it Morphic "every display object in Morphic is a Morph. [These] objects objects have the same basic structure (color, position,...) [..] and can be manipulated visually as real objects." [1]
[1] Bouraqadi, N. & Stinckwich, S. Bridging the gap between morphic visual programming and smalltalk code., In International Conference on Dynamic Languages (2007)
Approach #1
■ Should be able to execute all the JavaScript code in Lively
■ How is works...
□ Parse the JavaScript source code with OMeta/JS
□ Evaluate the AST
□ Reuse the underlying object model and semantics
lively.AST.Parser.parse("5 + 3","expr")
InterpreterVisitor.visitBinaryOp = function(node) { // simplified return this.visit(node.left) + this.visit(node.right); }
A JavaScript Interpreter in Lively
Approach #1
■ Start/Stop/Resume
□ Throw custom exception on "halt"
□ Exception contains all the stack frames, local variables and PCs
□ Resume means to restart the AST traversal
□ Skip nodes already computed
var a = 2; a += 3; alert(a);
Stepping through the code
var a = 2; if (a == 2) { alert("A"); } else { alert("B"); }
Approach #1
■ Problem
□ Very slow (about ~1000 times slower than native)
□ Running everything inside the interpreter not feasible
■ Partial Interpretation
□ Telling Lively to open a debugger (Ctrl+Shift+D)
□ Interpret Morphic scripts with 'debugger' statements
Approach #1
■ Array.prototype.forEach is native □ no JavaScript source code
□ internal state lost at breakpoints
■ Stack frames outside the interpreter □ no local variables or PC
■ Variables bound in "native" closures □ not accessible by the Interpreter
□ no resume possible
Approach #2
■ Lively is completely open source □ possible to add certain language level semantics by rewriting the source code*
* similar to source code weaving in AOP
■ We want to capture the stack when "halting" the executing code
function() { var a = 2; alert("A"); }
□ wrap calls and sends with try-catch
function() { var a = 2; try { var result5_alert = alert("A") } catch(e) { if (e.isUnwindException) { e.lastFrame = e.lastFrame.addCallingFrame( {a: a}, 5, arguments.callee.livelyClosure ) }; throw e }; result5_alert }
Approach #2
■ Relatively small performance overhead □ supposedly about 7 times slower than normal
Evaluation
■ Captures local variables and PC
■ Same problem with native code as with Approach #1
■ Not possible to stop at arbitary places in the code
□ feasible to rewrite the whole Lively environment
□ Stepping becomes difficult
Approach #3
■ Idea: Have a global error handler like in Squeak
■ Capture stack in case of unhandled error with Approach #2
■ Open debugger window
■ Allow stepping and resuming the call stack using Approach #1
■ Problem: What about intermediate results not stored in variables?
function foo() { debugger; return 3} function bar() { var i = 0; return (i++) - foo(); }
Conclusions
■ Cross-browser JavaScript debugger requires some tricks
□ How to represent the execution state? (local variables, PC?)
□ Performance penalty
□ Avoiding closures with "hidden" state
■ What are the use cases?
□ Just Morphic Scripts or the whole environment?
□ Stack Trace? Stepping? Exception Handler?
■ What is missing?
■ Merging debugger functionality into the Object Editor?
■ Stepping back in time? Observing morphs? Conditional breakpoints?