Commit 52e921da authored by Giovanni Campagna's avatar Giovanni Campagna
Browse files

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
parent 2ab1b3f0
......@@ -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
......
......@@ -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;
}
......
......@@ -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
......
......@@ -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;
......
......@@ -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);
......
......@@ -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));
......
......@@ -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");
......
......@@ -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);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment