Understanding JavaScript Code

Using

Dynamic Analysis

Understanding JavaScript Code

Using

Dynamic Analysis

Dynamic Analysis Tools

1. Observe Execution

2. Tell You About It

function square(n) {
    return n * n
}

square(5)
square("a")

Tern

fn(n: number|string) -> number

Flow

function square(n) {
    return n * n
}

square(5)
square("a")
2:     return n * n
              ^ string. The operand of an arithmetic
                operation must be a number.

Debugger

Time Travel Debugging

Node-ChakraCore

Console.log

function square(n) {
    console.log("n", n)
    var ret = n * n
    console.log("Return Value", ret)
    return ret
}

Automatic Logging

How Can We Collect Runtime Data

Wrapping Functions

window.setTimeout = function() {
  console.log("Called setTimeout")
}

Wrapping Functions

var _setTimeout = window.setTimeout
window.setTimeout = function() {
  console.log("Called setTimeout")
  return _setTimeout.apply(this, arguments)
}

Capture a Call Stack

window.setTimeout = function(){
    console.log("Called setTimeout: " + Error().stack)
}

document.createElement = ...

element.innerHTML

...

document.createElement

Object.defineProperty

var _createElement = document.createElement
document.createElement = function(){
  var newElement = _createElement.apply(this, arguments)
  newElement.__createdAt = new Error().stack
  return newElement
}
document.location
4 + 3
person.name = "John"

Wrapping Functions: Limitations

Modifying Code

obj.a = 123
assignPropertyValue(obj, "a", 123)
function assignPropertyValue(object, propertyName, value){
    object[propertyName] = value

}
    object[propetyName + "__history"] = ...

Babel

transforms js into other js

array.map(n => n * n)
array.map(function (n) {
  return n * n;
});
assignPropertyValue(obj, "a", 123)
obj.a = 123

History of an Object

a String

"Hello "

"World"

"Hello World"

"Hello World!"

"!"

var greeting = "Hello " + "World"
greeting += "!"
// "Hello World!"
var hello = "Hello"
var world = "World"
var greeting = hello + world
var hello = stringLiteral("Hello")
// { value: "Hello", stack: "..." }
var world = stringLiteral("World")
// { value: "World", stack: "..." }
var greeting = hello + world
// "[object Object][object Object]"
var hello = stringLiteral("Hello")
var world = stringLiteral("World")
var greeting = add(hello, world)
greeting = {
    value: "Hello World",
    stack: "(where string concat took place)"
    inputs: [
        {value: "Hello", stack: "..." },
        {value: "World", stack: "..." }
    ]
}

Problems

High Memory Consumption

Flaky If Invasive Changes

Slow Execution

Capturing Data Directly in the JavaScript Engine

var square;
for (var i=0; i<=1000000; i++) {
    log("i", i)
    square = i * i
    log("square", square)
}

=> Replay execution

var random;
for (var i=0; i<=1000000; i++) {
    random = Math.random()
}

=> Capture sources of non-determinism

1. Record execution

2. Replay execution using log

Node-ChakraCore

Collecting Runtime Data

1. Wrap Functions

2. Rewrite Code

3. Integrate with JS Engine

What Can We Do?

Annotate code

Debug a past invocation

 

Diff two execution traces

...

Node-ChakraCore: Time-Travel debugging for JS apps

 

Thanks!

Annotate code with runtime values

Track object property assignments

Follow string through application

Demo Links:

Understanding JavaScript Code - EnterJS 2017

By Matt Zeunert

Understanding JavaScript Code - EnterJS 2017

  • 1,442