Bug 394853: countHeap function for js shell and help() cleanup. r=brendan

This commit is contained in:
igor@mir2.org 2007-09-07 11:25:19 -07:00
parent a79f1f491e
commit 88c81d41b4

View File

@ -780,6 +780,163 @@ GCZeal(JSContext *cx, uintN argc, jsval *vp)
} }
#endif /* JS_GC_ZEAL */ #endif /* JS_GC_ZEAL */
typedef struct JSCountHeapNode JSCountHeapNode;
struct JSCountHeapNode {
void *thing;
uint32 kind;
JSCountHeapNode *next;
};
typedef struct JSCountHeapTracer {
JSTracer base;
JSDHashTable visited;
JSBool ok;
JSCountHeapNode *traceList;
JSCountHeapNode *recycleList;
} JSCountHeapTracer;
static void
CountHeapNotify(JSTracer *trc, void *thing, uint32 kind)
{
JSCountHeapTracer *countTracer;
JSDHashEntryStub *entry;
JSCountHeapNode *node;
JS_ASSERT(trc->callback == CountHeapNotify);
countTracer = (JSCountHeapTracer *)trc;
if (!countTracer->ok)
return;
entry = (JSDHashEntryStub *)
JS_DHashTableOperate(&countTracer->visited, thing, JS_DHASH_ADD);
if (!entry) {
JS_ReportOutOfMemory(trc->context);
countTracer->ok = JS_FALSE;
return;
}
if (entry->key)
return;
entry->key = thing;
node = countTracer->recycleList;
if (node) {
countTracer->recycleList = node->next;
} else {
node = (JSCountHeapNode *) JS_malloc(trc->context, sizeof *node);
if (!node) {
countTracer->ok = JS_FALSE;
return;
}
}
node->thing = thing;
node->kind = kind;
node->next = countTracer->traceList;
countTracer->traceList = node;
}
static JSBool
CountHeap(JSContext *cx, uintN argc, jsval *vp)
{
void* startThing;
int32 startTraceKind;
jsval v;
int32 traceKind, i;
JSString *str;
char *bytes;
JSCountHeapTracer countTracer;
JSCountHeapNode *node;
size_t counter;
static const struct {
const char *name;
int32 kind;
} traceKindNames[] = {
{ "all", -1 },
{ "object", JSTRACE_OBJECT },
{ "double", JSTRACE_DOUBLE },
{ "string", JSTRACE_STRING },
{ "function", JSTRACE_FUNCTION },
#if JS_HAS_XML_SUPPORT
{ "namespace", JSTRACE_NAMESPACE },
{ "qname", JSTRACE_QNAME },
{ "xml", JSTRACE_XML },
#endif
};
startThing = NULL;
startTraceKind = 0;
if (argc > 0) {
v = JS_ARGV(cx, vp)[0];
if (JSVAL_IS_TRACEABLE(v)) {
startThing = JSVAL_TO_TRACEABLE(v);
startTraceKind = JSVAL_TRACE_KIND(v);
} else if (v != JSVAL_NULL) {
fprintf(gErrFile,
"countHeap: argument 1 is not null or a heap-allocated "
"thing\n");
return JS_FALSE;
}
}
traceKind = -1;
if (argc > 1) {
str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]);
if (!str)
return JS_FALSE;
bytes = JS_GetStringBytes(str);
if (!bytes)
return JS_FALSE;
for (i = 0; ;) {
if (strcmp(bytes, traceKindNames[i].name) == 0) {
traceKind = traceKindNames[i].kind;
break;
}
if (++i == JS_ARRAY_LENGTH(traceKindNames)) {
fprintf(gErrFile,
"countHeap: trace kind name '%s' is unknown\n",
bytes);
return JS_FALSE;
}
}
}
JS_TRACER_INIT(&countTracer.base, cx, CountHeapNotify);
if (!JS_DHashTableInit(&countTracer.visited, JS_DHashGetStubOps(),
NULL, sizeof(JSDHashEntryStub),
JS_DHASH_DEFAULT_CAPACITY(100))) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
countTracer.ok = JS_TRUE;
countTracer.traceList = NULL;
countTracer.recycleList = NULL;
if (!startThing) {
JS_TraceRuntime(&countTracer.base);
} else {
JS_SET_TRACING_NAME(&countTracer.base, "root");
JS_CallTracer(&countTracer.base, startThing, startTraceKind);
}
counter = 0;
while ((node = countTracer.traceList) != NULL) {
if (traceKind == -1 || node->kind == traceKind)
counter++;
countTracer.traceList = node->next;
node->next = countTracer.recycleList;
countTracer.recycleList = node;
JS_TraceChildren(&countTracer.base, node->thing, node->kind);
}
while ((node = countTracer.recycleList) != NULL) {
countTracer.recycleList = node->next;
JS_free(cx, node);
}
JS_DHashTableFinish(&countTracer.visited);
return countTracer.ok && JS_NewNumberValue(cx, (jsdouble) counter, vp);
}
static JSScript * static JSScript *
ValueToScript(JSContext *cx, jsval v) ValueToScript(JSContext *cx, jsval v)
{ {
@ -1376,58 +1533,78 @@ DumpStats(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
} }
static JSBool static JSBool
DumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) DumpHeap(JSContext *cx, uintN argc, jsval *vp)
{ {
char *fileName = NULL; char *fileName;
void* startThing = NULL; jsval v;
uint32 startTraceKind = 0; void* startThing;
void *thingToFind = NULL; uint32 startTraceKind;
size_t maxDepth = (size_t)-1; const char *badTraceArg;
void *thingToIgnore = NULL; void *thingToFind;
jsval *vp; size_t maxDepth;
void *thingToIgnore;
FILE *dumpFile; FILE *dumpFile;
JSBool ok; JSBool ok;
vp = &argv[0]; fileName = NULL;
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (argc > 0) {
JSString *str; v = JS_ARGV(cx, vp)[0];
if (v != JSVAL_NULL) {
JSString *str;
str = JS_ValueToString(cx, *vp); str = JS_ValueToString(cx, v);
if (!str) if (!str)
return JS_FALSE; return JS_FALSE;
*vp = STRING_TO_JSVAL(str); JS_ARGV(cx, vp)[0] = STRING_TO_JSVAL(str);
fileName = JS_GetStringBytes(str); fileName = JS_GetStringBytes(str);
}
} }
vp = &argv[1]; startThing = NULL;
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) { startTraceKind = 0;
if (!JSVAL_IS_TRACEABLE(*vp)) if (argc > 1) {
v = JS_ARGV(cx, vp)[1];
if (JSVAL_IS_TRACEABLE(v)) {
startThing = JSVAL_TO_TRACEABLE(v);
startTraceKind = JSVAL_TRACE_KIND(v);
} else if (v != JSVAL_NULL) {
badTraceArg = "start";
goto not_traceable_arg; goto not_traceable_arg;
startThing = JSVAL_TO_TRACEABLE(*vp); }
startTraceKind = JSVAL_TRACE_KIND(*vp);
} }
vp = &argv[2]; thingToFind = NULL;
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (argc > 2) {
if (!JSVAL_IS_TRACEABLE(*vp)) v = JS_ARGV(cx, vp)[2];
if (JSVAL_IS_TRACEABLE(v)) {
thingToFind = JSVAL_TO_TRACEABLE(v);
} else if (v != JSVAL_NULL) {
badTraceArg = "toFind";
goto not_traceable_arg; goto not_traceable_arg;
thingToFind = JSVAL_TO_TRACEABLE(*vp); }
} }
vp = &argv[3]; maxDepth = (size_t)-1;
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (argc > 3) {
uint32 depth; v = JS_ARGV(cx, vp)[3];
if (v != JSVAL_NULL) {
uint32 depth;
if (!JS_ValueToECMAUint32(cx, *vp, &depth)) if (!JS_ValueToECMAUint32(cx, v, &depth))
return JS_FALSE; return JS_FALSE;
maxDepth = depth; maxDepth = depth;
}
} }
vp = &argv[4]; thingToIgnore = NULL;
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (argc > 4) {
if (!JSVAL_IS_TRACEABLE(*vp)) v = JS_ARGV(cx, vp)[4];
if (JSVAL_IS_TRACEABLE(v)) {
thingToIgnore = JSVAL_TO_TRACEABLE(v);
} else if (v != JSVAL_NULL) {
badTraceArg = "toIgnore";
goto not_traceable_arg; goto not_traceable_arg;
thingToIgnore = JSVAL_TO_TRACEABLE(*vp); }
} }
if (!fileName) { if (!fileName) {
@ -1449,8 +1626,8 @@ DumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
not_traceable_arg: not_traceable_arg:
fprintf(gErrFile, fprintf(gErrFile,
"dumpHeap: argument %u is not null or a heap-allocated thing\n", "dumpHeap: argument '%s' is not null or a heap-allocated thing\n",
(unsigned)(vp - argv)); badTraceArg);
return JS_FALSE; return JS_FALSE;
} }
@ -2242,6 +2419,7 @@ static JSFunctionSpec shell_functions[] = {
JS_FS("help", Help, 0,0,0), JS_FS("help", Help, 0,0,0),
JS_FS("quit", Quit, 0,0,0), JS_FS("quit", Quit, 0,0,0),
JS_FN("gc", GC, 0,0,0,0), JS_FN("gc", GC, 0,0,0,0),
JS_FN("countHeap", CountHeap, 0,0,0,0),
#ifdef JS_GC_ZEAL #ifdef JS_GC_ZEAL
JS_FN("gczeal", GCZeal, 1,1,0,0), JS_FN("gczeal", GCZeal, 1,1,0,0),
#endif #endif
@ -2256,7 +2434,7 @@ static JSFunctionSpec shell_functions[] = {
#ifdef DEBUG #ifdef DEBUG
JS_FS("dis", Disassemble, 1,0,0), JS_FS("dis", Disassemble, 1,0,0),
JS_FS("dissrc", DisassWithSrc, 1,0,0), JS_FS("dissrc", DisassWithSrc, 1,0,0),
JS_FS("dumpHeap", DumpHeap, 5,0,0), JS_FN("dumpHeap", DumpHeap, 0,0,0,0),
JS_FS("notes", Notes, 1,0,0), JS_FS("notes", Notes, 1,0,0),
JS_FS("tracing", Tracing, 0,0,0), JS_FS("tracing", Tracing, 0,0,0),
JS_FS("stats", DumpStats, 1,0,0), JS_FS("stats", DumpStats, 1,0,0),
@ -2279,68 +2457,139 @@ static JSFunctionSpec shell_functions[] = {
JS_FS_END JS_FS_END
}; };
/* NOTE: These must be kept in sync with the above. */ static const char shell_help_header[] =
"Command Description\n"
"======= ===========\n";
static char *shell_help_messages[] = { static const char *const shell_help_messages[] = {
"version([number]) Get or set JavaScript version number", "version([number]) Get or set JavaScript version number",
"options([option ...]) Get or toggle JavaScript options", "options([option ...]) Get or toggle JavaScript options",
"load(['foo.js' ...]) Load files named by string arguments", "load(['foo.js' ...]) Load files named by string arguments",
"readline() Read a single line from stdin", "readline() Read a single line from stdin",
"print([exp ...]) Evaluate and print expressions", "print([exp ...]) Evaluate and print expressions",
"help([name ...]) Display usage and help messages", "help([name ...]) Display usage and help messages",
"quit() Quit the shell", "quit() Quit the shell",
"gc() Run the garbage collector", "gc() Run the garbage collector",
"countHeap([start[, kind]])\n"
" Count the number of live GC things in the heap or things reachable from\n"
" start when it is given and is not null. kind is either 'all' (default) to\n"
" count all things or one of 'object', 'double', 'string', 'function',\n"
" 'qname', 'namespace', 'xml' to count only things of that kind",
#ifdef JS_GC_ZEAL #ifdef JS_GC_ZEAL
"gczeal(level) How zealous the garbage collector should be", "gczeal(level) How zealous the garbage collector should be",
#endif #endif
"trap([fun, [pc,]] exp) Trap bytecode execution", "trap([fun, [pc,]] exp) Trap bytecode execution",
"untrap(fun[, pc]) Remove a trap", "untrap(fun[, pc]) Remove a trap",
"line2pc([fun,] line) Map line number to PC", "line2pc([fun,] line) Map line number to PC",
"pc2line(fun[, pc]) Map PC to line number", "pc2line(fun[, pc]) Map PC to line number",
"stackQuota([number]) Query/set script stack quota", "stackQuota([number]) Query/set script stack quota",
"stringsAreUTF8() Check if strings are UTF-8 encoded", "stringsAreUTF8() Check if strings are UTF-8 encoded",
"testUTF8(mode) Perform UTF-8 tests (modes are 1 to 4)", "testUTF8(mode) Perform UTF-8 tests (modes are 1 to 4)",
"throwError() Throw an error from JS_ReportError", "throwError() Throw an error from JS_ReportError",
#ifdef DEBUG #ifdef DEBUG
"dis([fun]) Disassemble functions into bytecodes", "dis([fun]) Disassemble functions into bytecodes",
"dissrc([fun]) Disassemble functions with source lines", "dissrc([fun]) Disassemble functions with source lines",
"dumpHeap([fileName], [start], [toFind], [maxDepth], [toIgnore])\n" "dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n"
" Interface to JS_DumpHeap with output sent to file", " Interface to JS_DumpHeap with output sent to file",
"notes([fun]) Show source notes for functions", "notes([fun]) Show source notes for functions",
"tracing([toggle]) Turn tracing on or off", "tracing([toggle]) Turn tracing on or off",
"stats([string ...]) Dump 'arena', 'atom', 'global' stats", "stats([string ...]) Dump 'arena', 'atom', 'global' stats",
#endif #endif
#ifdef TEST_EXPORT #ifdef TEST_EXPORT
"xport(obj, id) Export identified property from object", "xport(obj, property) Export the given property of obj",
#endif #endif
#ifdef TEST_CVTARGS #ifdef TEST_CVTARGS
"cvtargs(b, c, ...) Test JS_ConvertArguments", "cvtargs(arg1..., arg12) Test argument formater",
#endif #endif
"build() Show build date and time", "build() Show build date and time",
"clear([obj]) Clear properties of object", "clear([obj]) Clear properties of object",
"intern(str) Internalize str in the atom table", "intern(str) Internalize str in the atom table",
"clone(fun[, scope]) Clone function object", "clone(fun[, scope]) Clone function object",
"seal(obj[, deep]) Seal object, or object graph if deep", "seal(obj[, deep]) Seal object, or object graph if deep",
"getpda(obj) Get the property descriptors for obj", "getpda(obj) Get the property descriptors for obj",
"getslx(obj) Get script line extent", "getslx(obj) Get script line extent",
"toint32(n) Testing hook for JS_ValueToInt32", "toint32(n) Testing hook for JS_ValueToInt32",
"evalcx(s[, o]) Evaluate s in optional sandbox object o\n" "evalcx(s[, o])\n"
" if (s == '' && !o) return new o with eager standard classes\n" " Evaluate s in optional sandbox object o\n"
" if (s == 'lazy' && !o) return new o with lazy standard classes", " if (s == '' && !o) return new o with eager standard classes\n"
0 " if (s == 'lazy' && !o) return new o with lazy standard classes",
}; };
static void /* Help messages must match shell functions. */
ShowHelpHeader(void) JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) + 1 ==
{ JS_ARRAY_LENGTH(shell_functions));
fprintf(gOutFile, "%-14s %-22s %s\n", "Command", "Usage", "Description");
fprintf(gOutFile, "%-14s %-22s %s\n", "=======", "=====", "===========");
}
#ifdef DEBUG
static void static void
ShowHelpForCommand(uintN n) CheckHelpMessages()
{ {
fprintf(gOutFile, "%-14.14s %s\n", shell_functions[n].name, shell_help_messages[n]); const char *const *m;
const char *lp;
/* Each message must begin with "function_name(" prefix. */
for (m = shell_help_messages; m != JS_ARRAY_END(shell_help_messages); ++m) {
lp = strchr(*m, '(');
JS_ASSERT(lp);
JS_ASSERT(memcmp(shell_functions[m - shell_help_messages].name,
*m, lp - *m) == 0);
}
}
#else
# define CheckHelpMessages() ((void) 0)
#endif
static JSBool
Help(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
uintN i, j;
int did_header, did_something;
JSType type;
JSFunction *fun;
JSString *str;
const char *bytes;
fprintf(gOutFile, "%s\n", JS_GetImplementationVersion());
if (argc == 0) {
fputs(shell_help_header, gOutFile);
for (i = 0; shell_functions[i].name; i++)
fprintf(gOutFile, "%s\n", shell_help_messages[i]);
} else {
did_header = 0;
for (i = 0; i < argc; i++) {
did_something = 0;
type = JS_TypeOfValue(cx, argv[i]);
if (type == JSTYPE_FUNCTION) {
fun = JS_ValueToFunction(cx, argv[i]);
str = fun->atom ? ATOM_TO_STRING(fun->atom) : NULL;
} else if (type == JSTYPE_STRING) {
str = JSVAL_TO_STRING(argv[i]);
} else {
str = NULL;
}
if (str) {
bytes = JS_GetStringBytes(str);
for (j = 0; shell_functions[j].name; j++) {
if (!strcmp(bytes, shell_functions[j].name)) {
if (!did_header) {
did_header = 1;
fputs(shell_help_header, gOutFile);
}
did_something = 1;
fprintf(gOutFile, "%s\n", shell_help_messages[j]);
break;
}
}
}
if (!did_something) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
fprintf(gErrFile, "Sorry, no help for %s\n",
JS_GetStringBytes(str));
}
}
}
return JS_TRUE;
} }
static JSObject * static JSObject *
@ -2377,60 +2626,6 @@ split_setup(JSContext *cx)
return inner; return inner;
} }
static JSBool
Help(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
uintN i, j;
int did_header, did_something;
JSType type;
JSFunction *fun;
JSString *str;
const char *bytes;
fprintf(gOutFile, "%s\n", JS_GetImplementationVersion());
if (argc == 0) {
ShowHelpHeader();
for (i = 0; shell_functions[i].name; i++)
ShowHelpForCommand(i);
} else {
did_header = 0;
for (i = 0; i < argc; i++) {
did_something = 0;
type = JS_TypeOfValue(cx, argv[i]);
if (type == JSTYPE_FUNCTION) {
fun = JS_ValueToFunction(cx, argv[i]);
str = fun->atom ? ATOM_TO_STRING(fun->atom) : NULL;
} else if (type == JSTYPE_STRING) {
str = JSVAL_TO_STRING(argv[i]);
} else {
str = NULL;
}
if (str) {
bytes = JS_GetStringBytes(str);
for (j = 0; shell_functions[j].name; j++) {
if (!strcmp(bytes, shell_functions[j].name)) {
if (!did_header) {
did_header = 1;
ShowHelpHeader();
}
did_something = 1;
ShowHelpForCommand(j);
break;
}
}
}
if (!did_something) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
fprintf(gErrFile, "Sorry, no help for %s\n",
JS_GetStringBytes(str));
}
}
}
return JS_TRUE;
}
/* /*
* Define a JS object called "it". Give it class operations that printf why * Define a JS object called "it". Give it class operations that printf why
* they're being called for tutorial purposes. * they're being called for tutorial purposes.
@ -3177,10 +3372,11 @@ main(int argc, char **argv, char **envp)
JNIEnv *java_env; JNIEnv *java_env;
#endif #endif
gStackBase = (jsuword)&stackDummy; CheckHelpMessages();
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
gStackBase = (jsuword)&stackDummy;
#ifdef XP_OS2 #ifdef XP_OS2
/* these streams are normally line buffered on OS/2 and need a \n, * /* these streams are normally line buffered on OS/2 and need a \n, *
* so we need to unbuffer then to get a reasonable prompt */ * so we need to unbuffer then to get a reasonable prompt */