Skip to main content
Tim Disney

Memory Checking in Low-Level JavaScript

So this past month I've been helping out on the Low-Level JavaScript (LLJS) project. Bit of a change for me since I usually hide out at as high a level as I can possibly get :)

LLJS is an experiment in adding low-level features like pointers and manual memory management to a dialect of JavaScript. These low-level features are pretty cool and can get you some nice performance wins, but they also come at a cost since you are always one null pointer dereference or memory leak away from oblivion.

One way C/C++ programmers have handled this danger is by using an analysis tool like Valgrind to detect memory errors that happen while the program is running. Since memory checking has proven to be useful in the C world, we figured it might be helpful for LLJS. So, I've hacked up a valgrind-like memory analysis for LLJS. It can detect four kinds of memory errors:

How to use #

Go grab the latest version of LLJS from its github repo. Then add the following snippet to the end of your script:

let m = require('memory');
console.log(m.memcheck.report());

This pulls in the memory module and prints out the result of the memory checking (this snippet is for node.js, so use load if you're using SpiderMonkey or grab memory off the global object if you're in the browser).

Then compile your LLJS code with the -m flag:

bin/ljc -m -o your_file.js your_file.ljs

and run it:

node --harmony-proxies your_file.js

As you can see the memory checker uses Proxies, which node/V8 hides behind a flag (no flag needed on SpiderMonkey).

For example, the following script:

extern console;

function test_unallocated() {
  let uint *x;
  // not allocated yet!
  return *x;
}
test_unallocated();

function test_leak() {
  // leak!
  let uint *leak = new uint;
}
test_leak();

function test_free() {
  let uint *x = new uint;
  delete x;
  delete x;
}
test_free();

let m = require('memory');
console.log(m.memcheck.report());

will print out the following report:

Unallocated: 0 at: test_unallocated:3:0

Undefined: 0 at: test_unallocated:3:0

Bad frees: 8184 at: test_free:16:0

Leaks: 8200 at: test_leak:10:0

How it works #

Memory allocation in LLJS is implemented as a big typed array where pointers are really just array indexes into the array.

To do memory checking we create a second shadow memory typed array that stores the allocation status of each byte of real memory (if the byte has been allocated, initialized, etc.). Calls to malloc and free change the allocation status of the bytes in shadow memory. The real memory array is wrapped in a Proxy that intercepts each get and set operation and checks the shadow memory for a possible error.

For example, say we we allocate an int and try to do some pointer arithmetic and dereference unallocated memory:

let int *x = new int;
*(x+1)

When the dereference of x+1 happens, the Proxy wrapping real memory will check in shadow memory to see if x+1 has been allocated. Since it hasn't, an unallocated memory access error is recorded.

This approach is basically a simplified version of how Valgrind does memory checking.