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
- Write down a formal specification
- Produce a "monitor" piece of software
- Attach the monitor to the application
- Run the program
- Verify execution against specification
at runtime!
1. FORMAL SPECIFICATION
Trace expressions!
const http = require('http')
const server = http.createServer((req, res) => {
res.write('okay')
// res.end() call missing
})
server.listen(80);
2. MONITOR IMPLEMENTATION
Prolog!
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
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
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