1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : *
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla Communicator client code, released
17 : * March 31, 1998.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Netscape Communications Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 1999
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * John Bandhauer <jband@netscape.com> (original author)
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 "xpcprivate.h"
42 :
43 : #ifdef TAB
44 : #undef TAB
45 : #endif
46 : #define TAB " "
47 :
48 617 : static const char* JSVAL2String(JSContext* cx, jsval val, JSBool* isString,
49 : JSAutoByteString *bytes)
50 : {
51 1234 : JSAutoRequest ar(cx);
52 617 : const char* value = nsnull;
53 617 : JSString* value_str = JS_ValueToString(cx, val);
54 617 : if (value_str)
55 617 : value = bytes->encode(cx, value_str);
56 617 : if (value) {
57 617 : const char* found = strstr(value, "function ");
58 617 : if (found && (value == found || value+1 == found || value+2 == found))
59 37 : value = "[function]";
60 : }
61 :
62 617 : if (isString)
63 562 : *isString = JSVAL_IS_STRING(val);
64 617 : return value;
65 : }
66 :
67 445 : static char* FormatJSFrame(JSContext* cx, JSStackFrame* fp,
68 : char* buf, int num,
69 : JSBool showArgs, JSBool showLocals, JSBool showThisProps)
70 : {
71 445 : JSPropertyDescArray callProps = {0, nsnull};
72 445 : JSPropertyDescArray thisProps = {0, nsnull};
73 445 : JSBool gotThisVal = false;
74 : jsval thisVal;
75 445 : JSObject* callObj = nsnull;
76 445 : JSString* funname = nsnull;
77 890 : JSAutoByteString funbytes;
78 445 : const char* filename = nsnull;
79 445 : PRInt32 lineno = 0;
80 445 : JSFunction* fun = nsnull;
81 445 : uint32_t namedArgCount = 0;
82 : jsval val;
83 : JSBool isString;
84 :
85 : // get the info for this stack frame
86 :
87 445 : JSScript* script = JS_GetFrameScript(cx, fp);
88 445 : jsbytecode* pc = JS_GetFramePC(cx, fp);
89 :
90 890 : JSAutoRequest ar(cx);
91 890 : JSAutoEnterCompartment ac;
92 445 : if (!ac.enter(cx, JS_GetGlobalForFrame(fp)))
93 0 : return buf;
94 :
95 445 : if (script && pc) {
96 396 : filename = JS_GetScriptFilename(cx, script);
97 396 : lineno = (PRInt32) JS_PCToLineNumber(cx, script, pc);
98 396 : fun = JS_GetFrameFunction(cx, fp);
99 396 : if (fun)
100 344 : funname = JS_GetFunctionId(fun);
101 :
102 396 : if (showArgs || showLocals) {
103 396 : callObj = JS_GetFrameCallObject(cx, fp);
104 396 : if (callObj)
105 344 : if (!JS_GetPropertyDescArray(cx, callObj, &callProps))
106 0 : callProps.array = nsnull; // just to be sure
107 : }
108 :
109 396 : gotThisVal = JS_GetFrameThis(cx, fp, &thisVal);
110 396 : if (!gotThisVal ||
111 : !showThisProps ||
112 0 : JSVAL_IS_PRIMITIVE(thisVal) ||
113 : !JS_GetPropertyDescArray(cx, JSVAL_TO_OBJECT(thisVal),
114 0 : &thisProps)) {
115 396 : thisProps.array = nsnull; // just to be sure
116 : }
117 : }
118 :
119 : // print the frame number and function name
120 :
121 445 : if (funname)
122 141 : buf = JS_sprintf_append(buf, "%d %s(", num, funbytes.encode(cx, funname));
123 304 : else if (fun)
124 203 : buf = JS_sprintf_append(buf, "%d anonymous(", num);
125 : else
126 101 : buf = JS_sprintf_append(buf, "%d <TOP LEVEL>", num);
127 445 : if (!buf) goto out;
128 :
129 : // print the function arguments
130 :
131 445 : if (showArgs && callObj) {
132 875 : for (uint32_t i = 0; i < callProps.length; i++) {
133 531 : JSPropertyDesc* desc = &callProps.array[i];
134 531 : if (desc->flags & JSPD_ARGUMENT) {
135 506 : JSAutoByteString nameBytes;
136 253 : const char* name = JSVAL2String(cx, desc->id, &isString, &nameBytes);
137 253 : if (!isString)
138 0 : name = nsnull;
139 506 : JSAutoByteString valueBytes;
140 253 : const char* value = JSVAL2String(cx, desc->value, &isString, &valueBytes);
141 :
142 : buf = JS_sprintf_append(buf, "%s%s%s%s%s%s",
143 : namedArgCount ? ", " : "",
144 : name ? name :"",
145 : name ? " = " : "",
146 : isString ? "\"" : "",
147 : value ? value : "?unknown?",
148 253 : isString ? "\"" : "");
149 253 : if (!buf) goto out;
150 506 : namedArgCount++;
151 : }
152 : }
153 :
154 : // print any unnamed trailing args (found in 'arguments' object)
155 :
156 688 : if (JS_GetProperty(cx, callObj, "arguments", &val) &&
157 344 : JSVAL_IS_OBJECT(val)) {
158 : uint32_t argCount;
159 344 : JSObject* argsObj = JSVAL_TO_OBJECT(val);
160 688 : if (JS_GetProperty(cx, argsObj, "length", &val) &&
161 344 : JS_ValueToECMAUint32(cx, val, &argCount) &&
162 : argCount > namedArgCount) {
163 2 : for (uint32_t k = namedArgCount; k < argCount; k++) {
164 : char number[8];
165 1 : JS_snprintf(number, 8, "%d", (int) k);
166 :
167 1 : if (JS_GetProperty(cx, argsObj, number, &val)) {
168 2 : JSAutoByteString valueBytes;
169 1 : const char *value = JSVAL2String(cx, val, &isString, &valueBytes);
170 : buf = JS_sprintf_append(buf, "%s%s%s%s",
171 : k ? ", " : "",
172 : isString ? "\"" : "",
173 : value ? value : "?unknown?",
174 1 : isString ? "\"" : "");
175 1 : if (!buf) goto out;
176 : }
177 : }
178 : }
179 : }
180 : }
181 :
182 : // print filename and line number
183 :
184 : buf = JS_sprintf_append(buf, "%s [\"%s\":%d]\n",
185 : fun ? ")" : "",
186 : filename ? filename : "<unknown>",
187 445 : lineno);
188 445 : if (!buf) goto out;
189 :
190 : // print local variables
191 :
192 445 : if (showLocals && callProps.array) {
193 820 : for (uint32_t i = 0; i < callProps.length; i++) {
194 531 : JSPropertyDesc* desc = &callProps.array[i];
195 531 : if (desc->flags & JSPD_VARIABLE) {
196 110 : JSAutoByteString nameBytes;
197 110 : JSAutoByteString valueBytes;
198 55 : const char *name = JSVAL2String(cx, desc->id, nsnull, &nameBytes);
199 55 : const char *value = JSVAL2String(cx, desc->value, &isString, &valueBytes);
200 :
201 55 : if (name && value) {
202 : buf = JS_sprintf_append(buf, TAB "%s = %s%s%s\n",
203 : name,
204 : isString ? "\"" : "",
205 : value,
206 55 : isString ? "\"" : "");
207 55 : if (!buf) goto out;
208 : }
209 : }
210 : }
211 : }
212 :
213 : // print the value of 'this'
214 :
215 445 : if (showLocals) {
216 445 : if (gotThisVal) {
217 : JSString* thisValStr;
218 792 : JSAutoByteString thisValBytes;
219 :
220 792 : if (nsnull != (thisValStr = JS_ValueToString(cx, thisVal)) &&
221 396 : thisValBytes.encode(cx, thisValStr)) {
222 396 : buf = JS_sprintf_append(buf, TAB "this = %s\n", thisValBytes.ptr());
223 396 : if (!buf) goto out;
224 : }
225 : } else
226 49 : buf = JS_sprintf_append(buf, TAB "<failed to get 'this' value>\n");
227 : }
228 :
229 : // print the properties of 'this', if it is an object
230 :
231 445 : if (showThisProps && thisProps.array) {
232 :
233 0 : for (uint32_t i = 0; i < thisProps.length; i++) {
234 0 : JSPropertyDesc* desc = &thisProps.array[i];
235 0 : if (desc->flags & JSPD_ENUMERATE) {
236 0 : JSAutoByteString nameBytes;
237 0 : JSAutoByteString valueBytes;
238 0 : const char *name = JSVAL2String(cx, desc->id, nsnull, &nameBytes);
239 0 : const char *value = JSVAL2String(cx, desc->value, &isString, &valueBytes);
240 0 : if (name && value) {
241 : buf = JS_sprintf_append(buf, TAB "this.%s = %s%s%s\n",
242 : name,
243 : isString ? "\"" : "",
244 : value,
245 0 : isString ? "\"" : "");
246 0 : if (!buf) goto out;
247 : }
248 : }
249 : }
250 : }
251 :
252 : out:
253 445 : if (callProps.array)
254 289 : JS_PutPropertyDescArray(cx, &callProps);
255 445 : if (thisProps.array)
256 0 : JS_PutPropertyDescArray(cx, &thisProps);
257 445 : return buf;
258 : }
259 :
260 49 : static char* FormatJSStackDump(JSContext* cx, char* buf,
261 : JSBool showArgs, JSBool showLocals,
262 : JSBool showThisProps)
263 : {
264 : JSStackFrame* fp;
265 49 : JSStackFrame* iter = nsnull;
266 49 : int num = 0;
267 :
268 543 : while (nsnull != (fp = JS_FrameIterator(cx, &iter))) {
269 445 : buf = FormatJSFrame(cx, fp, buf, num, showArgs, showLocals, showThisProps);
270 445 : num++;
271 : }
272 :
273 49 : if (!num)
274 0 : buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
275 :
276 49 : return buf;
277 : }
278 :
279 : JSBool
280 49 : xpc_DumpJSStack(JSContext* cx, JSBool showArgs, JSBool showLocals, JSBool showThisProps)
281 : {
282 49 : if (char* buf = xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps)) {
283 49 : fputs(buf, stdout);
284 49 : JS_smprintf_free(buf);
285 : }
286 49 : return true;
287 : }
288 :
289 : char*
290 49 : xpc_PrintJSStack(JSContext* cx, JSBool showArgs, JSBool showLocals,
291 : JSBool showThisProps)
292 : {
293 : char* buf;
294 49 : JSExceptionState *state = JS_SaveExceptionState(cx);
295 49 : if (!state)
296 0 : puts("Call to a debug function modifying state!");
297 :
298 49 : JS_ClearPendingException(cx);
299 :
300 49 : buf = FormatJSStackDump(cx, nsnull, showArgs, showLocals, showThisProps);
301 49 : if (!buf)
302 0 : puts("Failed to format JavaScript stack for dump");
303 :
304 49 : JS_RestoreExceptionState(cx, state);
305 49 : return buf;
306 : }
307 :
308 : /***************************************************************************/
309 :
310 : static void
311 0 : xpcDumpEvalErrorReporter(JSContext *cx, const char *message,
312 : JSErrorReport *report)
313 : {
314 0 : printf("Error: %s\n", message);
315 0 : }
316 :
317 : JSBool
318 0 : xpc_DumpEvalInJSStackFrame(JSContext* cx, uint32_t frameno, const char* text)
319 : {
320 : JSStackFrame* fp;
321 0 : JSStackFrame* iter = nsnull;
322 0 : uint32_t num = 0;
323 :
324 0 : if (!cx || !text) {
325 0 : puts("invalid params passed to xpc_DumpEvalInJSStackFrame!");
326 0 : return false;
327 : }
328 :
329 0 : printf("js[%d]> %s\n", frameno, text);
330 :
331 0 : while (nsnull != (fp = JS_FrameIterator(cx, &iter))) {
332 0 : if (num == frameno)
333 0 : break;
334 0 : num++;
335 : }
336 :
337 0 : if (!fp) {
338 0 : puts("invalid frame number!");
339 0 : return false;
340 : }
341 :
342 0 : JSAutoRequest ar(cx);
343 :
344 0 : JSExceptionState* exceptionState = JS_SaveExceptionState(cx);
345 0 : JSErrorReporter older = JS_SetErrorReporter(cx, xpcDumpEvalErrorReporter);
346 :
347 : jsval rval;
348 : JSString* str;
349 0 : JSAutoByteString bytes;
350 0 : if (JS_EvaluateInStackFrame(cx, fp, text, strlen(text), "eval", 1, &rval) &&
351 : nsnull != (str = JS_ValueToString(cx, rval)) &&
352 0 : bytes.encode(cx, str)) {
353 0 : printf("%s\n", bytes.ptr());
354 : } else
355 0 : puts("eval failed!");
356 0 : JS_SetErrorReporter(cx, older);
357 0 : JS_RestoreExceptionState(cx, exceptionState);
358 0 : return true;
359 : }
360 :
361 : /***************************************************************************/
362 :
363 : JSTrapStatus
364 49 : xpc_DebuggerKeywordHandler(JSContext *cx, JSScript *script, jsbytecode *pc,
365 : jsval *rval, void *closure)
366 : {
367 : static const char line[] =
368 : "------------------------------------------------------------------------";
369 49 : puts(line);
370 49 : puts("Hit JavaScript \"debugger\" keyword. JS call stack...");
371 49 : xpc_DumpJSStack(cx, true, true, false);
372 49 : puts(line);
373 49 : return JSTRAP_CONTINUE;
374 : }
375 :
376 1404 : JSBool xpc_InstallJSDebuggerKeywordHandler(JSRuntime* rt)
377 : {
378 1404 : return JS_SetDebuggerHandler(rt, xpc_DebuggerKeywordHandler, nsnull);
379 : }
380 :
381 : /***************************************************************************/
382 :
383 : // The following will dump info about an object to stdout...
384 :
385 :
386 : // Quick and dirty (debug only damnit!) class to track which JSObjects have
387 : // been visited as we traverse.
388 :
389 : class ObjectPile
390 : {
391 : public:
392 : enum result {primary, seen, overflow};
393 :
394 0 : result Visit(JSObject* obj)
395 : {
396 0 : if (member_count == max_count)
397 0 : return overflow;
398 0 : for (int i = 0; i < member_count; i++)
399 0 : if (array[i] == obj)
400 0 : return seen;
401 0 : array[member_count++] = obj;
402 0 : return primary;
403 : }
404 :
405 0 : ObjectPile() : member_count(0){}
406 :
407 : private:
408 : enum {max_count = 50};
409 : JSObject* array[max_count];
410 : int member_count;
411 : };
412 :
413 :
414 : static const int tab_width = 2;
415 : #define INDENT(_d) (_d)*tab_width, " "
416 :
417 0 : static void PrintObjectBasics(JSObject* obj)
418 : {
419 0 : if (JS_IsNative(obj))
420 : printf("%p 'native' <%s>",
421 0 : (void *)obj, js::GetObjectClass(obj)->name);
422 : else
423 0 : printf("%p 'host'", (void *)obj);
424 0 : }
425 :
426 0 : static void PrintObject(JSObject* obj, int depth, ObjectPile* pile)
427 : {
428 0 : PrintObjectBasics(obj);
429 :
430 0 : switch (pile->Visit(obj)) {
431 : case ObjectPile::primary:
432 0 : puts("");
433 0 : break;
434 : case ObjectPile::seen:
435 0 : puts(" (SEE ABOVE)");
436 0 : return;
437 : case ObjectPile::overflow:
438 0 : puts(" (TOO MANY OBJECTS)");
439 0 : return;
440 : }
441 :
442 0 : if (!JS_IsNative(obj))
443 0 : return;
444 :
445 0 : JSObject* parent = js::GetObjectParent(obj);
446 0 : JSObject* proto = js::GetObjectProto(obj);
447 :
448 0 : printf("%*sparent: ", INDENT(depth+1));
449 0 : if (parent)
450 0 : PrintObject(parent, depth+1, pile);
451 : else
452 0 : puts("null");
453 0 : printf("%*sproto: ", INDENT(depth+1));
454 0 : if (proto)
455 0 : PrintObject(proto, depth+1, pile);
456 : else
457 0 : puts("null");
458 : }
459 :
460 : JSBool
461 0 : xpc_DumpJSObject(JSObject* obj)
462 : {
463 0 : ObjectPile pile;
464 :
465 0 : puts("Debugging reminders...");
466 0 : puts(" class: (JSClass*)(obj->fslots[2]-1)");
467 0 : puts(" parent: (JSObject*)(obj->fslots[1])");
468 0 : puts(" proto: (JSObject*)(obj->fslots[0])");
469 0 : puts("");
470 :
471 0 : if (obj)
472 0 : PrintObject(obj, 0, &pile);
473 : else
474 0 : puts("xpc_DumpJSObject passed null!");
475 :
476 0 : return true;
477 : }
|