[gjs] GError: add stack, fileName and lineNumber



commit 52e921dacf553564f6503668bf0be82cfb782d7c
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Sat Mar 3 18:03:14 2012 +0100

    GError: add stack, fileName and lineNumber
    
    Similar to native Errors(), GLib.Error is extended to provide debug
    information in the form of fileName, lineNumber and stack (obtained
    using the JS debug API). At the same time, the existing stack logging
    facility is modified to be similar in format to the native one, and
    logError is modified to avoid iterating object properties (which
    gives an undefined order, and does not include prototype properties)
    
    https://bugzilla.gnome.org/show_bug.cgi?id=591480

 gi/arg.c               |    4 +-
 gi/gerror.c            |   63 ++++++++++++++++++++++++++++++++++++++++-
 gi/gerror.h            |    3 +-
 gi/value.c             |    2 +-
 gjs/context.h          |    4 ++
 gjs/jsapi-util-error.c |    2 +-
 gjs/jsapi-util.c       |   36 ++++++++++++-----------
 gjs/stack.c            |   73 +++++++++++++++++++++++++----------------------
 8 files changed, 130 insertions(+), 57 deletions(-)
---
diff --git a/gi/arg.c b/gi/arg.c
index af1750d..8250eb8 100644
--- a/gi/arg.c
+++ b/gi/arg.c
@@ -2379,7 +2379,7 @@ gjs_value_from_g_argument (JSContext  *context,
     case GI_TYPE_TAG_ERROR:
         {
             if (arg->v_pointer) {
-                JSObject *obj = gjs_error_from_gerror(context, arg->v_pointer);
+                JSObject *obj = gjs_error_from_gerror(context, arg->v_pointer, FALSE);
                 if (obj) {
                     *value_p = OBJECT_TO_JSVAL(obj);
                     return JS_TRUE;
@@ -2459,7 +2459,7 @@ gjs_value_from_g_argument (JSContext  *context,
             if (g_type_is_a(gtype, G_TYPE_ERROR)) {
                 JSObject *obj;
 
-                obj = gjs_error_from_gerror(context, arg->v_pointer);
+                obj = gjs_error_from_gerror(context, arg->v_pointer, FALSE);
                 if (obj)
                     value = OBJECT_TO_JSVAL(obj);
                 else
diff --git a/gi/gerror.c b/gi/gerror.c
index 5d50cd5..45dc387 100644
--- a/gi/gerror.c
+++ b/gi/gerror.c
@@ -35,6 +35,7 @@
 #include <util/log.h>
 
 #include <jsapi.h>
+#include <jsdbgapi.h>
 
 #include <girepository.h>
 
@@ -53,6 +54,8 @@ enum {
 
 static struct JSClass gjs_error_class;
 
+static void define_error_properties(JSContext *, JSObject *);
+
 GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Error, gjs_error_class)
 
 GJS_NATIVE_CONSTRUCTOR_DECLARE(error)
@@ -115,6 +118,9 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(error)
 
     g_free (message);
 
+    /* We assume this error will be thrown in the same line as the constructor */
+    define_error_properties(context, object);
+
     GJS_NATIVE_CONSTRUCTOR_FINISH(boxed);
 
     return JS_TRUE;
@@ -514,9 +520,61 @@ find_error_domain_info(GQuark domain)
     return info;
 }
 
+/* define properties that JS Error() expose, such as
+   fileName, lineNumber and stack
+*/
+static void
+define_error_properties(JSContext *context,
+                        JSObject  *obj)
+{
+    JSStackFrame *frame;
+    JSScript *script;
+    jsbytecode *pc;
+    jsval v;
+    GString *stack;
+    const char *filename;
+    GjsContext *gjs_context;
+
+    /* find the JS frame that triggered the error */
+    frame = NULL;
+    while (JS_FrameIterator(context, &frame)) {
+        if (JS_IsScriptFrame(context, frame))
+            break;
+    }
+
+    /* someone called gjs_throw at top of the stack?
+       well, no stack in that case
+    */
+    if (!frame)
+        return;
+
+    script = JS_GetFrameScript(context, frame);
+    pc = JS_GetFramePC(context, frame);
+
+    stack = g_string_new(NULL);
+    gjs_context = JS_GetContextPrivate(context);
+    gjs_context_print_stack_to_buffer(gjs_context, frame, stack);
+
+    if (gjs_string_from_utf8(context, stack->str, stack->len, &v))
+        JS_DefineProperty(context, obj, "stack", v,
+                          NULL, NULL, JSPROP_ENUMERATE);
+
+    filename = JS_GetScriptFilename(context, script);
+    if (gjs_string_from_filename(context, filename, -1, &v))
+        JS_DefineProperty(context, obj, "fileName", v,
+                          NULL, NULL, JSPROP_ENUMERATE);
+
+    v = INT_TO_JSVAL(JS_PCToLineNumber(context, script, pc));
+    JS_DefineProperty(context, obj, "lineNumber", v,
+                      NULL, NULL, JSPROP_ENUMERATE);
+
+    g_string_free(stack, TRUE);
+}
+
 JSObject*
 gjs_error_from_gerror(JSContext             *context,
-                      GError                *gerror)
+                      GError                *gerror,
+                      gboolean               add_stack)
 {
     JSObject *obj;
     JSObject *proto;
@@ -560,6 +618,9 @@ gjs_error_from_gerror(JSContext             *context,
     g_base_info_ref( (GIBaseInfo*) priv->info);
     priv->gerror = g_error_copy(gerror);
 
+    if (add_stack)
+        define_error_properties(context, obj);
+
     return obj;
 }
 
diff --git a/gi/gerror.h b/gi/gerror.h
index 0d93c44..3940d1a 100644
--- a/gi/gerror.h
+++ b/gi/gerror.h
@@ -46,7 +46,8 @@ JSClass*  gjs_lookup_error_class       (JSContext             *context,
 GError*   gjs_gerror_from_error        (JSContext             *context,
                                         JSObject              *obj);
 JSObject* gjs_error_from_gerror        (JSContext             *context,
-                                        GError                *gerror);
+                                        GError                *gerror,
+                                        gboolean               add_stack);
 
 G_END_DECLS
 
diff --git a/gi/value.c b/gi/value.c
index c64564f..4d02827 100644
--- a/gi/value.c
+++ b/gi/value.c
@@ -675,7 +675,7 @@ gjs_value_from_g_value_internal(JSContext    *context,
 
         /* special case GError */
         if (g_type_is_a(gtype, G_TYPE_ERROR)) {
-            obj = gjs_error_from_gerror(context, gboxed);
+            obj = gjs_error_from_gerror(context, gboxed, FALSE);
             *value_p = OBJECT_TO_JSVAL(obj);
 
             return TRUE;
diff --git a/gjs/context.h b/gjs/context.h
index 4e01cc7..d19d8a5 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -68,7 +68,11 @@ gboolean        gjs_context_define_string_array  (GjsContext  *js_context,
 GList*          gjs_context_get_all              (void);
 void*           gjs_context_get_native_context   (GjsContext *js_context);
 
+/* initial_frame is a JSStackFrame, but cannot be exposed as such in the
+   public API. Pass NULL to get the topmost frame.
+*/
 void            gjs_context_print_stack_to_buffer (GjsContext *js_context,
+                                                   void       *initial_frame,
                                                    GString    *buf);
 
 void            gjs_context_print_stack_stderr    (GjsContext *js_context);
diff --git a/gjs/jsapi-util-error.c b/gjs/jsapi-util-error.c
index 8bce07e..88ac97e 100644
--- a/gjs/jsapi-util-error.c
+++ b/gjs/jsapi-util-error.c
@@ -161,7 +161,7 @@ gjs_throw_g_error (JSContext       *context,
 
     JS_BeginRequest(context);
 
-    err_obj = gjs_error_from_gerror(context, error);
+    err_obj = gjs_error_from_gerror(context, error, TRUE);
     if (err_obj)
         JS_SetPendingException(context, OBJECT_TO_JSVAL(err_obj));
 
diff --git a/gjs/jsapi-util.c b/gjs/jsapi-util.c
index 6efd528..ca2965e 100644
--- a/gjs/jsapi-util.c
+++ b/gjs/jsapi-util.c
@@ -970,6 +970,21 @@ gjs_explain_scope(JSContext  *context,
     JS_EndRequest(context);
 }
 
+static void
+log_one_exception_property(JSContext  *context,
+                           JSObject   *object,
+                           const char *name)
+{
+    jsval v;
+    char *debugstr;
+
+    gjs_object_get_property(context, object, name, &v);
+
+    debugstr = gjs_value_debug_string(context, v);
+    gjs_debug(GJS_DEBUG_ERROR, "  %s = '%s'", name, debugstr);
+    g_free(debugstr);
+}
+
 void
 gjs_log_exception_props(JSContext *context,
                         jsval      exc)
@@ -988,23 +1003,10 @@ gjs_log_exception_props(JSContext *context,
 
         exc_obj = JSVAL_TO_OBJECT(exc);
 
-        /* I guess this is a SpiderMonkey bug.  If we don't get these
-         * properties here, only 'message' shows up when we enumerate
-         * all properties below. I did not debug in detail, so maybe
-         * it's something wrong with our enumeration loop below. In
-         * any case, if you remove this code block, check that "throw
-         * Error()" still results in printing all four of these props.
-         * For me right now, if you remove this block, only message
-         * gets printed.
-         */
-        gjs_object_has_property(context, exc_obj, "stack");
-        gjs_object_has_property(context, exc_obj, "fileName");
-        gjs_object_has_property(context, exc_obj, "lineNumber");
-        gjs_object_has_property(context, exc_obj, "message");
-
-        gjs_log_object_props(context, exc_obj,
-                             GJS_DEBUG_ERROR,
-                             "  ");
+        log_one_exception_property(context, exc_obj, "message");
+        log_one_exception_property(context, exc_obj, "fileName");
+        log_one_exception_property(context, exc_obj, "lineNumber");
+        log_one_exception_property(context, exc_obj, "stack");
     } else if (JSVAL_IS_STRING(exc)) {
         gjs_debug(GJS_DEBUG_ERROR,
                   "Exception was a String");
diff --git a/gjs/stack.c b/gjs/stack.c
index 363366d..84e1c6a 100644
--- a/gjs/stack.c
+++ b/gjs/stack.c
@@ -48,34 +48,40 @@
 #include "compat.h"
 #include "jsapi-util.h"
 
+/* Mimick the behaviour exposed by standard Error objects
+   (http://mxr.mozilla.org/mozilla-central/source/js/src/jsexn.cpp#554)
+*/
 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]");
-        }
+    JSString* value_str = NULL;
+
+    if (JSVAL_IS_PRIMITIVE(val)) {
+      value_str = JS_ValueToSource(cx, val);
+    } else {
+      JSObject *obj = JSVAL_TO_OBJECT(val);
+
+      if (JS_ObjectIsFunction(cx, obj)) {
+	JSFunction *fn = JS_ValueToFunction(cx, val);
+	value_str = JS_GetFunctionId(fn);
+
+	if (!value_str)
+	  value = g_strdup("[unknown function]");
+      } else {
+	value = g_strdup_printf("[object %s]", JS_GetClass(cx, obj)->name);
+      }
     }
 
+    if (!value && value_str)
+      value = gjs_value_debug_string(cx, val);
+
     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)
@@ -167,14 +173,10 @@ format_frame(JSContext* cx, JSStackFrame* fp,
         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) &&
+        if (JS_GetArrayLength(cx, args_obj, &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)) {
+                if (JS_GetElement(cx, args_obj, k, &val)) {
                     char *value = jsvalue_to_string(cx, val, &is_string);
                     g_string_append_printf(buf, "%s%s%s%s",
                                            k ? ", " : "",
@@ -189,38 +191,41 @@ format_frame(JSContext* cx, JSStackFrame* fp,
 
     /* print filename and line number */
 
-    g_string_append_printf(buf, "%s [\"%s\":%d]\n",
+    g_string_append_printf(buf, "%s %s:%d\n",
                            fun ? ")" : "",
-                           filename ? filename : "<unknown>",
+                           filename ? filename : "",
                            lineno);
+
   out:
+    if (call_props.array)
+      JS_PutPropertyDescArray(cx, &call_props);
+
     JS_LeaveLocalRootScope(cx);
 }
 
 void
-gjs_context_print_stack_to_buffer(GjsContext* context, GString *buf)
+gjs_context_print_stack_to_buffer(GjsContext* context, void *initial, GString *buf)
 {
     JSContext *js_context = (JSContext*)gjs_context_get_native_context(context);
-    JSStackFrame* fp;
-    JSStackFrame* iter = NULL;
+    JSStackFrame* fp = initial;
     int num = 0;
 
-    g_string_append_printf(buf, "== Stack trace for context %p ==\n", context);
-    while ((fp = JS_FrameIterator(js_context, &iter)) != NULL) {
+    while (fp) {
         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");
+	JS_FrameIterator(js_context, &fp);
+    }
 }
 
 void
 gjs_context_print_stack_stderr(GjsContext *context)
 {
   GString *str = g_string_new("");
-  gjs_context_print_stack_to_buffer(context, str);
+
+  g_string_append_printf(str, "== Stack trace for context %p ==\n", context);
+  gjs_context_print_stack_to_buffer(context, NULL, str);
+
   g_printerr("%s\n", str->str);
   g_string_free(str, TRUE);
 }



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