/* This file contains code derived from xpcdebug.cpp in Mozilla. The license * for that file follows: */ /* * ***** 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 Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * John Bandhauer (original author) * * 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 #include #include #include "context.h" #include "compat.h" #include "jsapi-util.h" static char* jsvalue_to_string(JSContext* cx, jsval val, gboolean* is_string) { char* value = NULL; JSString* value_str; (void)JS_EnterLocalRootScope(cx); value_str = JS_ValueToString(cx, val); if (value_str) value = gjs_value_debug_string(cx, val); if (value) { const char* found = strstr(value, "function "); if(found && (value == found || value+1 == found || value+2 == found)) { g_free(value); value = g_strdup("[function]"); } } if (is_string) *is_string = JSVAL_IS_STRING(val); JS_LeaveLocalRootScope(cx); return value; } static void format_frame(JSContext* cx, JSStackFrame* fp, GString *buf, int num) { JSPropertyDescArray call_props = { 0, NULL }; JSObject* this_obj = NULL; JSObject* call_obj = NULL; char* funname_str = NULL; const char* filename = NULL; guint32 lineno = 0; guint32 named_arg_count = 0; JSFunction* fun = NULL; JSScript* script; guchar* pc; guint32 i; gboolean is_string; jsval val; (void)JS_EnterLocalRootScope(cx); #ifdef HAVE_JS_ISSCRIPTFRAME if (!JS_IsScriptFrame(cx, fp)) { #else if (JS_IsNativeFrame(cx, fp)) { #endif g_string_append_printf(buf, "%d [native frame]\n", num); goto out; } /* get the info for this stack frame */ script = JS_GetFrameScript(cx, fp); pc = JS_GetFramePC(cx, fp); if (script && pc) { filename = JS_GetScriptFilename(cx, script); lineno = (guint32) JS_PCToLineNumber(cx, script, pc); fun = JS_GetFrameFunction(cx, fp); if (fun) { #ifdef HAVE_JS_GETFUNCTIONNAME funname_str = g_strdup(JS_GetFunctionName(fun)); #else JSString* funname = JS_GetFunctionId(fun); if (funname) funname_str = gjs_string_get_ascii(cx, STRING_TO_JSVAL(funname)); #endif } call_obj = JS_GetFrameCallObject(cx, fp); if (call_obj) { if (!JS_GetPropertyDescArray(cx, call_obj, &call_props)) call_props.array = NULL; } /* mozilla-central commit 38cbd4e02afc */ #ifdef HAVE_JS_ENDPC { jsval thisval; if (JS_GetFrameThis(cx, fp, &thisval) && JSVAL_IS_OBJECT(thisval)) this_obj = JSVAL_TO_OBJECT(thisval); else this_obj = NULL; } #else this_obj = JS_GetFrameThis(cx, fp); #endif } /* print the frame number and function name */ if (funname_str) { g_string_append_printf(buf, "%d %s(", num, funname_str); g_free(funname_str); } else if (fun) g_string_append_printf(buf, "%d anonymous(", num); else g_string_append_printf(buf, "%d ", num); for (i = 0; i < call_props.length; i++) { char *name = NULL; char *value = NULL; JSPropertyDesc* desc = &call_props.array[i]; if(desc->flags & JSPD_ARGUMENT) { name = jsvalue_to_string(cx, desc->id, &is_string); if(!is_string) { g_free(name); name = NULL; } value = jsvalue_to_string(cx, desc->value, &is_string); g_string_append_printf(buf, "%s%s%s%s%s%s", named_arg_count ? ", " : "", name ? name :"", name ? " = " : "", is_string ? "\"" : "", value ? value : "?unknown?", is_string ? "\"" : ""); named_arg_count++; } g_free(name); g_free(value); } /* print any unnamed trailing args (found in 'arguments' object) */ if (call_obj != NULL && JS_GetProperty(cx, call_obj, "arguments", &val) && JSVAL_IS_OBJECT(val)) { guint32 k; guint32 arg_count; JSObject* args_obj = JSVAL_TO_OBJECT(val); if (JS_GetProperty(cx, args_obj, "length", &val) && JS_ValueToECMAUint32(cx, val, &arg_count) && arg_count > named_arg_count) { for (k = named_arg_count; k < arg_count; k++) { char number[8]; g_snprintf(number, 8, "%d", (int) k); if (JS_GetProperty(cx, args_obj, number, &val)) { char *value = jsvalue_to_string(cx, val, &is_string); g_string_append_printf(buf, "%s%s%s%s", k ? ", " : "", is_string ? "\"" : "", value ? value : "?unknown?", is_string ? "\"" : ""); g_free(value); } } } } /* print filename and line number */ g_string_append_printf(buf, "%s [\"%s\":%d]\n", fun ? ")" : "", filename ? filename : "", lineno); out: JS_LeaveLocalRootScope(cx); } void gjs_context_print_stack_to_buffer(GjsContext* context, GString *buf) { JSContext *js_context = (JSContext*)gjs_context_get_native_context(context); JSStackFrame* fp; JSStackFrame* iter = NULL; int num = 0; g_string_append_printf(buf, "== Stack trace for context %p ==\n", context); while ((fp = JS_FrameIterator(js_context, &iter)) != NULL) { format_frame(js_context, fp, buf, num); num++; } if(!num) g_string_append_printf(buf, "(JavaScript stack is empty)\n"); g_string_append(buf, "\n"); } void gjs_context_print_stack_stderr(GjsContext *context) { GString *str = g_string_new(""); gjs_context_print_stack_to_buffer(context, str); g_printerr("%s\n", str->str); g_string_free(str, TRUE); } void gjs_dumpstack(void) { GList *contexts = gjs_context_get_all(); GList *iter; for (iter = contexts; iter; iter = iter->next) { GjsContext *context = (GjsContext*)iter->data; gjs_context_print_stack_stderr(context); g_object_unref(context); } g_list_free(contexts); } #if GJS_BUILD_TESTS void gjstest_test_func_gjs_stack_dump(void) { GjsContext *context; g_type_init(); /* TODO this test could be better - maybe expose dumpstack as a JS API * so that we have a JS stack to dump? At least here we're getting some * coverage. */ context = gjs_context_new(); gjs_dumpstack(); g_object_unref(context); gjs_dumpstack(); } #endif /* GJS_BUILD_TESTS */