1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is JavaScript heap tools.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Mozilla Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 2010
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Jim Blandy <jimb@mozilla.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include <string.h>
42 :
43 : #include "jsapi.h"
44 :
45 : #include "jsalloc.h"
46 : #include "jscntxt.h"
47 : #include "jscompartment.h"
48 : #include "jsfun.h"
49 : #include "jsobj.h"
50 : #include "jsprf.h"
51 : #include "jsutil.h"
52 :
53 : #include "jsobjinlines.h"
54 :
55 : using namespace js;
56 :
57 : #ifdef DEBUG
58 :
59 :
60 : /*** class HeapReverser **************************************************************************/
61 :
62 : /*
63 : * A class for constructing a map of the JavaScript heap, with all
64 : * reference edges reversed.
65 : *
66 : * Unfortunately, it's not possible to build the results for findReferences
67 : * while visiting things solely in the order that JS_TraceRuntime and
68 : * JS_TraceChildren reaches them. For example, as you work outward from the
69 : * roots, suppose an edge from thing T reaches a "gray" thing G --- G being gray
70 : * because you're still in the midst of traversing its descendants. At this
71 : * point, you don't know yet whether G will be a referrer or not, and so you
72 : * can't tell whether T should be a referrer either. And you won't visit T
73 : * again.
74 : *
75 : * So we take a brute-force approach. We reverse the entire graph, and then walk
76 : * outward from |target| to the representable objects that refer to it, stopping
77 : * at such objects.
78 : */
79 :
80 : /*
81 : * A JSTracer that produces a map of the heap with edges reversed.
82 : *
83 : * HeapReversers must be allocated in a stack frame. (They contain an AutoArrayRooter,
84 : * and those must be allocated and destroyed in a stack-like order.)
85 : *
86 : * HeapReversers keep all the roots they find in their traversal alive until
87 : * they are destroyed. So you don't need to worry about nodes going away while
88 : * you're using them.
89 : */
90 540 : class HeapReverser : public JSTracer {
91 : public:
92 : struct Edge;
93 :
94 : /* Metadata for a given Cell we have visited. */
95 30986208 : class Node {
96 : public:
97 17677440 : Node() { }
98 3343104 : Node(JSGCTraceKind kind)
99 3343104 : : kind(kind), incoming(), marked(false) { }
100 :
101 : /*
102 : * Move constructor and move assignment. These allow us to store our
103 : * incoming edge Vector in the hash table: Vectors support moves, but
104 : * not assignments or copy construction.
105 : */
106 9965664 : Node(MoveRef<Node> rhs)
107 9965664 : : kind(rhs->kind), incoming(Move(rhs->incoming)), marked(rhs->marked) { }
108 9965664 : Node &operator=(MoveRef<Node> rhs) {
109 9965664 : this->~Node();
110 9965664 : new(this) Node(rhs);
111 9965664 : return *this;
112 : }
113 :
114 : /* What kind of Cell this is. */
115 : JSGCTraceKind kind;
116 :
117 : /*
118 : * A vector of this Cell's incoming edges.
119 : * This must use SystemAllocPolicy because HashMap requires its elements to
120 : * be constructible with no arguments.
121 : */
122 : Vector<Edge, 0, SystemAllocPolicy> incoming;
123 :
124 : /* A mark bit, for other traversals. */
125 : bool marked;
126 :
127 : private:
128 : Node(const Node &);
129 : Node &operator=(const Node &);
130 : };
131 :
132 : /* Metadata for a heap edge we have traversed. */
133 : struct Edge {
134 : public:
135 4419644 : Edge(char *name, void *origin) : name(name), origin(origin) { }
136 10347505 : ~Edge() { js_free(name); }
137 :
138 : /*
139 : * Move constructor and move assignment. These allow us to live in
140 : * Vectors without needing to copy our name string when the vector is
141 : * resized.
142 : */
143 5927861 : Edge(MoveRef<Edge> rhs) : name(rhs->name), origin(rhs->origin) {
144 5927861 : rhs->name = NULL;
145 5927861 : }
146 : Edge &operator=(MoveRef<Edge> rhs) {
147 : this->~Edge();
148 : new(this) Edge(rhs);
149 : return *this;
150 : }
151 :
152 : /* The name of this heap edge. Owned by this Edge. */
153 : char *name;
154 :
155 : /*
156 : * The Cell from which this edge originates. NULL means a root. This is
157 : * a cell address instead of a Node * because Nodes live in HashMap
158 : * table entries; if the HashMap reallocates its table, all pointers to
159 : * the Nodes it contains would become invalid. You should look up the
160 : * address here in |map| to find its Node.
161 : */
162 : void *origin;
163 : };
164 :
165 : /*
166 : * The result of a reversal is a map from Cells' addresses to Node
167 : * structures describing their incoming edges.
168 : */
169 : typedef HashMap<void *, Node, DefaultHasher<void *>, SystemAllocPolicy> Map;
170 : Map map;
171 :
172 : /* Construct a HeapReverser for |context|'s heap. */
173 540 : HeapReverser(JSContext *cx) : rooter(cx, 0, NULL), parent(NULL) {
174 540 : JS_TracerInit(this, JS_GetRuntime(cx), traverseEdgeWithThis);
175 540 : }
176 :
177 540 : bool init() { return map.init(); }
178 :
179 : /* Build a reversed map of the heap in |map|. */
180 : bool reverseHeap();
181 :
182 : private:
183 : /*
184 : * Conservative scanning can, on a whim, decide that a root is no longer a
185 : * root, and cause bits of our graph to disappear. The 'roots' vector holds
186 : * all the roots we find alive, and 'rooter' keeps them alive until we're
187 : * destroyed.
188 : *
189 : * Note that AutoArrayRooters must be constructed and destroyed in a
190 : * stack-like order, so the same rule applies to this HeapReverser. The
191 : * easiest way to satisfy this requirement is to only allocate HeapReversers
192 : * as local variables in functions, or in types that themselves follow that
193 : * rule. This is kind of dumb, but JSAPI doesn't provide any less restricted
194 : * way to register arrays of roots.
195 : */
196 : Vector<jsval, 0, SystemAllocPolicy> roots;
197 : AutoArrayRooter rooter;
198 :
199 : /*
200 : * Return the name of the most recent edge this JSTracer has traversed. The
201 : * result is allocated with malloc; if we run out of memory, raise an error
202 : * in this HeapReverser's context and return NULL.
203 : *
204 : * This may not be called after that edge's call to traverseEdge has
205 : * returned.
206 : */
207 : char *getEdgeDescription();
208 :
209 : /* Class for setting new parent, and then restoring the original. */
210 : class AutoParent {
211 : public:
212 3343104 : AutoParent(HeapReverser *reverser, void *newParent) : reverser(reverser) {
213 3343104 : savedParent = reverser->parent;
214 3343104 : reverser->parent = newParent;
215 3343104 : }
216 3343104 : ~AutoParent() {
217 3343104 : reverser->parent = savedParent;
218 3343104 : }
219 : private:
220 : HeapReverser *reverser;
221 : void *savedParent;
222 : };
223 :
224 : /* A work item in the stack of nodes whose children we need to traverse. */
225 7766244 : struct Child {
226 3343104 : Child(void *cell, JSGCTraceKind kind) : cell(cell), kind(kind) { }
227 : void *cell;
228 : JSGCTraceKind kind;
229 : };
230 :
231 : /*
232 : * A stack of work items. We represent the stack explicitly to avoid
233 : * overflowing the C++ stack when traversing long chains of objects.
234 : */
235 : Vector<Child, 0, SystemAllocPolicy> work;
236 :
237 : /* When traverseEdge is called, the Cell and kind at which the edge originated. */
238 : void *parent;
239 :
240 : /* Traverse an edge. */
241 : bool traverseEdge(void *cell, JSGCTraceKind kind);
242 :
243 : /*
244 : * JS_TraceRuntime and JS_TraceChildren don't propagate error returns,
245 : * and out-of-memory errors, by design, don't establish an exception in
246 : * |context|, so traverseEdgeWithThis uses this to communicate the
247 : * result of the traversal to reverseHeap.
248 : */
249 : bool traversalStatus;
250 :
251 : /* Static member function wrapping 'traverseEdge'. */
252 4419644 : static void traverseEdgeWithThis(JSTracer *tracer, void **thingp, JSGCTraceKind kind) {
253 4419644 : HeapReverser *reverser = static_cast<HeapReverser *>(tracer);
254 4419644 : reverser->traversalStatus = reverser->traverseEdge(*thingp, kind);
255 4419644 : }
256 :
257 : /* Return a jsval representing a node, if possible; otherwise, return JSVAL_VOID. */
258 2603264 : jsval nodeToValue(void *cell, int kind) {
259 2603264 : if (kind != JSTRACE_OBJECT)
260 2581515 : return JSVAL_VOID;
261 21749 : JSObject *object = static_cast<JSObject *>(cell);
262 21749 : return OBJECT_TO_JSVAL(object);
263 : }
264 : };
265 :
266 : bool
267 4419644 : HeapReverser::traverseEdge(void *cell, JSGCTraceKind kind) {
268 : /* If this is a root, make our own root for it as well. */
269 4419644 : if (!parent) {
270 2603264 : if (!roots.append(nodeToValue(cell, kind)))
271 0 : return false;
272 2603264 : rooter.changeArray(roots.begin(), roots.length());
273 : }
274 :
275 : /* Capture this edge before the JSTracer members get overwritten. */
276 4419644 : char *edgeDescription = getEdgeDescription();
277 4419644 : if (!edgeDescription)
278 0 : return false;
279 8839288 : Edge e(edgeDescription, parent);
280 :
281 4419644 : Map::AddPtr a = map.lookupForAdd(cell);
282 4419644 : if (!a) {
283 : /*
284 : * We've never visited this cell before. Add it to the map (thus
285 : * marking it as visited), and put it on the work stack, to be
286 : * visited from the main loop.
287 : */
288 6686208 : Node n(kind);
289 3343104 : uint32_t generation = map.generation();
290 6686208 : if (!map.add(a, cell, Move(n)) ||
291 3343104 : !work.append(Child(cell, kind)))
292 0 : return false;
293 : /* If the map has been resized, re-check the pointer. */
294 3343104 : if (map.generation() != generation)
295 4860 : a = map.lookupForAdd(cell);
296 : }
297 :
298 : /* Add this edge to the reversed map. */
299 4419644 : return a->value.incoming.append(Move(e));
300 : }
301 :
302 : bool
303 540 : HeapReverser::reverseHeap() {
304 : /* Prime the work stack with the roots of collection. */
305 540 : JS_TraceRuntime(this);
306 540 : if (!traversalStatus)
307 0 : return false;
308 :
309 : /* Traverse children until the stack is empty. */
310 3344184 : while (!work.empty()) {
311 3343104 : const Child child = work.popCopy();
312 6686208 : AutoParent autoParent(this, child.cell);
313 3343104 : JS_TraceChildren(this, child.cell, child.kind);
314 3343104 : if (!traversalStatus)
315 0 : return false;
316 : }
317 :
318 540 : return true;
319 : }
320 :
321 : char *
322 4419644 : HeapReverser::getEdgeDescription()
323 : {
324 4419644 : if (!debugPrinter && debugPrintIndex == (size_t) -1) {
325 4051559 : const char *arg = static_cast<const char *>(debugPrintArg);
326 4051559 : char *name = static_cast<char *>(js_malloc(strlen(arg) + 1));
327 4051559 : if (!name)
328 0 : return NULL;
329 4051559 : strcpy(name, arg);
330 4051559 : return name;
331 : }
332 :
333 : /* Lovely; but a fixed size is required by JSTraceNamePrinter. */
334 : static const int nameSize = 200;
335 368085 : char *name = static_cast<char *>(js_malloc(nameSize));
336 368085 : if (!name)
337 0 : return NULL;
338 368085 : if (debugPrinter)
339 359079 : debugPrinter(this, name, nameSize);
340 : else
341 : JS_snprintf(name, nameSize, "%s[%lu]",
342 9006 : static_cast<const char *>(debugPrintArg), debugPrintIndex);
343 :
344 : /* Shrink storage to fit. */
345 368085 : return static_cast<char *>(js_realloc(name, strlen(name) + 1));
346 : }
347 :
348 :
349 : /*** class ReferenceFinder ***********************************************************************/
350 :
351 : /* A class for finding an object's referrers, given a reversed heap map. */
352 : class ReferenceFinder {
353 : public:
354 540 : ReferenceFinder(JSContext *cx, const HeapReverser &reverser)
355 540 : : context(cx), reverser(reverser) { }
356 :
357 : /* Produce an object describing all references to |target|. */
358 : JSObject *findReferences(JSObject *target);
359 :
360 : private:
361 : /* The context in which to do allocation and error-handling. */
362 : JSContext *context;
363 :
364 : /* A reversed map of the current heap. */
365 : const HeapReverser &reverser;
366 :
367 : /* The results object we're currently building. */
368 : JSObject *result;
369 :
370 : /* A list of edges we've traversed to get to a certain point. */
371 : class Path {
372 : public:
373 4085 : Path(const HeapReverser::Edge &edge, Path *next) : edge(edge), next(next) { }
374 :
375 : /*
376 : * Compute the full path represented by this Path. The result is
377 : * owned by the caller.
378 : */
379 : char *computeName(JSContext *cx);
380 :
381 : private:
382 : const HeapReverser::Edge &edge;
383 : Path *next;
384 : };
385 :
386 : struct AutoNodeMarker {
387 540 : AutoNodeMarker(HeapReverser::Node *node) : node(node) { node->marked = true; }
388 540 : ~AutoNodeMarker() { node->marked = false; }
389 : private:
390 : HeapReverser::Node *node;
391 : };
392 :
393 : /*
394 : * Given that we've reached |cell| via |path|, with all Nodes along that
395 : * path marked, add paths from all reportable objects reachable from cell
396 : * to |result|.
397 : */
398 : bool visit(void *cell, Path *path);
399 :
400 : /*
401 : * If |cell|, of |kind|, is representable as a JavaScript value, return that
402 : * value; otherwise, return JSVAL_VOID.
403 : */
404 1362 : jsval representable(void *cell, int kind) {
405 1362 : if (kind == JSTRACE_OBJECT) {
406 1362 : JSObject *object = static_cast<JSObject *>(cell);
407 :
408 : /* Certain classes of object are for internal use only. */
409 5448 : if (object->isBlock() ||
410 1362 : object->isCall() ||
411 1362 : object->isWith() ||
412 1362 : object->isDeclEnv()) {
413 0 : return JSVAL_VOID;
414 : }
415 :
416 : /* Internal function objects should also not be revealed. */
417 1362 : if (JS_ObjectIsFunction(context, object) && IsInternalFunctionObject(object))
418 0 : return JSVAL_VOID;
419 :
420 1362 : return OBJECT_TO_JSVAL(object);
421 : }
422 :
423 0 : return JSVAL_VOID;
424 : }
425 :
426 : /* Add |referrer| as something that refers to |target| via |path|. */
427 : bool addReferrer(jsval referrer, Path *path);
428 : };
429 :
430 : bool
431 4625 : ReferenceFinder::visit(void *cell, Path *path)
432 : {
433 : /* In ReferenceFinder, paths will almost certainly fit on the C++ stack. */
434 4625 : JS_CHECK_RECURSION(context, return false);
435 :
436 : /* Have we reached a root? Always report that. */
437 4625 : if (!cell)
438 2723 : return addReferrer(JSVAL_NULL, path);
439 :
440 1902 : HeapReverser::Map::Ptr p = reverser.map.lookup(cell);
441 1902 : JS_ASSERT(p);
442 1902 : HeapReverser::Node *node = &p->value;
443 :
444 : /* Is |cell| a representable cell, reached via a non-empty path? */
445 1902 : if (path != NULL) {
446 1362 : jsval representation = representable(cell, node->kind);
447 1362 : if (!JSVAL_IS_VOID(representation))
448 1362 : return addReferrer(representation, path);
449 : }
450 :
451 : /*
452 : * If we've made a cycle, don't traverse further. We *do* want to include
453 : * paths from the target to itself, so we don't want to do this check until
454 : * after we've possibly reported this cell as a referrer.
455 : */
456 540 : if (node->marked)
457 0 : return true;
458 1080 : AutoNodeMarker marker(node);
459 :
460 : /* Visit the origins of all |cell|'s incoming edges. */
461 4625 : for (size_t i = 0; i < node->incoming.length(); i++) {
462 4085 : const HeapReverser::Edge &edge = node->incoming[i];
463 4085 : Path extendedPath(edge, path);
464 4085 : if (!visit(edge.origin, &extendedPath))
465 0 : return false;
466 : }
467 :
468 540 : return true;
469 : }
470 :
471 : char *
472 4085 : ReferenceFinder::Path::computeName(JSContext *cx)
473 : {
474 : /* Walk the edge list and compute the total size of the path. */
475 4085 : size_t size = 6;
476 8170 : for (Path *l = this; l; l = l->next)
477 4085 : size += strlen(l->edge.name) + (l->next ? 2 : 0);
478 4085 : size += 1;
479 :
480 4085 : char *path = static_cast<char *>(cx->malloc_(size));
481 4085 : if (!path)
482 0 : return NULL;
483 :
484 : /*
485 : * Walk the edge list again, and copy the edge names into place, with
486 : * appropriate separators. Note that we constructed the edge list from
487 : * target to referrer, which means that the list links point *towards* the
488 : * target, so we can walk the list and build the path from left to right.
489 : */
490 4085 : strcpy(path, "edge: ");
491 4085 : char *next = path + 6;
492 8170 : for (Path *l = this; l; l = l->next) {
493 4085 : strcpy(next, l->edge.name);
494 4085 : next += strlen(next);
495 4085 : if (l->next) {
496 0 : strcpy(next, "; ");
497 0 : next += 2;
498 : }
499 : }
500 4085 : JS_ASSERT(next + 1 == path + size);
501 :
502 4085 : return path;
503 : }
504 :
505 : bool
506 4085 : ReferenceFinder::addReferrer(jsval referrer, Path *path)
507 : {
508 4085 : if (!context->compartment->wrap(context, &referrer))
509 0 : return NULL;
510 :
511 4085 : char *pathName = path->computeName(context);
512 4085 : if (!pathName)
513 0 : return false;
514 8170 : AutoReleasePtr releasePathName(context, pathName);
515 :
516 : /* Find the property of the results object named |pathName|. */
517 : jsval v;
518 4085 : if (!JS_GetProperty(context, result, pathName, &v))
519 0 : return false;
520 4085 : if (JSVAL_IS_VOID(v)) {
521 : /* Create an array to accumulate referents under this path. */
522 2982 : JSObject *array = JS_NewArrayObject(context, 1, &referrer);
523 2982 : if (!array)
524 0 : return false;
525 2982 : v = OBJECT_TO_JSVAL(array);
526 2982 : return !!JS_SetProperty(context, result, pathName, &v);
527 : }
528 :
529 : /* The property's value had better be an array. */
530 1103 : JS_ASSERT(JSVAL_IS_OBJECT(v) && !JSVAL_IS_NULL(v));
531 1103 : JSObject *array = JSVAL_TO_OBJECT(v);
532 1103 : JS_ASSERT(JS_IsArrayObject(context, array));
533 :
534 : /* Append our referrer to this array. */
535 : uint32_t length;
536 1103 : return JS_GetArrayLength(context, array, &length) &&
537 1103 : JS_SetElement(context, array, length, &referrer);
538 : }
539 :
540 : JSObject *
541 540 : ReferenceFinder::findReferences(JSObject *target)
542 : {
543 540 : result = JS_NewObject(context, NULL, NULL, NULL);
544 540 : if (!result)
545 0 : return NULL;
546 540 : if (!visit(target, NULL))
547 0 : return NULL;
548 :
549 540 : return result;
550 : }
551 :
552 : /* See help(findReferences). */
553 : JSBool
554 540 : FindReferences(JSContext *cx, unsigned argc, jsval *vp)
555 : {
556 540 : if (argc < 1) {
557 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
558 0 : "findReferences", "0", "s");
559 0 : return false;
560 : }
561 :
562 540 : jsval target = JS_ARGV(cx, vp)[0];
563 540 : if (!JSVAL_IS_OBJECT(target) || JSVAL_IS_NULL(target)) {
564 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
565 0 : "argument", "not an object");
566 0 : return false;
567 : }
568 :
569 : /* Walk the JSRuntime, producing a reversed map of the heap. */
570 1080 : HeapReverser reverser(cx);
571 540 : if (!reverser.init() || !reverser.reverseHeap())
572 0 : return false;
573 :
574 : /* Given the reversed map, find the referents of target. */
575 540 : ReferenceFinder finder(cx, reverser);
576 540 : JSObject *references = finder.findReferences(JSVAL_TO_OBJECT(target));
577 540 : if (!references)
578 0 : return false;
579 :
580 540 : JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(references));
581 540 : return true;
582 : }
583 :
584 : #endif /* DEBUG */
|