0.1.0 • Published 4 years ago

blunt-instrument-core v0.1.0

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

blunt-instrument-core

Contents:

ASTBundle

Programs that have been instrumented with blunt-instrument record the value of each expression they evaluate. Every value is associated with the node of the program's abstract syntax tree corresponding to the expression, so that you can look up all the values for any given expression. blunt-instrument uses the ASTs produced by babel. This class wraps a set of those ASTs and adds some extra fields to them.

(The instrumenter can instrument multiple files so that they'll produce a combined trace; each file's AST will have its own astId.)

const astb = new ASTBundle();
astb.add('one', ast1, sourceCode1);
astb.add('two', ast2, sourceCode2);
astb.getNode('one', 10);
astb.getNodeByKey(toNodeKey('one', 10));
astb.filterNodes((node) => node.codeSlice === 'x + 1');

See the JSDoc in ASTBundle.js for more info.

Node IDs

Every node in the AST needs a unique identifier so that the tracer can indicate which node a result corresponds to. This is stored in the biId field.

To assign sequential node IDs to all nodes in a tree that don't have them, use addIdsToAST. blunt-instrument-babel-plugin calls this for you.

biId is only unique within a single AST. You need to know the AST's id too to fully identify a node. The functions toNodeKey and fromNodeKey provide a way to encode/decode the combination of AST id and node id into a single string for convenience.

copyNodeIdsBetweenASTs is available for less common situations.

Tracer

Instrumented code must be given an instance of the Tracer class. You can either create an instance via new Tracer() or rely on the defaultTracer instance exported by the package. If you're using blunt-instrument-eval, it will create an instance for you; if you're using the babel plugin directly, you can configure it as documented there.

The Tracer supports adding listeners which will receive two callbacks, described below. These can be used for recording the data of a trace; see ArrayTrace below for a basic implementation.

handleTrev

This will be called every time the instrumented code reports that a tracing event ("trev") occurred. These events include the evaluation of an expression, that start of a function call, returning from a function call, etc.

tracer.addListener({
  onTrev(trev) {
    console.log(trev);
  }
});

See babel-plugin-blunt-instrument's README for an example of what trevs look like. Unlike what's shown there, though, the data field on this trev object has not been cloned or encoded in any way, so it might be modified as soon as control returns to the instrumented code. (The ArrayTracer class replaces the data field with an encoded copy.)

handleRegisterAST

This will be called when the instrumented code is initializing, unless that functionality has been disabled in the babel plugin opts. The arguments are the AST ID (important if you have instrumented multiple files that are all reporting to the same Tracer) and the root babel Node of the AST.

tracer.addListener({
  onRegisterAST(astId, ast) {
    console.log(astId, ast);
  }
});

ArrayTrace

An ArrayTrace instance will listen to all the trevs reported by a Tracer instance and save them to an array. It also keeps track of any ASTs registered via the Tracer. See blunt-instrument-eval for a simplified way to instrument & run code & retrieve an ArrayTrace from it.

import { ArrayTrace } from 'blunt-instrument-trace-utils';

const trace = new ArrayTrace();
tracer.addListener(trace);
/* ... invoke some instrumented code ... */

console.log(trace.trevs);

See the jsdoc in ArrayTrace.js for more info.

See the babel plugin's README for an example of what an array of trevs looks like.

Data Encoding

One difficulty in producing a full trace of a program is that every value (every number and string, every state of every array and object) needs to be copied or serialized in some way. ArrayTrace encodes the data field in every trev using object-graph-as-json, which attempts to preserve as much information about the objects being copied as it can.

By default, each instance of ArrayTrace creates a new instance of Encoder, but you can specify an encoder to the constructor instead if you wish:

const trace = new ArrayTrace({ encoder: myEncoder });

TrevCollection

A TrevCollection contains a list of trace events, along with the ASTs for the code that generated those events. It provides convenience methods for denormalizing extra data onto the trevs and getting basic statistics about them.

const tc = arrayTrace.toTC();

// Get all the trevs in the collection
trevCollection.trevs;

// Get a new TrevCollection filtered according to a function you provide -
// in this case, find all the times that any expression evaluated to the
// string "hello world!"
tc.filter((trev) => trev.type === 'expr' && trev.data === 'hello world!');

// Attach extra fields to all the trevs for convenience
const fancyTC = tc.withDenormalizedInfo();

// Filter out literals; i.e., for a trace of the code 'x = y + 3',
// omit all the entries reporting that 3 evaluated to 3.
import types from '@babel/types';
fancyTC.filter((trev) => !types.isLiteral(trev.denormalized.node));

// Get the number of trevs by node, node type, and type.
const { nodes, nodeTypes, types } = tc.getFacets();

See the JSDoc in TrevCollection.js for more info.

FileTraceWriter

A FileTraceWriter will listen to everything reported by a Tracer and write it to a file. Like ArrayTrace, this encodes the data field using object-graph-as-json, and the constructor supports an optional encoder param.

import { FileTraceWriter } from 'blunt-instrument-core';

const writer = new FileTraceWriter({ prefix: 'some-dir/some-filename-prefix' });
tracer.addListener(writer);

// After invoking instrumented code, a file with a name like "some-dir/some-filename-prefix.123456789.tracebi" will exist.

See the jsdoc in FileTraceWriter.js for more info.

Reading File Traces

To load a trace produced by FileTraceWriter, use the static method readToTC (takes a file name, requires Node) or parseToTC (takes the file contents):

import { FileTraceWriter } from 'blunt-instrument-core';

FileTraceWriter.readToTC('some-dir/some-filename-prefix.123456789.tracebi')
  .then((tc) => console.log(tc));

// OR

const tc = FileTraceWriter.parseToTC(traceFileContents);
console.log(tc);