blob: 088bd6d893045d734c3dc4aa728cd81e62f597ae [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <string.h>
#include "jsapi.h"
#include "jsalloc.h"
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsfun.h"
#include "jsobj.h"
#include "jsprf.h"
#include "jsutil.h"
#include "jsobjinlines.h"
using namespace js;
#ifdef DEBUG
/*** class HeapReverser **************************************************************************/
/*
* A class for constructing a map of the JavaScript heap, with all
* reference edges reversed.
*
* Unfortunately, it's not possible to build the results for findReferences
* while visiting things solely in the order that JS_TraceRuntime and
* JS_TraceChildren reaches them. For example, as you work outward from the
* roots, suppose an edge from thing T reaches a "gray" thing G --- G being gray
* because you're still in the midst of traversing its descendants. At this
* point, you don't know yet whether G will be a referrer or not, and so you
* can't tell whether T should be a referrer either. And you won't visit T
* again.
*
* So we take a brute-force approach. We reverse the entire graph, and then walk
* outward from |target| to the representable objects that refer to it, stopping
* at such objects.
*/
/*
* A JSTracer that produces a map of the heap with edges reversed.
*
* HeapReversers must be allocated in a stack frame. (They contain an AutoArrayRooter,
* and those must be allocated and destroyed in a stack-like order.)
*
* HeapReversers keep all the roots they find in their traversal alive until
* they are destroyed. So you don't need to worry about nodes going away while
* you're using them.
*/
class HeapReverser : public JSTracer {
public:
struct Edge;
/* Metadata for a given Cell we have visited. */
class Node {
public:
Node() { }
Node(JSGCTraceKind kind)
: kind(kind), incoming(), marked(false) { }
/*
* Move constructor and move assignment. These allow us to store our
* incoming edge Vector in the hash table: Vectors support moves, but
* not assignments or copy construction.
*/
Node(MoveRef<Node> rhs)
: kind(rhs->kind), incoming(Move(rhs->incoming)), marked(rhs->marked) { }
Node &operator=(MoveRef<Node> rhs) {
this->~Node();
new(this) Node(rhs);
return *this;
}
/* What kind of Cell this is. */
JSGCTraceKind kind;
/*
* A vector of this Cell's incoming edges.
* This must use SystemAllocPolicy because HashMap requires its elements to
* be constructible with no arguments.
*/
Vector<Edge, 0, SystemAllocPolicy> incoming;
/* A mark bit, for other traversals. */
bool marked;
private:
Node(const Node &);
Node &operator=(const Node &);
};
/* Metadata for a heap edge we have traversed. */
struct Edge {
public:
Edge(char *name, void *origin) : name(name), origin(origin) { }
~Edge() { js_free(name); }
/*
* Move constructor and move assignment. These allow us to live in
* Vectors without needing to copy our name string when the vector is
* resized.
*/
Edge(MoveRef<Edge> rhs) : name(rhs->name), origin(rhs->origin) {
rhs->name = NULL;
}
Edge &operator=(MoveRef<Edge> rhs) {
this->~Edge();
new(this) Edge(rhs);
return *this;
}
/* The name of this heap edge. Owned by this Edge. */
char *name;
/*
* The Cell from which this edge originates. NULL means a root. This is
* a cell address instead of a Node * because Nodes live in HashMap
* table entries; if the HashMap reallocates its table, all pointers to
* the Nodes it contains would become invalid. You should look up the
* address here in |map| to find its Node.
*/
void *origin;
};
/*
* The result of a reversal is a map from Cells' addresses to Node
* structures describing their incoming edges.
*/
typedef HashMap<void *, Node, DefaultHasher<void *>, SystemAllocPolicy> Map;
Map map;
/* Construct a HeapReverser for |context|'s heap. */
HeapReverser(JSContext *cx) : rooter(cx, 0, NULL), parent(NULL) {
JS_TracerInit(this, JS_GetRuntime(cx), traverseEdgeWithThis);
}
bool init() { return map.init(); }
/* Build a reversed map of the heap in |map|. */
bool reverseHeap();
private:
/*
* Once we've produced a reversed map of the heap, we need to keep the
* engine from freeing the objects we've found in it, until we're done using
* the map. Even if we're only using the map to construct a result object,
* and not rearranging the heap ourselves, any allocation could cause a
* garbage collection, which could free objects held internally by the
* engine (for example, JaegerMonkey object templates, used by jit scripts).
*
* So, each time reverseHeap reaches any object, we add it to 'roots', which
* is cited by 'rooter', so the object will stay alive long enough for us to
* include it in the results, if needed.
*
* Note that AutoArrayRooters must be constructed and destroyed in a
* stack-like order, so the same rule applies to this HeapReverser. The
* easiest way to satisfy this requirement is to only allocate HeapReversers
* as local variables in functions, or in types that themselves follow that
* rule. This is kind of dumb, but JSAPI doesn't provide any less restricted
* way to register arrays of roots.
*/
Vector<jsval, 0, SystemAllocPolicy> roots;
AutoArrayRooter rooter;
/*
* Return the name of the most recent edge this JSTracer has traversed. The
* result is allocated with malloc; if we run out of memory, raise an error
* in this HeapReverser's context and return NULL.
*
* This may not be called after that edge's call to traverseEdge has
* returned.
*/
char *getEdgeDescription();
/* Class for setting new parent, and then restoring the original. */
class AutoParent {
public:
AutoParent(HeapReverser *reverser, void *newParent) : reverser(reverser) {
savedParent = reverser->parent;
reverser->parent = newParent;
}
~AutoParent() {
reverser->parent = savedParent;
}
private:
HeapReverser *reverser;
void *savedParent;
};
/* A work item in the stack of nodes whose children we need to traverse. */
struct Child {
Child(void *cell, JSGCTraceKind kind) : cell(cell), kind(kind) { }
void *cell;
JSGCTraceKind kind;
};
/*
* A stack of work items. We represent the stack explicitly to avoid
* overflowing the C++ stack when traversing long chains of objects.
*/
Vector<Child, 0, SystemAllocPolicy> work;
/* When traverseEdge is called, the Cell and kind at which the edge originated. */
void *parent;
/* Traverse an edge. */
bool traverseEdge(void *cell, JSGCTraceKind kind);
/*
* JS_TraceRuntime and JS_TraceChildren don't propagate error returns,
* and out-of-memory errors, by design, don't establish an exception in
* |context|, so traverseEdgeWithThis uses this to communicate the
* result of the traversal to reverseHeap.
*/
bool traversalStatus;
/* Static member function wrapping 'traverseEdge'. */
static void traverseEdgeWithThis(JSTracer *tracer, void **thingp, JSGCTraceKind kind) {
HeapReverser *reverser = static_cast<HeapReverser *>(tracer);
if (!reverser->traverseEdge(*thingp, kind))
reverser->traversalStatus = false;
}
/* Return a jsval representing a node, if possible; otherwise, return JSVAL_VOID. */
jsval nodeToValue(void *cell, int kind) {
if (kind != JSTRACE_OBJECT)
return JSVAL_VOID;
JSObject *object = static_cast<JSObject *>(cell);
return OBJECT_TO_JSVAL(object);
}
};
bool
HeapReverser::traverseEdge(void *cell, JSGCTraceKind kind)
{
jsval v = nodeToValue(cell, kind);
if (v.isObject()) {
if (!roots.append(v))
return false;
rooter.changeArray(roots.begin(), roots.length());
}
/* Capture this edge before the JSTracer members get overwritten. */
char *edgeDescription = getEdgeDescription();
if (!edgeDescription)
return false;
Edge e(edgeDescription, parent);
Map::AddPtr a = map.lookupForAdd(cell);
if (!a) {
/*
* We've never visited this cell before. Add it to the map (thus
* marking it as visited), and put it on the work stack, to be
* visited from the main loop.
*/
Node n(kind);
uint32_t generation = map.generation();
if (!map.add(a, cell, Move(n)) ||
!work.append(Child(cell, kind)))
return false;
/* If the map has been resized, re-check the pointer. */
if (map.generation() != generation)
a = map.lookupForAdd(cell);
}
/* Add this edge to the reversed map. */
return a->value.incoming.append(Move(e));
}
bool
HeapReverser::reverseHeap()
{
traversalStatus = true;
/* Prime the work stack with the roots of collection. */
JS_TraceRuntime(this);
if (!traversalStatus)
return false;
/* Traverse children until the stack is empty. */
while (!work.empty()) {
const Child child = work.popCopy();
AutoParent autoParent(this, child.cell);
JS_TraceChildren(this, child.cell, child.kind);
if (!traversalStatus)
return false;
}
return true;
}
char *
HeapReverser::getEdgeDescription()
{
if (!debugPrinter && debugPrintIndex == (size_t) -1) {
const char *arg = static_cast<const char *>(debugPrintArg);
char *name = js_pod_malloc<char>(strlen(arg) + 1);
if (!name)
return NULL;
strcpy(name, arg);
return name;
}
/* Lovely; but a fixed size is required by JSTraceNamePrinter. */
static const int nameSize = 200;
char *name = js_pod_malloc<char>(nameSize);
if (!name)
return NULL;
if (debugPrinter)
debugPrinter(this, name, nameSize);
else
JS_snprintf(name, nameSize, "%s[%lu]",
static_cast<const char *>(debugPrintArg), debugPrintIndex);
/* Shrink storage to fit. */
return static_cast<char *>(js_realloc(name, strlen(name) + 1));
}
/*** class ReferenceFinder ***********************************************************************/
/* A class for finding an object's referrers, given a reversed heap map. */
class ReferenceFinder {
public:
ReferenceFinder(JSContext *cx, const HeapReverser &reverser)
: context(cx), reverser(reverser), result(cx) { }
/* Produce an object describing all references to |target|. */
JSObject *findReferences(HandleObject target);
private:
/* The context in which to do allocation and error-handling. */
JSContext *context;
/* A reversed map of the current heap. */
const HeapReverser &reverser;
/* The results object we're currently building. */
RootedObject result;
/* A list of edges we've traversed to get to a certain point. */
class Path {
public:
Path(const HeapReverser::Edge &edge, Path *next) : edge(edge), next(next) { }
/*
* Compute the full path represented by this Path. The result is
* owned by the caller.
*/
char *computeName(JSContext *cx);
private:
const HeapReverser::Edge &edge;
Path *next;
};
struct AutoNodeMarker {
AutoNodeMarker(HeapReverser::Node *node) : node(node) { node->marked = true; }
~AutoNodeMarker() { node->marked = false; }
private:
HeapReverser::Node *node;
};
/*
* Given that we've reached |cell| via |path|, with all Nodes along that
* path marked, add paths from all reportable objects reachable from cell
* to |result|.
*/
bool visit(void *cell, Path *path);
/*
* If |cell|, of |kind|, is representable as a JavaScript value, return that
* value; otherwise, return JSVAL_VOID.
*/
jsval representable(void *cell, int kind) {
if (kind == JSTRACE_OBJECT) {
JSObject *object = static_cast<JSObject *>(cell);
/* Certain classes of object are for internal use only. */
if (object->is<BlockObject>() ||
object->is<CallObject>() ||
object->is<WithObject>() ||
object->is<DeclEnvObject>()) {
return JSVAL_VOID;
}
/* Internal function objects should also not be revealed. */
if (JS_ObjectIsFunction(context, object) && IsInternalFunctionObject(object))
return JSVAL_VOID;
return OBJECT_TO_JSVAL(object);
}
return JSVAL_VOID;
}
/* Add |referrer| as something that refers to |target| via |path|. */
bool addReferrer(jsval referrer, Path *path);
};
bool
ReferenceFinder::visit(void *cell, Path *path)
{
/* In ReferenceFinder, paths will almost certainly fit on the C++ stack. */
JS_CHECK_RECURSION(context, return false);
/* Have we reached a root? Always report that. */
if (!cell)
return addReferrer(JSVAL_NULL, path);
HeapReverser::Map::Ptr p = reverser.map.lookup(cell);
JS_ASSERT(p);
HeapReverser::Node *node = &p->value;
/* Is |cell| a representable cell, reached via a non-empty path? */
if (path != NULL) {
jsval representation = representable(cell, node->kind);
if (!JSVAL_IS_VOID(representation))
return addReferrer(representation, path);
}
/*
* If we've made a cycle, don't traverse further. We *do* want to include
* paths from the target to itself, so we don't want to do this check until
* after we've possibly reported this cell as a referrer.
*/
if (node->marked)
return true;
AutoNodeMarker marker(node);
/* Visit the origins of all |cell|'s incoming edges. */
for (size_t i = 0; i < node->incoming.length(); i++) {
const HeapReverser::Edge &edge = node->incoming[i];
Path extendedPath(edge, path);
if (!visit(edge.origin, &extendedPath))
return false;
}
return true;
}
char *
ReferenceFinder::Path::computeName(JSContext *cx)
{
/* Walk the edge list and compute the total size of the path. */
size_t size = 6;
for (Path *l = this; l; l = l->next)
size += strlen(l->edge.name) + (l->next ? 2 : 0);
size += 1;
char *path = cx->pod_malloc<char>(size);
if (!path)
return NULL;
/*
* Walk the edge list again, and copy the edge names into place, with
* appropriate separators. Note that we constructed the edge list from
* target to referrer, which means that the list links point *towards* the
* target, so we can walk the list and build the path from left to right.
*/
strcpy(path, "edge: ");
char *next = path + 6;
for (Path *l = this; l; l = l->next) {
strcpy(next, l->edge.name);
next += strlen(next);
if (l->next) {
strcpy(next, "; ");
next += 2;
}
}
JS_ASSERT(next + 1 == path + size);
return path;
}
bool
ReferenceFinder::addReferrer(jsval referrerArg, Path *path)
{
RootedValue referrer(context, referrerArg);
if (!context->compartment()->wrap(context, &referrer))
return false;
ScopedJSFreePtr<char> pathName(path->computeName(context));
if (!pathName)
return false;
/* Find the property of the results object named |pathName|. */
RootedValue valRoot(context);
Value &v = valRoot.get();
if (!JS_GetProperty(context, result, pathName, &v))
return false;
if (v.isUndefined()) {
/* Create an array to accumulate referents under this path. */
JSObject *array = JS_NewArrayObject(context, 1, referrer.address());
if (!array)
return false;
v.setObject(*array);
return !!JS_SetProperty(context, result, pathName, &v);
}
/* The property's value had better be an array. */
RootedObject array(context, &v.toObject());
JS_ASSERT(JS_IsArrayObject(context, array));
/* Append our referrer to this array. */
uint32_t length;
return JS_GetArrayLength(context, array, &length) &&
JS_SetElement(context, array, length, referrer.address());
}
JSObject *
ReferenceFinder::findReferences(HandleObject target)
{
result = JS_NewObject(context, NULL, NULL, NULL);
if (!result)
return NULL;
if (!visit(target, NULL))
return NULL;
return result;
}
/* See help(findReferences). */
JSBool
FindReferences(JSContext *cx, unsigned argc, jsval *vp)
{
if (argc < 1) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
"findReferences", "0", "s");
return false;
}
RootedValue target(cx, JS_ARGV(cx, vp)[0]);
if (!target.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
"argument", "not an object");
return false;
}
/* Walk the JSRuntime, producing a reversed map of the heap. */
HeapReverser reverser(cx);
if (!reverser.init() || !reverser.reverseHeap())
return false;
/* Given the reversed map, find the referents of target. */
ReferenceFinder finder(cx, reverser);
Rooted<JSObject*> targetObj(cx, &target.toObject());
JSObject *references = finder.findReferences(targetObj);
if (!references)
return false;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(references));
return true;
}
#endif /* DEBUG */