<commit_before>/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
 * Copyright (c) 2008  litl, LLC
 * Copyright (c) 2009 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * 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.
 */

#include <config.h>

#include <util/log.h>
#include <util/glib.h>
#include <util/misc.h>

#include "jsapi-util.h"
#include "compat.h"
#include "jsapi-private.h"
#include "runtime.h"

#include <string.h>
#include <math.h>

static GMutex gc_lock;

GQuark
gjs_util_error_quark (void)
{
    return g_quark_from_static_string ("gjs-util-error-quark");
}

/**
 * gjs_get_import_global:
 * @context: a #JSContext
 *
 * Gets the "import global" for the context's runtime. The import
 * global object is the global object for the context. It is used
 * as the root object for the scope of modules loaded by GJS in this
 * runtime, and should also be used as the globals 'obj' argument passed
 * to JS_InitClass() and the parent argument passed to JS_ConstructObject()
 * when creating a native classes that are shared between all contexts using
 * the runtime. (The standard JS classes are not shared, but we share
 * classes such as GObject proxy classes since objects of these classes can
 * easily migrate between contexts and having different classes depending
 * on the context where they were first accessed would be confusing.)
 *
 * Return value: the "import global" for the context's
 *  runtime. Will never return %NULL while GJS has an active context
 *  for the runtime.
 */
JSObject*
gjs_get_import_global(JSContext *context)
{
    return JS_GetGlobalObject(context);
}

static JSClass global_class = {
    "GjsGlobal", JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(GJS_GLOBAL_SLOT_LAST),
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
    NULL /* checkAccess */, NULL /* call */, NULL /* hasInstance */, NULL /* construct */, NULL,
    { NULL }
};

/**
 * gjs_init_context_standard:
 * @context: a #JSContext
 *
 * This function creates a default global object for @context,
 * and calls JS_InitStandardClasses using it.
 *
 * Returns: %TRUE on success, %FALSE otherwise
 */
gboolean
gjs_init_context_standard (JSContext       *context)
{
    JSObject *global;

    global = JS_NewGlobalObject(context, &global_class, NULL);
    if (global == NULL)
        return FALSE;
    if (!JS_InitStandardClasses(context, global))
        return FALSE;

    return TRUE;
}

void
gjs_set_global_slot (JSContext     *context,
                     GjsGlobalSlot  slot,
                     jsval          value)
{
    JSObject *global;
    global = JS_GetGlobalObject(context);
    JS_SetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot, value);
}

jsval
gjs_get_global_slot (JSContext     *context,
                     GjsGlobalSlot  slot)
{
    JSObject *global;
    global = JS_GetGlobalObject(context);
    return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
}

/* Returns whether the object had the property; if the object did
 * not have the property, always sets an exception. Treats
 * "the property's value is JSVAL_VOID" the same as "no such property,".
 * Guarantees that *value_p is set to something, if only JSVAL_VOID,
 * even if an exception is set and false is returned.
 *
 * Requires request.
 */
gboolean
gjs_object_require_property(JSContext       *context,
                            JSObject        *obj,
                            const char      *obj_description,
                            jsid             property_name,
                            jsval           *value_p)
{
    jsval value;
    char *name;

    value = JSVAL_VOID;
    if (value_p)
        *value_p = value;

    if (G_UNLIKELY (!JS_GetPropertyById(context, obj, property_name, &value)))
        return JS_FALSE;

    if (G_LIKELY (!JSVAL_IS_VOID(value))) {
        if (value_p)
            *value_p = value;
        return JS_TRUE;
    }

    /* remember gjs_throw() is a no-op if JS_GetProperty()
     * already set an exception
     */

    gjs_get_string_id(context, property_name, &name);

    if (obj_description)
        gjs_throw(context,
                  "No property '%s' in %s (or its value was undefined)",
                  name, obj_description);
    else
        gjs_throw(context,
                  "No property '%s' in object %p (or its value was undefined)",
                  name, obj);

    g_free(name);
    return JS_FALSE;
}

void
gjs_throw_constructor_error(JSContext *context)
{
    gjs_throw(context,
              "Constructor called as normal method. Use 'new SomeObject()' not 'SomeObject()'");
}

void
gjs_throw_abstract_constructor_error(JSContext *context,
                                     jsval     *vp)
{
    jsval callee;
    jsval prototype;
    JSClass *proto_class;
    const char *name = "anonymous";

    callee = JS_CALLEE(context, vp);

    if (JSVAL_IS_OBJECT(callee)) {
        if (gjs_object_get_property_const(context, JSVAL_TO_OBJECT(callee),
                                          GJS_STRING_PROTOTYPE, &prototype)) {
            proto_class = JS_GetClass(JSVAL_TO_OBJECT(prototype));
            name = proto_class->name;
        }
    }

    gjs_throw(context, "You cannot construct new instances of '%s'", name);
}

JSObject*
gjs_define_string_array(JSContext   *context,
                        JSObject    *in_object,
                        const char  *array_name,
                        gssize       array_length,
                        const char **array_values,
                        unsigned     attrs)
{
    GArray *elems;
    JSObject *array;
    int i;

    JS_BeginRequest(context);

    if (!JS_EnterLocalRootScope(context)) {
        JS_EndRequest(context);
        return JS_FALSE;
    }

    if (array_length == -1)
        array_length = g_strv_length((char**)array_values);

    elems = g_array_sized_new(FALSE, FALSE, sizeof(jsval), array_length);

    for (i = 0; i < array_length; ++i) {
        jsval element;
        element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, array_values[i]));
        g_array_append_val(elems, element);
    }

    array = JS_NewArrayObject(context, elems->len, (jsval*) elems->data);
    g_array_free(elems, TRUE);

    if (array != NULL) {
        if (!JS_DefineProperty(context, in_object,
                               array_name, OBJECT_TO_JSVAL(array),
                               NULL, NULL, attrs))
            array = NULL;
    }

    JS_LeaveLocalRootScope(context);

    JS_EndRequest(context);
    return array;
}

/**
 * gjs_string_readable:
 *
 * Return a string that can be read back by gjs-console; for
 * JS strings that contain valid Unicode, we return a UTF-8 formatted
 * string.  Otherwise, we return one where non-ASCII-printable bytes
 * are \x escaped.
 *
 */
static char *
gjs_string_readable (JSContext   *context,
                     JSString    *string)
{
    GString *buf = g_string_new("");
    char *chars;

    JS_BeginRequest(context);

    g_string_append_c(buf, '"');

    if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(string), &chars)) {
        size_t i, len;
        const jschar *uchars;

        uchars = JS_GetStringCharsAndLength(context, string, &len);

        for (i = 0; i < len; i++) {
            jschar c = uchars[i];
            if (c >> 8 == 0 && g_ascii_isprint(c & 0xFF))
                g_string_append_c(buf, c & 0xFF);
            else
                g_string_append_printf(buf, "\\u%04X", c);
        }
    } else {
        g_string_append(buf, chars);
        g_free(chars);
    }

    g_string_append_c(buf, '"');

    JS_EndRequest(context);

    return g_string_free(buf, FALSE);
}

/**
 * gjs_value_debug_string:
 * @context:
 * @value: Any JavaScript value
 *
 * Returns: A UTF-8 encoded string describing @value
 */
char*
gjs_value_debug_string(JSContext      *context,
                       jsval           value)
{
    JSString *str;
    char *bytes;
    char *debugstr;

    /* Special case debug strings for strings */
    if (JSVAL_IS_STRING(value)) {
        return gjs_string_readable(context, JSVAL_TO_STRING(value));
    }

    JS_BeginRequest(context);

    str = JS_ValueToString(context, value);

    if (str == NULL) {
        if (JSVAL_IS_OBJECT(value)) {
            /* Specifically the Call object (see jsfun.c in spidermonkey)
             * does not have a toString; there may be others also.
             */
            JSClass *klass;

            klass = JS_GetClass(JSVAL_TO_OBJECT(value));
            if (klass != NULL) {
                str = JS_NewStringCopyZ(context, klass->name);
                JS_ClearPendingException(context);
                if (str == NULL) {
                    JS_EndRequest(context);
                    return g_strdup("[out of memory copying class name]");
                }
            } else {
                gjs_log_exception(context);
                JS_EndRequest(context);
                return g_strdup("[unknown object]");
            }
        } else {
            JS_EndRequest(context);
            return g_strdup("[unknown non-object]");
        }
    }

    g_assert(str != NULL);

    size_t len = JS_GetStringEncodingLength(context, str);
    if (len != (size_t)(-1)) {
        bytes = g_malloc((len + 1) * sizeof(char));
        JS_EncodeStringToBuffer(str, bytes, len);
        bytes[len] = '\0';
    } else {
        bytes = g_strdup("[invalid string]");
    }
    JS_EndRequest(context);

    debugstr = _gjs_g_utf8_make_valid(bytes);
    g_free(bytes);

    return debugstr;
}

void
gjs_log_object_props(JSContext      *context,
                     JSObject       *obj,
                     GjsDebugTopic   topic,
                     const char     *prefix)
{
    JSObject *props_iter;
    jsid prop_id;

    JS_BeginRequest(context);

    /* We potentially create new strings, plus the property iterator,
     * that could get collected as we go through this process. So
     * create a local root scope.
     */
    (void)JS_EnterLocalRootScope(context);

    props_iter = JS_NewPropertyIterator(context, obj);
    if (props_iter == NULL) {
        gjs_log_exception(context);
        goto done;
    }

    prop_id = JSID_VOID;
    if (!JS_NextProperty(context, props_iter, &prop_id))
        goto done;

    while (!JSID_IS_VOID(prop_id)) {
        jsval propval;
        char *debugstr;
        char *name;

        if (!JS_GetPropertyById(context, obj, prop_id, &propval))
            goto next;

        if (!gjs_get_string_id(context, prop_id, &name))
            goto next;

        debugstr = gjs_value_debug_string(context, propval);
        gjs_debug(topic,
                  "%s%s = '%s'",
                  prefix, name,
                  debugstr);
        g_free(debugstr);

    next:
        g_free(name);
        prop_id = JSID_VOID;
        if (!JS_NextProperty(context, props_iter, &prop_id))
            break;
    }

 done:
    JS_LeaveLocalRootScope(context);
    JS_EndRequest(context);
}

void
gjs_explain_scope(JSContext  *context,
                  const char *title)
{
    JSObject *global;
    JSObject *parent;
    GString *chain;
    char *debugstr;

    gjs_debug(GJS_DEBUG_SCOPE,
              "=== %s ===",
              title);

    JS_BeginRequest(context);

    (void)JS_EnterLocalRootScope(context);

    gjs_debug(GJS_DEBUG_SCOPE,
              "  Context: %p %s",
              context,
              "");

    global = JS_GetGlobalObject(context);
    debugstr = gjs_value_debug_string(context, OBJECT_TO_JSVAL(global));
    gjs_debug(GJS_DEBUG_SCOPE,
              "  Global: %p %s",
              global, debugstr);
    g_free(debugstr);

    parent = JS_GetGlobalForScopeChain(context);
    chain = g_string_new(NULL);
    while (parent != NULL) {
        char *debug;
        debug = gjs_value_debug_string(context, OBJECT_TO_JSVAL(parent));

        if (chain->len > 0)
            g_string_append(chain, ", ");

        g_string_append_printf(chain, "%p %s",
                               parent, debug);
        g_free(debug);
        parent = JS_GetParent(parent);
    }
    gjs_debug(GJS_DEBUG_SCOPE,
              "  Chain: %s",
              chain->str);
    g_string_free(chain, TRUE);

    JS_LeaveLocalRootScope(context);

    JS_EndRequest(context);
}

JSBool
gjs_log_exception_full(JSContext *context,
                       jsval      exc,
                       JSString  *message)
{
    jsval stack;
    JSString *exc_str;
    char *utf8_exception, *utf8_message, *utf8_stack;

    JS_BeginRequest(context);

    exc_str = JS_ValueToString(context, exc);
    if (exc_str != NULL)
        gjs_string_to_utf8(context, STRING_TO_JSVAL(exc_str), &utf8_exception);
    else
        utf8_exception = NULL;

    if (message != NULL)
        gjs_string_to_utf8(context, STRING_TO_JSVAL(message), &utf8_message);
    else
        utf8_message = NULL;

    if (JSVAL_IS_OBJECT(exc) &&
        gjs_object_get_property_const(context, JSVAL_TO_OBJECT(exc),
                                      GJS_STRING_STACK, &stack) &&
        JSVAL_IS_STRING(stack))
        gjs_string_to_utf8(context, stack, &utf8_stack);
    else
        utf8_stack = NULL;

    if (utf8_message) {
        if (utf8_stack)
            g_warning("JS ERROR: %s: %s\n%s", utf8_message, utf8_exception, utf8_stack);
        else
            g_warning("JS ERROR: %s: %s", utf8_message, utf8_exception);
    } else {
        if (utf8_stack)
            g_warning("JS ERROR: %s\n%s", utf8_exception, utf8_stack);
        else
            g_warning("JS ERROR: %s", utf8_exception);
    }

    g_free(utf8_exception);
    g_free(utf8_message);
    g_free(utf8_stack);

    return JS_TRUE;
}

static JSBool
log_and_maybe_keep_exception(JSContext  *context,
                             gboolean    keep)
{
    jsval exc = JSVAL_VOID;
    JSBool retval = JS_FALSE;

    JS_BeginRequest(context);

    JS_AddValueRoot(context, &exc);
    if (!JS_GetPendingException(context, &exc))
        goto out;

    JS_ClearPendingException(context);

    gjs_log_exception_full(context, exc, NULL);

    /* We clear above and then set it back so any exceptions
     * from the logging process don't overwrite the original
     */
    if (keep)
        JS_SetPendingException(context, exc);
<commit_msg>util: Seal up a missing EndRequest<commit_after>/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
 * Copyright (c) 2008  litl, LLC
 * Copyright (c) 2009 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * 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.
 */

#include <config.h>

#include <util/log.h>
#include <util/glib.h>
#include <util/misc.h>

#include "jsapi-util.h"
#include "compat.h"
#include "jsapi-private.h"
#include "runtime.h"

#include <string.h>
#include <math.h>

static GMutex gc_lock;

GQuark
gjs_util_error_quark (void)
{
    return g_quark_from_static_string ("gjs-util-error-quark");
}

/**
 * gjs_get_import_global:
 * @context: a #JSContext
 *
 * Gets the "import global" for the context's runtime. The import
 * global object is the global object for the context. It is used
 * as the root object for the scope of modules loaded by GJS in this
 * runtime, and should also be used as the globals 'obj' argument passed
 * to JS_InitClass() and the parent argument passed to JS_ConstructObject()
 * when creating a native classes that are shared between all contexts using
 * the runtime. (The standard JS classes are not shared, but we share
 * classes such as GObject proxy classes since objects of these classes can
 * easily migrate between contexts and having different classes depending
 * on the context where they were first accessed would be confusing.)
 *
 * Return value: the "import global" for the context's
 *  runtime. Will never return %NULL while GJS has an active context
 *  for the runtime.
 */
JSObject*
gjs_get_import_global(JSContext *context)
{
    return JS_GetGlobalObject(context);
}

static JSClass global_class = {
    "GjsGlobal", JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(GJS_GLOBAL_SLOT_LAST),
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
    NULL /* checkAccess */, NULL /* call */, NULL /* hasInstance */, NULL /* construct */, NULL,
    { NULL }
};

/**
 * gjs_init_context_standard:
 * @context: a #JSContext
 *
 * This function creates a default global object for @context,
 * and calls JS_InitStandardClasses using it.
 *
 * Returns: %TRUE on success, %FALSE otherwise
 */
gboolean
gjs_init_context_standard (JSContext       *context)
{
    JSObject *global;

    global = JS_NewGlobalObject(context, &global_class, NULL);
    if (global == NULL)
        return FALSE;
    if (!JS_InitStandardClasses(context, global))
        return FALSE;

    return TRUE;
}

void
gjs_set_global_slot (JSContext     *context,
                     GjsGlobalSlot  slot,
                     jsval          value)
{
    JSObject *global;
    global = JS_GetGlobalObject(context);
    JS_SetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot, value);
}

jsval
gjs_get_global_slot (JSContext     *context,
                     GjsGlobalSlot  slot)
{
    JSObject *global;
    global = JS_GetGlobalObject(context);
    return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
}

/* Returns whether the object had the property; if the object did
 * not have the property, always sets an exception. Treats
 * "the property's value is JSVAL_VOID" the same as "no such property,".
 * Guarantees that *value_p is set to something, if only JSVAL_VOID,
 * even if an exception is set and false is returned.
 *
 * Requires request.
 */
gboolean
gjs_object_require_property(JSContext       *context,
                            JSObject        *obj,
                            const char      *obj_description,
                            jsid             property_name,
                            jsval           *value_p)
{
    jsval value;
    char *name;

    value = JSVAL_VOID;
    if (value_p)
        *value_p = value;

    if (G_UNLIKELY (!JS_GetPropertyById(context, obj, property_name, &value)))
        return JS_FALSE;

    if (G_LIKELY (!JSVAL_IS_VOID(value))) {
        if (value_p)
            *value_p = value;
        return JS_TRUE;
    }

    /* remember gjs_throw() is a no-op if JS_GetProperty()
     * already set an exception
     */

    gjs_get_string_id(context, property_name, &name);

    if (obj_description)
        gjs_throw(context,
                  "No property '%s' in %s (or its value was undefined)",
                  name, obj_description);
    else
        gjs_throw(context,
                  "No property '%s' in object %p (or its value was undefined)",
                  name, obj);

    g_free(name);
    return JS_FALSE;
}

void
gjs_throw_constructor_error(JSContext *context)
{
    gjs_throw(context,
              "Constructor called as normal method. Use 'new SomeObject()' not 'SomeObject()'");
}

void
gjs_throw_abstract_constructor_error(JSContext *context,
                                     jsval     *vp)
{
    jsval callee;
    jsval prototype;
    JSClass *proto_class;
    const char *name = "anonymous";

    callee = JS_CALLEE(context, vp);

    if (JSVAL_IS_OBJECT(callee)) {
        if (gjs_object_get_property_const(context, JSVAL_TO_OBJECT(callee),
                                          GJS_STRING_PROTOTYPE, &prototype)) {
            proto_class = JS_GetClass(JSVAL_TO_OBJECT(prototype));
            name = proto_class->name;
        }
    }

    gjs_throw(context, "You cannot construct new instances of '%s'", name);
}

JSObject*
gjs_define_string_array(JSContext   *context,
                        JSObject    *in_object,
                        const char  *array_name,
                        gssize       array_length,
                        const char **array_values,
                        unsigned     attrs)
{
    GArray *elems;
    JSObject *array;
    int i;

    JS_BeginRequest(context);

    if (!JS_EnterLocalRootScope(context)) {
        JS_EndRequest(context);
        return JS_FALSE;
    }

    if (array_length == -1)
        array_length = g_strv_length((char**)array_values);

    elems = g_array_sized_new(FALSE, FALSE, sizeof(jsval), array_length);

    for (i = 0; i < array_length; ++i) {
        jsval element;
        element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, array_values[i]));
        g_array_append_val(elems, element);
    }

    array = JS_NewArrayObject(context, elems->len, (jsval*) elems->data);
    g_array_free(elems, TRUE);

    if (array != NULL) {
        if (!JS_DefineProperty(context, in_object,
                               array_name, OBJECT_TO_JSVAL(array),
                               NULL, NULL, attrs))
            array = NULL;
    }

    JS_LeaveLocalRootScope(context);

    JS_EndRequest(context);
    return array;
}

/**
 * gjs_string_readable:
 *
 * Return a string that can be read back by gjs-console; for
 * JS strings that contain valid Unicode, we return a UTF-8 formatted
 * string.  Otherwise, we return one where non-ASCII-printable bytes
 * are \x escaped.
 *
 */
static char *
gjs_string_readable (JSContext   *context,
                     JSString    *string)
{
    GString *buf = g_string_new("");
    char *chars;

    JS_BeginRequest(context);

    g_string_append_c(buf, '"');

    if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(string), &chars)) {
        size_t i, len;
        const jschar *uchars;

        uchars = JS_GetStringCharsAndLength(context, string, &len);

        for (i = 0; i < len; i++) {
            jschar c = uchars[i];
            if (c >> 8 == 0 && g_ascii_isprint(c & 0xFF))
                g_string_append_c(buf, c & 0xFF);
            else
                g_string_append_printf(buf, "\\u%04X", c);
        }
    } else {
        g_string_append(buf, chars);
        g_free(chars);
    }

    g_string_append_c(buf, '"');

    JS_EndRequest(context);

    return g_string_free(buf, FALSE);
}

/**
 * gjs_value_debug_string:
 * @context:
 * @value: Any JavaScript value
 *
 * Returns: A UTF-8 encoded string describing @value
 */
char*
gjs_value_debug_string(JSContext      *context,
                       jsval           value)
{
    JSString *str;
    char *bytes;
    char *debugstr;

    /* Special case debug strings for strings */
    if (JSVAL_IS_STRING(value)) {
        return gjs_string_readable(context, JSVAL_TO_STRING(value));
    }

    JS_BeginRequest(context);

    str = JS_ValueToString(context, value);

    if (str == NULL) {
        if (JSVAL_IS_OBJECT(value)) {
            /* Specifically the Call object (see jsfun.c in spidermonkey)
             * does not have a toString; there may be others also.
             */
            JSClass *klass;

            klass = JS_GetClass(JSVAL_TO_OBJECT(value));
            if (klass != NULL) {
                str = JS_NewStringCopyZ(context, klass->name);
                JS_ClearPendingException(context);
                if (str == NULL) {
                    JS_EndRequest(context);
                    return g_strdup("[out of memory copying class name]");
                }
            } else {
                gjs_log_exception(context);
                JS_EndRequest(context);
                return g_strdup("[unknown object]");
            }
        } else {
            JS_EndRequest(context);
            return g_strdup("[unknown non-object]");
        }
    }

    g_assert(str != NULL);

    size_t len = JS_GetStringEncodingLength(context, str);
    if (len != (size_t)(-1)) {
        bytes = g_malloc((len + 1) * sizeof(char));
        JS_EncodeStringToBuffer(str, bytes, len);
        bytes[len] = '\0';
    } else {
        bytes = g_strdup("[invalid string]");
    }
    JS_EndRequest(context);

    debugstr = _gjs_g_utf8_make_valid(bytes);
    g_free(bytes);

    return debugstr;
}

void
gjs_log_object_props(JSContext      *context,
                     JSObject       *obj,
                     GjsDebugTopic   topic,
                     const char     *prefix)
{
    JSObject *props_iter;
    jsid prop_id;

    JS_BeginRequest(context);

    /* We potentially create new strings, plus the property iterator,
     * that could get collected as we go through this process. So
     * create a local root scope.
     */
    (void)JS_EnterLocalRootScope(context);

    props_iter = JS_NewPropertyIterator(context, obj);
    if (props_iter == NULL) {
        gjs_log_exception(context);
        goto done;
    }

    prop_id = JSID_VOID;
    if (!JS_NextProperty(context, props_iter, &prop_id))
        goto done;

    while (!JSID_IS_VOID(prop_id)) {
        jsval propval;
        char *debugstr;
        char *name;

        if (!JS_GetPropertyById(context, obj, prop_id, &propval))
            goto next;

        if (!gjs_get_string_id(context, prop_id, &name))
            goto next;

        debugstr = gjs_value_debug_string(context, propval);
        gjs_debug(topic,
                  "%s%s = '%s'",
                  prefix, name,
                  debugstr);
        g_free(debugstr);

    next:
        g_free(name);
        prop_id = JSID_VOID;
        if (!JS_NextProperty(context, props_iter, &prop_id))
            break;
    }

 done:
    JS_LeaveLocalRootScope(context);
    JS_EndRequest(context);
}

void
gjs_explain_scope(JSContext  *context,
                  const char *title)
{
    JSObject *global;
    JSObject *parent;
    GString *chain;
    char *debugstr;

    gjs_debug(GJS_DEBUG_SCOPE,
              "=== %s ===",
              title);

    JS_BeginRequest(context);

    (void)JS_EnterLocalRootScope(context);

    gjs_debug(GJS_DEBUG_SCOPE,
              "  Context: %p %s",
              context,
              "");

    global = JS_GetGlobalObject(context);
    debugstr = gjs_value_debug_string(context, OBJECT_TO_JSVAL(global));
    gjs_debug(GJS_DEBUG_SCOPE,
              "  Global: %p %s",
              global, debugstr);
    g_free(debugstr);

    parent = JS_GetGlobalForScopeChain(context);
    chain = g_string_new(NULL);
    while (parent != NULL) {
        char *debug;
        debug = gjs_value_debug_string(context, OBJECT_TO_JSVAL(parent));

        if (chain->len > 0)
            g_string_append(chain, ", ");

        g_string_append_printf(chain, "%p %s",
                               parent, debug);
        g_free(debug);
        parent = JS_GetParent(parent);
    }
    gjs_debug(GJS_DEBUG_SCOPE,
              "  Chain: %s",
              chain->str);
    g_string_free(chain, TRUE);

    JS_LeaveLocalRootScope(context);

    JS_EndRequest(context);
}

JSBool
gjs_log_exception_full(JSContext *context,
                       jsval      exc,
                       JSString  *message)
{
    jsval stack;
    JSString *exc_str;
    char *utf8_exception, *utf8_message, *utf8_stack;

    JS_BeginRequest(context);

    exc_str = JS_ValueToString(context, exc);
    if (exc_str != NULL)
        gjs_string_to_utf8(context, STRING_TO_JSVAL(exc_str), &utf8_exception);
    else
        utf8_exception = NULL;

    if (message != NULL)
        gjs_string_to_utf8(context, STRING_TO_JSVAL(message), &utf8_message);
    else
        utf8_message = NULL;

    if (JSVAL_IS_OBJECT(exc) &&
        gjs_object_get_property_const(context, JSVAL_TO_OBJECT(exc),
                                      GJS_STRING_STACK, &stack) &&
        JSVAL_IS_STRING(stack))
        gjs_string_to_utf8(context, stack, &utf8_stack);
    else
        utf8_stack = NULL;

    if (utf8_message) {
        if (utf8_stack)
            g_warning("JS ERROR: %s: %s\n%s", utf8_message, utf8_exception, utf8_stack);
        else
            g_warning("JS ERROR: %s: %s", utf8_message, utf8_exception);
    } else {
        if (utf8_stack)
            g_warning("JS ERROR: %s\n%s", utf8_exception, utf8_stack);
        else
            g_warning("JS ERROR: %s", utf8_exception);
    }

    g_free(utf8_exception);
    g_free(utf8_message);
    g_free(utf8_stack);

    JS_EndRequest(context);

    return JS_TRUE;
}

static JSBool
log_and_maybe_keep_exception(JSContext  *context,
                             gboolean    keep)
{
    jsval exc = JSVAL_VOID;
    JSBool retval = JS_FALSE;

    JS_BeginRequest(context);

    JS_AddValueRoot(context, &exc);
    if (!JS_GetPendingException(context, &exc))
        goto out;

    JS_ClearPendingException(context);

    gjs_log_exception_full(context, exc, NULL);

    /* We clear above and then set it back so any exceptions
     * from the logging process don't overwrite the original
     */
    if (keep)
        JS_SetPendingException(context, exc);

{'fraction_non_alphanumeric': 0.09626680590001305, 'fraction_numerical': 0.003393812818169952, 'mean_word_length': 2.392185078591986, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7010162', 'n_tokens_mistral': 9585, 'n_tokens_neox': 9307, 'n_words': 2478}