TRACE EXPRESSIONS
FOR RUNTIME VERIFICATION
OF NODE.JS APPLICATIONS

Luca Franceschini

PhD days seminars, 12 October 2018

WHY IS IT RELEVANT?

  • Enables full stack JS development

  • JS/Node.js most popular language/framework

  • Largest package ecosystem

... a lot to do on verification

VERIFIYING JAVASCRIPT IS HARD...

  • Dynamically typed

  • Objects change structure

  • Reflection

... VERIFYING NODE.JS
IS HARDER!

ASYNCHRONOUS PROGRAMMING

const http = require('http')

for (let i = 1; i <= 100; i++) {
    const req = http.request('www.website.com', {method:'POST'})
    req.write(String(i))
    req.end()
}

ASYNCHRONOUS PROGRAMMING

const http = require('http')

for (let i = 1; i <= 100; i++) {
    const req = http.request('www.website.com', {method:'POST'})
    req.write(String(i))
    req.end()
}

ASYNCHRONOUS PROGRAMMING

const http = require('http')

function request(i) {
    if (i <= MAX) {
        const req = http.request('www.website.com', {method:'POST'})
        req.write(String(i))
        req.end(() => request(i+1))
    }
}

request(1)

CALLBACK HELL

function verifyUser(username, password, callback) {
    dataBase.verifyUser(username, password, (error, userInfo) => {
        if (error) {
            callback(error)
        } else {
            dataBase.getRoles(username, (error, roles) => {
                if (error) {
                    callback(error)
                } else {
                    dataBase.logAccess(username, (error) => {
                        if (error) {
                            callback(error)
                        } else {
                            callback(null, userInfo, roles)
                        }
                    })
                }
            })
        }
    })
}

EXAMPLE

const http = require('http')

const server = http.createServer((req, res) => {
    res.write('okay')
    // res.end() call missing
})

server.listen(80);

This method signals to the server that all of the response headers and body have been sent; that server should consider this message complete. The method, response.end(), MUST be called on each response.

RUNTIME VERIFICATION

  1. Write down a formal specification
  2. Produce a "monitor" piece of software
  3. Attach the monitor to the application
  4. Run the program
  5. Verify execution against specification
    at runtime!

1. FORMAL SPECIFICATION

Trace expressions!

\tau = \langle \mathsf{id}_ {cb}; \mathit{create}(\mathsf{id}_{cb} ): \tau_{cb}\rangle
τ=idcb;create(idcb):τcb\tau = \langle \mathsf{id}_ {cb}; \mathit{create}(\mathsf{id}_{cb} ): \tau_{cb}\rangle
\tau_{cb} = \langle \mathsf{id}_{res}; \mathit{callback}(\mathsf{id}_{cb}, \mathsf{id}_{res}) : (\tau_w | \tau_{cb})\rangle
τcb=idres;callback(idcb,idres):(τwτcb)\tau_{cb} = \langle \mathsf{id}_{res}; \mathit{callback}(\mathsf{id}_{cb}, \mathsf{id}_{res}) : (\tau_w | \tau_{cb})\rangle
\tau_w = (\mathit{write}(\mathsf{id}_{res}) : \tau_w) \lor (\mathit{end}(\mathsf{id}_{res}) : \epsilon)
τw=(write(idres):τw)(end(idres):ϵ)\tau_w = (\mathit{write}(\mathsf{id}_{res}) : \tau_w) \lor (\mathit{end}(\mathsf{id}_{res}) : \epsilon)
const http = require('http')

const server = http.createServer((req, res) => {
    res.write('okay')
    // res.end() call missing
})

server.listen(80);

2. MONITOR IMPLEMENTATION

Prolog!

\tau = \langle \mathsf{id}_ {cb}; \mathit{create}(\mathsf{id}_{cb} ): \tau_{cb}\rangle
τ=idcb;create(idcb):τcb\tau = \langle \mathsf{id}_ {cb}; \mathit{create}(\mathsf{id}_{cb} ): \tau_{cb}\rangle
T = var(idCb, (create(var(id)) : Tcb))

3. INSTRUMENTATION

Jalangi!

We intercept by code instrumentation all

  • function calls
  • property accesses
  • call arguments
  • returned results
  • object identities
  • callbacks and matching functions

4. RUNTIME ARCHITECTURE

5. FINALLY, VERIFY!

  • Every event is sent to the monitor for verification
     
  • As soon as an unexpected event is observed,
    an error occurs
     
  • Verification can happen after deployment

LAST YEAR PROGRESS

USE CASES

Last submitted paper includes 12 examples

  • HTTP client/server interaction
  • Express framework
  • File system library

INSTRUMENTING A REAL LIBRARY

Express code is not trivial:

  • Reflection
  • Circular objects
  • Getter side-effects

GENERIC TRACE EXPRESSIONS

Make specifications reusable

 

Declare                         

 

      Use                        

\tau[x] = \dots
τ[x]=\tau[x] = \dots
\tau[42]
τ[42]\tau[42]

OPTIMIZATIONS

  • Child process devoted to monitoring
  • Ad-hoc JSON serialization
  • Filter out non relevant events before sending

From a few RPS to hundreds!

BENCHMARKING

GITHUB REPO

Experiments are now available and reproducible:

  • Examples
  • Prolog monitor
  • Jalangi instrumentation
  • Runnable benchmarks

NODE-RED & IoT

NODE-RED & IoT

NEXT STEPS
& W.I.P.

RUNTIME MONITORING LANGUAGE

\tau = \{\texttt{let}\;\;\mathsf{x}, \mathsf{y}\;\;\texttt{in}\;\;\tau' \cdot \mathit{event}(\mathsf{x}, \mathsf{y})\}
τ={let&ThickSpace;&ThickSpace;x,y&ThickSpace;&ThickSpace;in&ThickSpace;&ThickSpace;τevent(x,y)}\tau = \{\texttt{let}\;\;\mathsf{x}, \mathsf{y}\;\;\texttt{in}\;\;\tau&#x27; \cdot \mathit{event}(\mathsf{x}, \mathsf{y})\}
\tau = \langle \mathsf{x}; \langle \mathsf{y};\;\;\tau' \cdot (\mathit{event}(\mathsf{x}, \mathsf{y}) : \epsilon) \rangle \rangle
τ=x;y;&ThickSpace;&ThickSpace;τ(event(x,y):ϵ)\tau = \langle \mathsf{x}; \langle \mathsf{y};\;\;\tau&#x27; \cdot (\mathit{event}(\mathsf{x}, \mathsf{y}) : \epsilon) \rangle \rangle

PROLOG GENERATION

Automatically generate Prolog code from specification

DEAL WITH BOUND FUNCTIONS

const obj = {
  x: 42,
  getX: function() {
    return this.x;
  }
}

const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined

const boundGetX = unboundGetX.bind(obj);
console.log(boundGetX()); // 42

AUTOMATIC FILTERING

Automatically derive which functions needs to be monitored from the specification, and produce an appropriate filter

WEBSOCKETS

Currently a new HTTP connection for every observed event... not very efficient

QUESTIONS?

PhD days

By Luca Franceschini

PhD days

  • 707