[gjs] Add gjs_context_get_all and gjs_dumpstack



commit 6974e0fdfaeaccb821a5f10d60a695f5833d01bc
Author: Colin Walters <walters verbum org>
Date:   Wed Mar 4 10:28:30 2009 -0500

    Add gjs_context_get_all and gjs_dumpstack
    
    The new gjs_dumpstack global function is useful to invoke from a debugger
    such as gdb.  gjs_context_get_all is infrastructure to support the implementation
    of that function, but made public for other global debugging-like uses.
---
 COPYING       |    7 +-
 Makefile.am   |    2 +
 gjs/context.c |   35 ++++++++
 gjs/context.h |    9 ++
 gjs/stack.c   |  247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 298 insertions(+), 2 deletions(-)

diff --git a/COPYING b/COPYING
index 2759cdf..f5350bd 100644
--- a/COPYING
+++ b/COPYING
@@ -18,5 +18,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
-The console module (modules/console.c) contains code from Mozilla which
-is tripple licensed under MPL1.1/LGPLv2+/GPLv2+.
+
+The following files contain code from Mozilla which
+is triple licensed under MPL1.1/LGPLv2+/GPLv2+:
+The console module (modules/console.c)
+Stack printer (gjs/stack.c)
diff --git a/Makefile.am b/Makefile.am
index 25619ea..a1bbf38 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -72,6 +72,7 @@ libgjs_la_SOURCES =		\
 	gjs/mem.c		\
 	gjs/native.c		\
 	gjs/profiler.c		\
+	gjs/stack.c		\
 	util/error.c		\
 	util/glib.c		\
 	util/crash.c		\
@@ -82,6 +83,7 @@ gjstest_files_with_tests += 	\
 	gjs/jsapi-util-array.c	\
 	gjs/jsapi-util-error.c	\
 	gjs/jsapi-util-string.c	\
+	gjs/stack.c				\
 	util/glib.c
 
 include Makefile-gi.am
diff --git a/gjs/context.c b/gjs/context.c
index 8448343..b121a1b 100644
--- a/gjs/context.c
+++ b/gjs/context.c
@@ -86,6 +86,11 @@ enum {
     PROP_IS_LOAD_CONTEXT
 };
 
+
+static GStaticMutex contexts_lock = G_STATIC_MUTEX_INIT;
+static GList *all_contexts = NULL;
+
+
 static JSBool
 gjs_log(JSContext *context,
         JSObject  *obj,
@@ -291,6 +296,10 @@ gjs_context_finalize(GObject *object)
         g_strfreev(js_context->search_path);
         js_context->search_path = NULL;
     }
+    
+    g_static_mutex_lock(&contexts_lock);
+    all_contexts = g_list_remove(all_contexts, object);
+    g_static_mutex_unlock(&contexts_lock);
 
     G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
 }
@@ -411,6 +420,10 @@ gjs_context_constructor (GType                  type,
         js_context->profiler = gjs_profiler_new(js_context->runtime);
     }
 
+    g_static_mutex_lock (&contexts_lock);
+    all_contexts = g_list_prepend(all_contexts, object);
+    g_static_mutex_unlock (&contexts_lock);
+
     return object;
 }
 
@@ -476,6 +489,28 @@ gjs_context_new_with_search_path(char** search_path)
                          NULL);
 }
 
+/**
+ * gjs_context_get_all:
+ * 
+ * Returns a newly-allocated list containing all known instances of #GjsContext.
+ * This is useful for operating on the contexts from a process-global situation
+ * such as a debugger.
+ * 
+ * Return value: (element-type GjsContext) (transfer full): Known #GjsContext instances
+ */
+GList*
+gjs_context_get_all(void)
+{
+  GList *result;
+  GList *iter;
+  g_static_mutex_lock (&contexts_lock);
+  result = g_list_copy(all_contexts);
+  for (iter = result; iter; iter = iter->next)
+    g_object_ref((GObject*)iter->data);
+  g_static_mutex_unlock (&contexts_lock);
+  return result;
+}
+
 JSContext*
 gjs_context_get_context(GjsContext *js_context)
 {
diff --git a/gjs/context.h b/gjs/context.h
index cbb9bed..a5b518c 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -62,6 +62,15 @@ gboolean        gjs_context_define_string_array  (GjsContext  *js_context,
                                                   const char   **array_values,
                                                   GError       **error);
 
+GList*          gjs_context_get_all              (void);
+
+void            gjs_context_print_stack_to_buffer (GjsContext *js_context,
+                                                   GString    *buf);
+
+void            gjs_context_print_stack_stderr    (GjsContext *js_context);
+
+void            gjs_dumpstack                     (void);
+
 G_END_DECLS
 
 #endif  /* __GJS_CONTEXT_H__ */
diff --git a/gjs/stack.c b/gjs/stack.c
new file mode 100644
index 0000000..611c4af
--- /dev/null
+++ b/gjs/stack.c
@@ -0,0 +1,247 @@
+/* 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 <jband netscape com> (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 <config.h>
+#include "context-jsapi.h"
+#include <glib.h>
+#include <string.h>
+#include <jsdbgapi.h>
+
+static const char* 
+jsvalue_to_string(JSContext* cx, jsval val, gboolean* is_string)
+{
+    const char* value = NULL;
+    JSString* value_str;
+    
+    JS_EnterLocalRootScope(cx);
+    
+    value_str = JS_ValueToString(cx, val);
+    if (value_str)
+        value = JS_GetStringBytes(value_str);
+    if (value) {
+        const char* found = strstr(value, "function ");
+        if(found && (value == found || value+1 == found || value+2 == found))
+            value = "[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;
+    const char* funname = 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;
+
+    JS_EnterLocalRootScope(cx);
+    
+    if (JS_IsNativeFrame(cx, fp)) {
+        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)
+            funname = JS_GetFunctionName(fun);
+        
+        call_obj = JS_GetFrameCallObject(cx, fp);
+        if (call_obj) {
+            if (!JS_GetPropertyDescArray(cx, call_obj, &call_props))
+                call_props.array = NULL;
+        }
+
+        this_obj = JS_GetFrameThis(cx, fp);
+    }
+
+    /* print the frame number and function name */
+
+    if (funname)
+        g_string_append_printf(buf, "%d %s(", num, funname);
+    else if (fun)
+        g_string_append_printf(buf, "%d anonymous(", num);
+    else
+        g_string_append_printf(buf, "%d <TOP LEVEL>", num);
+
+    for (i = 0; i < call_props.length; i++) {
+        const char *name;
+        const char *value;
+        JSPropertyDesc* desc = &call_props.array[i];
+        if(desc->flags & JSPD_ARGUMENT) {
+            name = jsvalue_to_string(cx, desc->id, &is_string);
+            if(!is_string)
+                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++;
+        }
+    }
+
+    /* 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)) {
+                    const 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 ? "\"" : "");
+                }
+            }
+        }
+    }
+
+    /* print filename and line number */
+
+    g_string_append_printf(buf, "%s [\"%s\":%d]\n",
+                           fun ? ")" : "",
+                           filename ? filename : "<unknown>",
+                           lineno);
+  out:
+    JS_LeaveLocalRootScope(cx);
+}
+
+void
+gjs_context_print_stack_to_buffer(GjsContext* context, GString *buf)
+{
+    JSContext *js_context = gjs_context_get_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 */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]