Skip to content

Commit

Permalink
work in progress...
Browse files Browse the repository at this point in the history
  • Loading branch information
fitzgen committed Jul 24, 2014
1 parent 23a35bc commit 5683888
Showing 1 changed file with 82 additions and 45 deletions.
127 changes: 82 additions & 45 deletions strace.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (typeof Set != "function") {
};

Set.prototype.has = function (x) {
return indexOf.call(this._items, x) === -1;
return indexOf.call(this._items, x) !== -1;
};

return Set;
Expand All @@ -36,21 +36,46 @@ if (typeof Set != "function") {
// Hold references to the original, un-instrumented natives we use inside
// monkey punched methods so that they don't get logged all the time and/or
// cause too-much-recursion errors.
var log = console.log.bind(console);
var trace = console.trace.bind(console);
var warn = console.warn.bind(console);
var functionProtoToString = Function.prototype.toString;
var objectProtoToString = Object.prototype.toString;
var slice = Array.prototype.slice;
var arrayProtoPush = Array.prototype.push;
var apply = Function.prototype.apply;

function toArray(it) {
return slice.call(it);

var log = console.log.bind(console);
var table = "table" in console ? console.table.bind(console) : log;
var trace = console.trace.bind(console);
var warn = console.warn.bind(console);

var call = Function.prototype.call;
var apply = call.bind(Function.prototype.apply);
var functionProtoToString = call.bind(Function.prototype.toString);
var objectProtoToString = call.bind(Object.prototype.toString);
var slice = call.bind(Array.prototype.slice);
var arrayProtoPush = call.bind(Array.prototype.push);
var forEach = call.bind(Array.prototype.forEach);
var push = call.bind(Array.prototype.push);
var toArray = call.bind(Array.prototype.slice);
var test = call.bind(RegExp.prototype.test);

var keys = Object.keys;
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var defineProperty = Object.defineProperty;
var getOwnPropertyNames = Object.getOwnPropertyNames;

var Error_ = Error;

function getDescriptor(obj, property) {
try {
return getOwnPropertyDescriptor(obj, property);
} catch(e) {
warn(e);
}
}

function push(array, item) {
return arrayProtoPush.call(array, item);
function setProperty(obj, prop, desc) {
try {
defineProperty(obj, prop, desc);
} catch (e) {
// Most likely a non-configurable property. In any case, there's nothing
// we can do.
warn(e);
}
}

// Matches the "function () { [native code] }" form that native functions
Expand All @@ -64,7 +89,7 @@ if (typeof Set != "function") {
}

try {
return NATIVE_FUNCTION_SOURCE_REGEXP.test(functionProtoToString.call(thing));
return NATIVE_FUNCTION_SOURCE_REGEXP.test(functionProtoToString(thing));
} catch (e) {
return false;
}
Expand All @@ -73,29 +98,22 @@ if (typeof Set != "function") {
var TYPES = ["value", "get", "set"];
var TYPES_LENGTH = TYPES.length;

// To a depth first search of all objects on the heap reachable from node. The
// Do a depth first search of all objects on the heap reachable from node. The
// visit function is called for each native function found.
function dfs(node, seen, visit) {
if (!node) {
if (!node || seen.has(node)) {
return;
}

seen.add(node);

if (typeof node == "object" || typeof node == "function") {
var edges = Object.getOwnPropertyNames(node);
var edges = getOwnPropertyNames(node);
push(edges, "__proto__");

for (var i = edges.length; i; i--) {
var name = edges[i];

try {
var desc = Object.getOwnPropertyDescriptor(node, name);
} catch(e) { }

var desc = getDescriptor(node, name);
if (!desc) {
warn("strace.js: Cannot read property descriptor for '%s'",
makePrettyName(node, name, "value"));
continue;
}

Expand All @@ -115,7 +133,7 @@ if (typeof Set != "function") {
}

function makePrettyName(parent, name, type) {
var parentName = objectProtoToString.call(parent).slice(8, -1);
var parentName = objectProtoToString(parent).slice(8, -1);

if (type == "value") {
return parentName + "." + name;
Expand All @@ -127,39 +145,42 @@ if (typeof Set != "function") {
function copyProperty(func, punched, desc, name) {
// TODO remove desc param and just get it inside here?

if (!desc.configurable) {
if (!desc || !desc.configurable) {
return;
}

if (typeof desc.value == "function") {
var value = desc.value;
desc.value = function () {
return value.apply(this === punched ? func : this, arguments);
return apply(value, this === punched ? func : this, arguments);
};
}
if (typeof desc.set == "function") {
var set = desc.set;
desc.set = function () {
return set.apply(this === punched ? func : this, arguments);
return apply(set, this === punched ? func : this, arguments);
};
}
if (typeof desc.get == "function") {
var get = desc.get;
desc.get = function () {
return get.apply(this === punched ? func : this, arguments);
return apply(get, this === punched ? func : this, arguments);
};
}

Object.defineProperty(punched, name, desc);
setProperty(punched, name, desc);
}

// Instrument via monkey-punching the function `func` at `parent[name]` as
// either a normal value, getter, or setter, depending on if `type` is
// "value", "get", or "set" respectively.
function punch(func, parent, name, type) {
var desc = Object.getOwnPropertyDescriptor(parent, name);
var desc = getDescriptor(parent, name);
if (!desc || !desc.configurable || name === "stack") {
return;
}

var prettyName = makePrettyName(parent, name, type);
log("strace.js: Instrumenting '%s'.", prettyName);

// For whatever reason, native methods complain about invalid `this` values
// when we construct instances like this:
Expand All @@ -177,6 +198,10 @@ if (typeof Set != "function") {
var punched = desc[type] = function punched(a, b, c, d, e, f, g, h, i, j, k,
l, m, n, o, p, q, r, s, t, u, v,
w, x, y, z) {
if (test(/InjectedScript\./, Error_().stack)) {
return;
}

if (this
&& typeof this == "object"
&& punched.prototype
Expand Down Expand Up @@ -230,29 +255,26 @@ if (typeof Set != "function") {
if (strace.loggingArguments) {
push(logArgs, toArray(arguments));
}
apply.call(log, null, logArgs);
apply(log, null, logArgs);
if (strace.loggingStacks) {
trace();
}
}

return func.apply(this, arguments);
incrementAggregateCount(prettyName);
return apply(func, this, arguments);
};

var toCopy = Object.getOwnPropertyNames(func);
var toCopy = getOwnPropertyNames(func);
for (var i = 0, len = toCopy.length; i < len; i++) {
copyProperty(func, punched,
Object.getOwnPropertyDescriptor(func, toCopy[i]), toCopy[i]);
getDescriptor(func, toCopy[i]), toCopy[i]);
}

punched.prototype = func.prototype;
punched.__$$_strace_original_$$__ = func;

try {
Object.defineProperty(parent, name, desc);
} catch (e) {
warn("strace.js: Failed to instrument '%s'", prettyName);
}
setProperty(parent, name, desc);
}

function findNatives(root, seen) {
Expand All @@ -270,6 +292,17 @@ if (typeof Set != "function") {
return natives;
}

// TODO FITZGEN
var aggregateData = Object.create(null);

function incrementAggregateCount(name) {
if (!aggregateData[name]) {
aggregateData[name] = 1;
} else {
aggregateData[name]++;
}
}

// The set of nodes in the heap we have already visited and, if the node is a
// native function, already instrumented.
var seen = new Set();
Expand Down Expand Up @@ -303,12 +336,16 @@ if (typeof Set != "function") {

// Clears strace's aggregated data.
strace.clear = function () {
TODO;
aggregateData = Object.create(null);
};

// Logs a summary of aggregated data.
strace.summary = function () {
TODO;
var rows = [];
forEach(keys(aggregateData), function (key) {
push(rows, { native: key, count: aggregateData[key] });
});
table(rows);
};

return strace;
Expand Down

0 comments on commit 5683888

Please sign in to comment.