js/src/shell/jsheaptools.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "shell/jsheaptools.h"
     9 #include "mozilla/Move.h"
    11 #include <string.h>
    13 #include "jsalloc.h"
    14 #include "jsapi.h"
    15 #include "jscntxt.h"
    16 #include "jscompartment.h"
    17 #include "jsobj.h"
    18 #include "jsprf.h"
    20 #include "jsobjinlines.h"
    22 using namespace js;
    24 using mozilla::Move;
    26 #ifdef DEBUG
    29 /*** class HeapReverser **************************************************************************/
    31 /*
    32  * A class for constructing a map of the JavaScript heap, with all
    33  * reference edges reversed.
    34  *
    35  * Unfortunately, it's not possible to build the results for findReferences
    36  * while visiting things solely in the order that JS_TraceRuntime and
    37  * JS_TraceChildren reaches them. For example, as you work outward from the
    38  * roots, suppose an edge from thing T reaches a "gray" thing G --- G being gray
    39  * because you're still in the midst of traversing its descendants. At this
    40  * point, you don't know yet whether G will be a referrer or not, and so you
    41  * can't tell whether T should be a referrer either. And you won't visit T
    42  * again.
    43  *
    44  * So we take a brute-force approach. We reverse the entire graph, and then walk
    45  * outward from |target| to the representable objects that refer to it, stopping
    46  * at such objects.
    47  */
    49 /*
    50  * A JSTracer that produces a map of the heap with edges reversed.
    51  *
    52  * HeapReversers must be allocated in a stack frame. (They are derived from
    53  * CustomAutoRooter, and those must be allocated and destroyed in a stack-like
    54  * order.)
    55  *
    56  * HeapReversers keep all the roots they find in their traversal alive until
    57  * they are destroyed. So you don't need to worry about nodes going away while
    58  * you're using them.
    59  */
    60 class HeapReverser : public JSTracer, public JS::CustomAutoRooter
    61 {
    62   public:
    63     struct Edge;
    65     /* Metadata for a given Cell we have visited. */
    66     class Node {
    67       public:
    68         Node() { }
    69         Node(JSGCTraceKind kind)
    70           : kind(kind), incoming(), marked(false) { }
    72         /*
    73          * Move constructor and move assignment. These allow us to store our
    74          * incoming edge Vector in the hash table: Vectors support moves, but
    75          * not assignments or copy construction.
    76          */
    77         Node(Node &&rhs)
    78           : kind(rhs.kind), incoming(Move(rhs.incoming)), marked(rhs.marked) { }
    79         Node &operator=(Node &&rhs) {
    80             MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
    81             this->~Node();
    82             new(this) Node(Move(rhs));
    83             return *this;
    84         }
    86         void trace(JSTracer *trc) {
    87             for (Edge *e = incoming.begin(); e != incoming.end(); e++)
    88                 e->trace(trc);
    89         }
    91         /* What kind of Cell this is. */
    92         JSGCTraceKind kind;
    94         /*
    95          * A vector of this Cell's incoming edges.
    96          * This must use SystemAllocPolicy because HashMap requires its elements to
    97          * be constructible with no arguments.
    98          */
    99         Vector<Edge, 0, SystemAllocPolicy> incoming;
   101         /* A mark bit, for other traversals. */
   102         bool marked;
   104       private:
   105         Node(const Node &) MOZ_DELETE;
   106         Node &operator=(const Node &) MOZ_DELETE;
   107     };
   109     /* Metadata for a heap edge we have traversed. */
   110     struct Edge {
   111       public:
   112         Edge(char *name, void *origin) : name(name), origin(origin) { }
   113         ~Edge() { js_free(name); }
   115         /*
   116          * Move constructor and move assignment. These allow us to live in
   117          * Vectors without needing to copy our name string when the vector is
   118          * resized.
   119          */
   120         Edge(Edge &&rhs) : name(rhs.name), origin(rhs.origin) {
   121             rhs.name = nullptr;
   122         }
   123         Edge &operator=(Edge &&rhs) {
   124             MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
   125             this->~Edge();
   126             new(this) Edge(Move(rhs));
   127             return *this;
   128         }
   130         void trace(JSTracer *trc) {
   131             if (origin)
   132                 gc::MarkGCThingRoot(trc, &origin, "HeapReverser::Edge");
   133         }
   135         /* The name of this heap edge. Owned by this Edge. */
   136         char *name;
   138         /*
   139          * The Cell from which this edge originates. nullptr means a root. This
   140          * is a cell address instead of a Node * because Nodes live in HashMap
   141          * table entries; if the HashMap reallocates its table, all pointers to
   142          * the Nodes it contains would become invalid. You should look up the
   143          * address here in |map| to find its Node.
   144          */
   145         void *origin;
   146     };
   148     /*
   149      * The result of a reversal is a map from Cells' addresses to Node
   150      * structures describing their incoming edges.
   151      */
   152     typedef HashMap<void *, Node, DefaultHasher<void *>, SystemAllocPolicy> Map;
   153     Map map;
   155     /* Construct a HeapReverser for |context|'s heap. */
   156     HeapReverser(JSContext *cx)
   157       : JSTracer(cx->runtime(), traverseEdgeWithThis),
   158         JS::CustomAutoRooter(cx),
   159         noggc(JS_GetRuntime(cx)),
   160         runtime(JS_GetRuntime(cx)),
   161         parent(nullptr)
   162     {
   163     }
   165     bool init() { return map.init(); }
   167     /* Build a reversed map of the heap in |map|. */
   168     bool reverseHeap();
   170   private:
   171     JS::AutoDisableGenerationalGC noggc;
   173     /* A runtime pointer for use by the destructor. */
   174     JSRuntime *runtime;
   176     /*
   177      * Return the name of the most recent edge this JSTracer has traversed. The
   178      * result is allocated with malloc; if we run out of memory, raise an error
   179      * in this HeapReverser's context and return nullptr.
   180      *
   181      * This may not be called after that edge's call to traverseEdge has
   182      * returned.
   183      */
   184     char *getEdgeDescription();
   186     /* Class for setting new parent, and then restoring the original. */
   187     class AutoParent {
   188       public:
   189         AutoParent(HeapReverser *reverser, void *newParent) : reverser(reverser) {
   190             savedParent = reverser->parent;
   191             reverser->parent = newParent;
   192         }
   193         ~AutoParent() {
   194             reverser->parent = savedParent;
   195         }
   196       private:
   197         HeapReverser *reverser;
   198         void *savedParent;
   199     };
   201     /* A work item in the stack of nodes whose children we need to traverse. */
   202     struct Child {
   203         Child(void *cell, JSGCTraceKind kind) : cell(cell), kind(kind) { }
   204         void *cell;
   205         JSGCTraceKind kind;
   206     };
   208     /*
   209      * A stack of work items. We represent the stack explicitly to avoid
   210      * overflowing the C++ stack when traversing long chains of objects.
   211      */
   212     Vector<Child, 0, SystemAllocPolicy> work;
   214     /* When traverseEdge is called, the Cell and kind at which the edge originated. */
   215     void *parent;
   217     /* Traverse an edge. */
   218     bool traverseEdge(void *cell, JSGCTraceKind kind);
   220     /*
   221      * JS_TraceRuntime and JS_TraceChildren don't propagate error returns,
   222      * and out-of-memory errors, by design, don't establish an exception in
   223      * |context|, so traverseEdgeWithThis uses this to communicate the
   224      * result of the traversal to reverseHeap.
   225      */
   226     bool traversalStatus;
   228     /* Static member function wrapping 'traverseEdge'. */
   229     static void traverseEdgeWithThis(JSTracer *tracer, void **thingp, JSGCTraceKind kind) {
   230         HeapReverser *reverser = static_cast<HeapReverser *>(tracer);
   231         if (!reverser->traverseEdge(*thingp, kind))
   232             reverser->traversalStatus = false;
   233     }
   235     /* Return a jsval representing a node, if possible; otherwise, return JSVAL_VOID. */
   236     jsval nodeToValue(void *cell, int kind) {
   237         if (kind != JSTRACE_OBJECT)
   238             return JSVAL_VOID;
   239         JSObject *object = static_cast<JSObject *>(cell);
   240         return OBJECT_TO_JSVAL(object);
   241     }
   243     /* Keep all tracked objects live across GC. */
   244     virtual void trace(JSTracer *trc) MOZ_OVERRIDE {
   245         if (!map.initialized())
   246             return;
   247         for (Map::Enum e(map); !e.empty(); e.popFront()) {
   248             gc::MarkGCThingRoot(trc, const_cast<void **>(&e.front().key()), "HeapReverser::map::key");
   249             e.front().value().trace(trc);
   250         }
   251         for (Child *c = work.begin(); c != work.end(); ++c)
   252             gc::MarkGCThingRoot(trc, &c->cell, "HeapReverser::Child");
   253     }
   254 };
   256 bool
   257 HeapReverser::traverseEdge(void *cell, JSGCTraceKind kind)
   258 {
   259     /* Capture this edge before the JSTracer members get overwritten. */
   260     char *edgeDescription = getEdgeDescription();
   261     if (!edgeDescription)
   262         return false;
   263     Edge e(edgeDescription, parent);
   265     Map::AddPtr a = map.lookupForAdd(cell);
   266     if (!a) {
   267         /*
   268          * We've never visited this cell before. Add it to the map (thus
   269          * marking it as visited), and put it on the work stack, to be
   270          * visited from the main loop.
   271          */
   272         Node n(kind);
   273         uint32_t generation = map.generation();
   274         if (!map.add(a, cell, Move(n)) ||
   275             !work.append(Child(cell, kind)))
   276             return false;
   277         /* If the map has been resized, re-check the pointer. */
   278         if (map.generation() != generation)
   279             a = map.lookupForAdd(cell);
   280     }
   282     /* Add this edge to the reversed map. */
   283     return a->value().incoming.append(Move(e));
   284 }
   286 bool
   287 HeapReverser::reverseHeap()
   288 {
   289     traversalStatus = true;
   291     /* Prime the work stack with the roots of collection. */
   292     JS_TraceRuntime(this);
   293     if (!traversalStatus)
   294         return false;
   296     /* Traverse children until the stack is empty. */
   297     while (!work.empty()) {
   298         const Child child = work.popCopy();
   299         AutoParent autoParent(this, child.cell);
   300         JS_TraceChildren(this, child.cell, child.kind);
   301         if (!traversalStatus)
   302             return false;
   303     }
   305     return true;
   306 }
   308 char *
   309 HeapReverser::getEdgeDescription()
   310 {
   311     if (!debugPrinter() && debugPrintIndex() == (size_t) -1) {
   312         const char *arg = static_cast<const char *>(debugPrintArg());
   313         char *name = js_pod_malloc<char>(strlen(arg) + 1);
   314         if (!name)
   315             return nullptr;
   316         strcpy(name, arg);
   317         return name;
   318     }
   320     /* Lovely; but a fixed size is required by JSTraceNamePrinter. */
   321     static const int nameSize = 200;
   322     char *name = js_pod_malloc<char>(nameSize);
   323     if (!name)
   324         return nullptr;
   325     if (debugPrinter())
   326         debugPrinter()(this, name, nameSize);
   327     else
   328         JS_snprintf(name, nameSize, "%s[%lu]",
   329                     static_cast<const char *>(debugPrintArg()), debugPrintIndex());
   331     /* Shrink storage to fit. */
   332     return static_cast<char *>(js_realloc(name, strlen(name) + 1));
   333 }
   336 /*** class ReferenceFinder ***********************************************************************/
   338 /* A class for finding an object's referrers, given a reversed heap map. */
   339 class ReferenceFinder {
   340   public:
   341     ReferenceFinder(JSContext *cx, const HeapReverser &reverser)
   342       : context(cx), reverser(reverser), result(cx) { }
   344     /* Produce an object describing all references to |target|. */
   345     JSObject *findReferences(HandleObject target);
   347   private:
   348     /* The context in which to do allocation and error-handling. */
   349     JSContext *context;
   351     /* A reversed map of the current heap. */
   352     const HeapReverser &reverser;
   354     /* The results object we're currently building. */
   355     RootedObject result;
   357     /* A list of edges we've traversed to get to a certain point. */
   358     class Path {
   359       public:
   360         Path(const HeapReverser::Edge &edge, Path *next) : edge(edge), next(next) { }
   362         /*
   363          * Compute the full path represented by this Path. The result is
   364          * owned by the caller.
   365          */
   366         char *computeName(JSContext *cx);
   368       private:
   369         const HeapReverser::Edge &edge;
   370         Path *next;
   371     };
   373     struct AutoNodeMarker {
   374         AutoNodeMarker(HeapReverser::Node *node) : node(node) { node->marked = true; }
   375         ~AutoNodeMarker() { node->marked = false; }
   376       private:
   377         HeapReverser::Node *node;
   378     };
   380     /*
   381      * Given that we've reached |cell| via |path|, with all Nodes along that
   382      * path marked, add paths from all reportable objects reachable from cell
   383      * to |result|.
   384      */
   385     bool visit(void *cell, Path *path);
   387     /*
   388      * If |cell|, of |kind|, is representable as a JavaScript value, return that
   389      * value; otherwise, return JSVAL_VOID.
   390      */
   391     jsval representable(void *cell, int kind) {
   392         if (kind == JSTRACE_OBJECT) {
   393             JSObject *object = static_cast<JSObject *>(cell);
   395             /* Certain classes of object are for internal use only. */
   396             if (object->is<BlockObject>() ||
   397                 object->is<CallObject>() ||
   398                 object->is<StaticWithObject>() ||
   399                 object->is<DynamicWithObject>() ||
   400                 object->is<DeclEnvObject>()) {
   401                 return JSVAL_VOID;
   402             }
   404             /* Internal function objects should also not be revealed. */
   405             if (JS_ObjectIsFunction(context, object) && IsInternalFunctionObject(object))
   406                 return JSVAL_VOID;
   408             return OBJECT_TO_JSVAL(object);
   409         }
   411         return JSVAL_VOID;
   412     }
   414     /* Add |referrer| as something that refers to |target| via |path|. */
   415     bool addReferrer(jsval referrer, Path *path);
   416 };
   418 bool
   419 ReferenceFinder::visit(void *cell, Path *path)
   420 {
   421     /* In ReferenceFinder, paths will almost certainly fit on the C++ stack. */
   422     JS_CHECK_RECURSION(context, return false);
   424     /* Have we reached a root? Always report that. */
   425     if (!cell)
   426         return addReferrer(JSVAL_NULL, path);
   428     HeapReverser::Map::Ptr p = reverser.map.lookup(cell);
   429     JS_ASSERT(p);
   430     HeapReverser::Node *node = &p->value();
   432     /* Is |cell| a representable cell, reached via a non-empty path? */
   433     if (path != nullptr) {
   434         jsval representation = representable(cell, node->kind);
   435         if (!JSVAL_IS_VOID(representation))
   436             return addReferrer(representation, path);
   437     }
   439     /*
   440      * If we've made a cycle, don't traverse further. We *do* want to include
   441      * paths from the target to itself, so we don't want to do this check until
   442      * after we've possibly reported this cell as a referrer.
   443      */
   444     if (node->marked)
   445         return true;
   446     AutoNodeMarker marker(node);
   448     /* Visit the origins of all |cell|'s incoming edges. */
   449     for (size_t i = 0; i < node->incoming.length(); i++) {
   450         const HeapReverser::Edge &edge = node->incoming[i];
   451         Path extendedPath(edge, path);
   452         if (!visit(edge.origin, &extendedPath))
   453             return false;
   454     }
   456     return true;
   457 }
   459 char *
   460 ReferenceFinder::Path::computeName(JSContext *cx)
   461 {
   462     /* Walk the edge list and compute the total size of the path. */
   463     size_t size = 6;
   464     for (Path *l = this; l; l = l->next)
   465         size += strlen(l->edge.name) + (l->next ? 2 : 0);
   466     size += 1;
   468     char *path = cx->pod_malloc<char>(size);
   469     if (!path)
   470         return nullptr;
   472     /*
   473      * Walk the edge list again, and copy the edge names into place, with
   474      * appropriate separators. Note that we constructed the edge list from
   475      * target to referrer, which means that the list links point *towards* the
   476      * target, so we can walk the list and build the path from left to right.
   477      */
   478     strcpy(path, "edge: ");
   479     char *next = path + 6;
   480     for (Path *l = this; l; l = l->next) {
   481         strcpy(next, l->edge.name);
   482         next += strlen(next);
   483         if (l->next) {
   484             strcpy(next, "; ");
   485             next += 2;
   486         }
   487     }
   488     JS_ASSERT(next + 1 == path + size);
   490     return path;
   491 }
   493 bool
   494 ReferenceFinder::addReferrer(jsval referrerArg, Path *path)
   495 {
   496     RootedValue referrer(context, referrerArg);
   498     if (!context->compartment()->wrap(context, &referrer))
   499         return false;
   501     ScopedJSFreePtr<char> pathName(path->computeName(context));
   502     if (!pathName)
   503         return false;
   505     /* Find the property of the results object named |pathName|. */
   506     RootedValue v(context);
   508     if (!JS_GetProperty(context, result, pathName, &v))
   509         return false;
   510     if (v.isUndefined()) {
   511         /* Create an array to accumulate referents under this path. */
   512         JSObject *array = JS_NewArrayObject(context, referrer);
   513         if (!array)
   514             return false;
   515         v.setObject(*array);
   516         return !!JS_SetProperty(context, result, pathName, v);
   517     }
   519     /* The property's value had better be an array. */
   520     RootedObject array(context, &v.toObject());
   521     JS_ASSERT(JS_IsArrayObject(context, array));
   523     /* Append our referrer to this array. */
   524     uint32_t length;
   525     return JS_GetArrayLength(context, array, &length) &&
   526            JS_SetElement(context, array, length, referrer);
   527 }
   529 JSObject *
   530 ReferenceFinder::findReferences(HandleObject target)
   531 {
   532     result = JS_NewObject(context, nullptr, JS::NullPtr(), JS::NullPtr());
   533     if (!result)
   534         return nullptr;
   535     if (!visit(target, nullptr))
   536         return nullptr;
   538     return result;
   539 }
   541 /* See help(findReferences). */
   542 bool
   543 FindReferences(JSContext *cx, unsigned argc, jsval *vp)
   544 {
   545     CallArgs args = CallArgsFromVp(argc, vp);
   546     if (args.length() < 1) {
   547         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
   548                              "findReferences", "0", "s");
   549         return false;
   550     }
   552     RootedValue target(cx, args[0]);
   553     if (!target.isObject()) {
   554         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
   555                              "argument", "not an object");
   556         return false;
   557     }
   559     /* Walk the JSRuntime, producing a reversed map of the heap. */
   560     HeapReverser reverser(cx);
   561     if (!reverser.init() || !reverser.reverseHeap())
   562         return false;
   564     /* Given the reversed map, find the referents of target. */
   565     ReferenceFinder finder(cx, reverser);
   566     Rooted<JSObject*> targetObj(cx, &target.toObject());
   567     JSObject *references = finder.findReferences(targetObj);
   568     if (!references)
   569         return false;
   571     args.rval().setObject(*references);
   572     return true;
   573 }
   575 #endif /* DEBUG */

mercurial