diff --git a/js/src/jsapi.h b/js/src/jsapi.h index bcaf5dca9a58..66adb3b1dfde 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1671,7 +1671,7 @@ JS_CallTracer(JSTracer *trc, void *thing, uint32 kind); * that stores the reference. * * When printer callback is not null, the arg and index arguments are - * available to the callback as debugPrinterArg and debugPrintIndex fields + * available to the callback as debugPrintArg and debugPrintIndex fields * of JSTracer. * * The storage for name or callback's arguments needs to live only until diff --git a/js/src/shell/Makefile.in b/js/src/shell/Makefile.in index 8d0166e13d59..4df10ed2ff91 100644 --- a/js/src/shell/Makefile.in +++ b/js/src/shell/Makefile.in @@ -49,6 +49,7 @@ CPPSRCS = \ js.cpp \ jsworkers.cpp \ jsoptparse.cpp \ + jsheaptools.cpp \ $(NULL) DEFINES += -DEXPORT_JS_API diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 4b79c0298e35..012e9489f92c 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -76,6 +76,7 @@ #include "jstypedarray.h" #include "jsxml.h" #include "jsperf.h" +#include "jshashtable.h" #include "prmjtime.h" @@ -91,6 +92,7 @@ #include "jsoptparse.h" #include "jsworkers.h" +#include "jsheaptools.h" #include "jsinterpinlines.h" #include "jsobjinlines.h" @@ -4505,7 +4507,7 @@ static JSFunctionSpec shell_functions[] = { JS_FN("gcparam", GCParameter, 2,0), JS_FN("countHeap", CountHeap, 0,0), JS_FN("makeFinalizeObserver", MakeFinalizeObserver, 0,0), - JS_FN("finalizeCount", FinalizeCount, 0,0), + JS_FN("finalizeCount", FinalizeCount, 0,0), #ifdef JS_GC_ZEAL JS_FN("gczeal", GCZeal, 2,0), JS_FN("schedulegc", ScheduleGC, 1,0), @@ -4530,6 +4532,7 @@ static JSFunctionSpec shell_functions[] = { JS_FN("dumpObject", DumpObject, 1,0), JS_FN("notes", Notes, 1,0), JS_FN("stats", DumpStats, 1,0), + JS_FN("findReferences", FindReferences, 1,0), #endif JS_FN("dumpStack", DumpStack, 1,0), #ifdef TEST_CVTARGS @@ -4663,6 +4666,8 @@ static const char *const shell_help_messages[] = { "dumpObject() Dump an internal representation of an object", "notes([fun]) Show source notes for functions", "stats([string ...]) Dump 'arena', 'atom', 'global' stats", +"findReferences(target)\n" +" Walk the heap and return an object describing all references to target", #endif "dumpStack() Dump the stack as an array of callees (youngest first)", #ifdef TEST_CVTARGS diff --git a/js/src/shell/jsheaptools.cpp b/js/src/shell/jsheaptools.cpp new file mode 100644 index 000000000000..46867f7178e2 --- /dev/null +++ b/js/src/shell/jsheaptools.cpp @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is JavaScript shell workers. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jason Orendorff + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include + +#include "jsapi.h" + +#include "jsalloc.h" +#include "jscntxt.h" +#include "jscompartment.h" +#include "jsfun.h" +#include "jshashtable.h" +#include "jsobj.h" +#include "jsprf.h" +#include "jsutil.h" +#include "jsvalue.h" +#include "jsvector.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. */ +class HeapReverser : public JSTracer { + public: + struct Edge; + + /* Metadata for a given Cell we have visited. */ + class Node { + public: + Node() { } + Node(uint32 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 rhs) + : kind(rhs->kind), incoming(Move(rhs->incoming)), marked(rhs->marked) { } + Node &operator=(MoveRef rhs) { + this->~Node(); + new(this) Node(rhs); + return *this; + } + + /* What kind of Cell this is. */ + uint32 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 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() { 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 rhs) : name(rhs->name), origin(rhs->origin) { + rhs->name = NULL; + } + Edge &operator=(MoveRef 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 Map; + Map map; + + /* Construct a HeapReverser for |context|'s heap. */ + HeapReverser(JSContext *cx) : map(cx), work(cx), parent(NULL) { + context = cx; + callback = traverseEdgeWithThis; + } + + bool init() { return map.init(); } + + /* Build a reversed map of the heap in |map|. */ + bool reverseHeap(); + + private: + /* + * 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, uint32 kind) : cell(cell), kind(kind) { } + void *cell; + uint32 kind; + }; + + /* + * A stack of work items. We represent the stack explicitly to avoid + * overflowing the C++ stack when traversing long chains of objects. + */ + Vector work; + + /* When traverseEdge is called, the Cell and kind at which the edge originated. */ + void *parent; + + /* Traverse an edge. */ + bool traverseEdge(void *cell, uint32 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 *cell, uint32 kind) { + HeapReverser *reverser = static_cast(tracer); + reverser->traversalStatus = reverser->traverseEdge(cell, kind); + } +}; + +bool +HeapReverser::traverseEdge(void *cell, uint32 kind) { + /* 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 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() { + /* 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(debugPrintArg); + char *name = static_cast(context->malloc_(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 = static_cast(context->malloc_(nameSize)); + if (!name) + return NULL; + if (debugPrinter) + debugPrinter(this, name, nameSize); + else + JS_snprintf(name, nameSize, "%s[%lu]", + static_cast(debugPrintArg), debugPrintIndex); + + /* Shrink storage to fit. */ + return static_cast(context->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) { } + + /* Produce an object describing all references to |target|. */ + JSObject *findReferences(JSObject *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. */ + JSObject *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(cell); + + /* Certain classes of object are for internal use only. */ + JSClass *clasp = JS_GET_CLASS(context, object); + if (clasp == Jsvalify(&js_BlockClass) || + clasp == Jsvalify(&js_CallClass) || + clasp == Jsvalify(&js_WithClass) || + clasp == Jsvalify(&js_DeclEnvClass)) + 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 = static_cast(cx->malloc_(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 referrer, Path *path) +{ + if (!context->compartment->wrap(context, Valueify(&referrer))) + return NULL; + + char *pathName = path->computeName(context); + if (!pathName) + return false; + AutoReleasePtr releasePathName(context, pathName); + + /* Find the property of the results object named |pathName|. */ + jsval v; + if (!JS_GetProperty(context, result, pathName, &v)) + return false; + if (JSVAL_IS_VOID(v)) { + /* Create an array to accumulate referents under this path. */ + JSObject *array = JS_NewArrayObject(context, 1, &referrer); + if (!array) + return false; + v = OBJECT_TO_JSVAL(array); + return !!JS_SetProperty(context, result, pathName, &v); + } + + /* The property's value had better be an array. */ + JS_ASSERT(JSVAL_IS_OBJECT(v) && !JSVAL_IS_NULL(v)); + JSObject *array = JSVAL_TO_OBJECT(v); + JS_ASSERT(JS_IsArrayObject(context, array)); + + /* Append our referrer to this array. */ + jsuint length; + return JS_GetArrayLength(context, array, &length) && + JS_SetElement(context, array, length, &referrer); +} + +JSObject * +ReferenceFinder::findReferences(JSObject *target) +{ + result = JS_NewObject(context, NULL, NULL, NULL); + if (!result) + return NULL; + if (!visit(target, NULL)) + return NULL; + + return result; +} + +/* + * findReferences(thing) + * + * Walk the entire heap, looking for references to |thing|, and return a + * "references object" describing what we found. + * + * Each property of the references object describes one kind of reference. The + * property's name is the label supplied to MarkObject, JS_CALL_TRACER, or what + * have you, prefixed with "edge: " to avoid collisions with system properties + * (like "toString" and "__proto__"). The property's value is an array of things + * that refer to |thing| via that kind of reference. Ordinary references from + * one object to another are named after the property name (with the "edge: " + * prefix). + * + * Garbage collection roots appear as references from 'null'. We use the name + * given to the root (with the "edge: " prefix) as the name of the reference. + * + * Note that the references object does record references from objects that are + * only reachable via |thing| itself, not just the references reachable + * themselves from roots that keep |thing| from being collected. (We could make + * this distinction if it is useful.) + * + * If any references are found by the conservative scanner, the references + * object will have a property named "edge: machine stack"; the referrers will + * be 'null', because they are roots. + */ +JSBool +FindReferences(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "findReferences", 1, ""); + return false; + } + + jsval target = JS_ARGV(cx, vp)[0]; + if (!JSVAL_IS_OBJECT(target) || JSVAL_IS_NULL(target)) { + 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); + JSObject *references = finder.findReferences(JSVAL_TO_OBJECT(target)); + if (!references) + return false; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(references)); + return true; +} + +#endif /* DEBUG */ diff --git a/js/src/shell/jsheaptools.h b/js/src/shell/jsheaptools.h new file mode 100644 index 000000000000..b2954cca5061 --- /dev/null +++ b/js/src/shell/jsheaptools.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is JavaScript shell workers. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jim Blandy + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsheaptools_h___ +#define jsheaptools_h___ + +#include "jsapi.h" + +#ifdef DEBUG +JSBool FindReferences(JSContext *cx, uintN argc, jsval *vp); +#endif /* DEBUG */ + +#endif /* jsheaptools_h___ */ diff --git a/js/src/tests/js1_8_5/extensions/findReferences-01.js b/js/src/tests/js1_8_5/extensions/findReferences-01.js new file mode 100644 index 000000000000..9c636ba4531f --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/findReferences-01.js @@ -0,0 +1,52 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ +// Contributor: Jim Blandy + +if (typeof findReferences == "function") { + function C() {} + var o = new C; + o.x = {}; // via ordinary property + o[42] = {}; // via numeric property + o.myself = o; // self-references should be reported + o.alsoMyself = o; // multiple self-references should all be reported + + assertEq(referencesVia(o, 'proto', C.prototype), true); + assertEq(referencesVia(o, 'parent', this), true); + assertEq(referencesVia(o, 'x', o.x), true); + assertEq(referencesVia(o, '42', o[42]), true); + assertEq(referencesVia(o, 'myself', o), true); + assertEq(referencesVia(o, 'alsoMyself', o), true); + + function g() { return 42; } + function s(v) { } + var p = Object.defineProperty({}, 'a', { get:g, set:s }); + assertEq(referencesVia(p, 'shape; a getter', g), true); + assertEq(referencesVia(p, 'shape; a setter', s), true); + + // If there are multiple objects with the same shape referring to a getter + // or setter, findReferences should get all of them, even though the shape + // gets 'marked' the first time we visit it. + var q = Object.defineProperty({}, 'a', { get:g, set:s }); + assertEq(referencesVia(p, 'shape; a getter', g), true); + assertEq(referencesVia(q, 'shape; a getter', g), true); + + // If we extend each object's shape chain, both should still be able to + // reach the getter, even though the two shapes are each traversed twice. + p.b = 9; + q.b = 9; + assertEq(referencesVia(p, 'shape; a getter', g), true); + assertEq(referencesVia(q, 'shape; a getter', g), true); + + // These are really just ordinary own property references. + assertEq(referencesVia(C, 'prototype', Object.getPrototypeOf(o)), true); + assertEq(referencesVia(Object.getPrototypeOf(o), 'constructor', C), true); + + // Dense arrays should work, too. + a = []; + a[1] = o; + assertEq(referencesVia(a, 'element[1]', o), true); + + reportCompare(true, true); +} else { + reportCompare(true, true, "test skipped: findReferences is not a function"); +} \ No newline at end of file diff --git a/js/src/tests/js1_8_5/extensions/findReferences-02.js b/js/src/tests/js1_8_5/extensions/findReferences-02.js new file mode 100644 index 000000000000..8cd702a9b762 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/findReferences-02.js @@ -0,0 +1,28 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ +// Contributor: Jim Blandy + +if (typeof findReferences == "function") { + (function f() { + assertEq(referencesVia(arguments, 'callee', f), true); + })(); + + var o = ({}); + + function returnFlat(x) { return function flat() { return x; }; } + assertEq(referencesVia(returnFlat(o), 'upvars[0]', o), true); + + function returnHeavy(y) { eval(''); return function heavy() { return y; }; } + assertEq(referencesVia(returnHeavy(o), 'parent; y', o), true); + assertEq(referencesVia(returnHeavy(o), 'parent; parent', this), true); + + function returnBlock(z) { eval(''); let(w = z) { return function block() { return w; }; }; } + assertEq(referencesVia(returnBlock(o), 'parent; w', o), true); + + function returnWithObj(v) { with(v) return function withObj() { return u; }; } + assertEq(referencesVia(returnWithObj(o), 'parent; proto', o), true); + + reportCompare(true, true); +} else { + reportCompare(true, true, "test skipped: findReferences is not a function"); +} diff --git a/js/src/tests/js1_8_5/extensions/findReferences-03.js b/js/src/tests/js1_8_5/extensions/findReferences-03.js new file mode 100644 index 000000000000..a7f1679ea27e --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/findReferences-03.js @@ -0,0 +1,41 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ +// Contributor: Jim Blandy + +if (typeof findReferences == "function") { + + function makeGenerator(c) { eval(c); yield function generatorClosure() { return x; }; } + var generator = makeGenerator('var x = 42'); + var closure = generator.next(); + referencesVia(closure, 'parent; generator object', generator); + + var o = {}; + + assertEq(function f() { return referencesVia(null, 'arguments', arguments); } (), true); + + var rvalueCorrect; + + function finallyHoldsRval() { + try { + return o; + } finally { + rvalueCorrect = referencesVia(null, 'rval', o); + } + } + rvalueCorrect = false; + finallyHoldsRval(); + assertEq(rvalueCorrect, true); + + // Because we don't distinguish between JavaScript stack marking and C++ + // stack marking (both use the conservative scanner), we can't really write + // the following tests meaningfully: + // generator frame -> generator object + // stack frame -> local variables + // stack frame -> this + // stack frame -> callee + // for(... in x) loop's reference to x + + reportCompare(true, true); +} else { + reportCompare(true, true, "test skipped: findReferences is not a function"); +} \ No newline at end of file diff --git a/js/src/tests/js1_8_5/extensions/findReferences-04.js b/js/src/tests/js1_8_5/extensions/findReferences-04.js new file mode 100644 index 000000000000..6fa400128bea --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/findReferences-04.js @@ -0,0 +1,18 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ +// Contributor: Jim Blandy + +if (typeof findReferences == "function") { + + var global = newGlobal('new-compartment'); + var o = ({}); + global.o = o; + + // Don't trip a cross-compartment reference assertion. + findReferences(o); + + reportCompare(true, true); + +} else { + reportCompare(true, true, "test skipped: findReferences is not a function"); +} diff --git a/js/src/tests/js1_8_5/extensions/jstests.list b/js/src/tests/js1_8_5/extensions/jstests.list index 54742924ac2c..c6f873820e9a 100644 --- a/js/src/tests/js1_8_5/extensions/jstests.list +++ b/js/src/tests/js1_8_5/extensions/jstests.list @@ -30,6 +30,10 @@ skip-if(!xulRuntime.shell) script clone-forge.js skip-if(!xulRuntime.shell) script clone-complex-object.js script set-property-non-extensible.js script recursion.js +script findReferences-01.js +script findReferences-02.js +script findReferences-03.js +script findReferences-04.js script regress-627859.js script regress-627984-1.js script regress-627984-2.js diff --git a/js/src/tests/js1_8_5/extensions/shell.js b/js/src/tests/js1_8_5/extensions/shell.js index 9cf458ee20b0..dce4b7cba7f1 100644 --- a/js/src/tests/js1_8_5/extensions/shell.js +++ b/js/src/tests/js1_8_5/extensions/shell.js @@ -170,3 +170,27 @@ var Match = MatchError: MatchError }; })(); + +function referencesVia(from, edge, to) { + edge = "edge: " + edge; + var edges = findReferences(to); + if (edge in edges && edges[edge].indexOf(from) != -1) + return true; + + // Be nice: make it easy to fix if the edge name has just changed. + var alternatives = []; + for (var e in edges) { + if (edges[e].indexOf(from) != -1) + alternatives.push(e); + } + if (alternatives.length == 0) { + print("referent not referred to by referrer after all"); + } else { + print("referent is not referenced via: " + uneval(edge)); + print("but it is referenced via: " + uneval(alternatives)); + } + print("all incoming edges, from any object:"); + for (var e in edges) + print(e); + return false; +}