Commit 6b25ce3b authored by Philip Chimento's avatar Philip Chimento

js: Call JS_Init() and JS_ShutDown()

Starting with mozjs31, JS_Init() is required. Calling JS_ShutDown() on
exit is not required, but may become so in the future.

This does so in the constructor and destructor of a static object.
Normally this is discouraged because the order in which the constructors
and destructors are called is not guaranteed, but I don't think that is a
problem here since it's unlikely that anyone will try to use GJS API from
a static constructor.

However, API clients still must unref any GjsContext before the program
shuts down. Usually this is not a problem, unless a JS script calls
System.exit(), in which case the code would exit immediately without
unreffing the GjsContext before JS_ShutDown was called. Instead, we make
System.exit() throw an uncatchable exception, which is propagated all the
way up to JS::Evaluate() and turned into a GError with code
GJS_ERROR_SYSTEM_EXIT, thrown from gjs_context_eval().

For this, we need to expose GjsError and its error codes in the public
API. (They should have been exposed already, because gjs_context_eval()
could already throw GJS_ERROR_FAILED.)

Finally, the tests added as part of this patch made the testSystemExit
script obsolete.

https://bugzilla.gnome.org/show_bug.cgi?id=751252
parent bee68349
......@@ -4,7 +4,7 @@ EXTRA_DIST += \
installed-tests/js/jsunit.gresources.xml \
$(NULL)
MAINTAINERCLEANFILES += jsunit.test testSystemExit.test
MAINTAINERCLEANFILES += jsunit.test
gjsinsttestdir = $(pkglibexecdir)/installed-tests
installedtestmetadir = $(datadir)/installed-tests/gjs
......@@ -22,9 +22,8 @@ if BUILDOPT_INSTALL_TESTS
gjsinsttest_PROGRAMS += jsunit
gjsinsttest_DATA += $(TEST_INTROSPECTION_TYPELIBS)
installedtestmeta_DATA += jsunit.test testSystemExit.test
installedtestmeta_DATA += jsunit.test
jstests_DATA += $(common_jstests_files)
jsscripttests_DATA += installed-tests/scripts/testSystemExit.js
pkglib_LTLIBRARIES += libregress.la libwarnlib.la libgimarshallingtests.la
if ENABLE_CAIRO
......
......@@ -269,7 +269,6 @@ EXTRA_DIST += \
installed-tests/js/testCairo.js \
installed-tests/js/testGtk.js \
installed-tests/js/testGDBus.js \
installed-tests/scripts/testSystemExit.js \
$(NULL)
### TEST EXECUTION #####################################################
......
......@@ -37,7 +37,9 @@ gjs_public_includedir = $(includedir)/gjs-1.0
########################################################################
nobase_gjs_public_include_HEADERS = \
gjs/context.h \
gjs/gjs.h
gjs/gjs.h \
util/error.h \
$(NULL)
########################################################################
pkgconfig_DATA = gjs-1.0.pc
......@@ -96,7 +98,6 @@ libgjs_la_SOURCES = \
modules/modules.cpp \
modules/modules.h \
util/error.cpp \
util/error.h \
util/hash-x32.cpp \
util/hash-x32.h \
util/glib.cpp \
......
......@@ -103,6 +103,23 @@ NEXT
arguments into account for the time being, while still passing them on to the
script. A warning will be logged if you are using the deprecated behaviour.
- News for GJS embedders such as gnome-shell:
* New API: gjs_error_quark() is now exposed, and the error domain GJS_ERROR
and codes GJS_ERROR_FAILED and GJS_ERROR_SYSTEM_EXIT.
* Backwards-incompatible change: Calling System.exit() from JS code will now
not abort the program immediately, but instead will return immediately from
gjs_context_eval() so that you can unref your GjsContext before internal
resources are released on exit.
If gjs_context_eval() or gjs_context_eval_file() returns an error with code
GJS_ERROR_SYSTEM_EXIT, it means that the JS code called System.exit(). The
exit code will be found in the 'exit_status_p' out parameter to
gjs_context_eval() or gjs_context_eval_file(). If you receive this error,
you should do any cleanup needed and exit your program with the given exit
code.
Version 1.46.0
--------------
......
......@@ -286,8 +286,8 @@ main(int argc, char **argv)
/* evaluate the script */
if (!gjs_context_eval(js_context, script, len,
filename, &code, &error)) {
code = 1;
g_printerr("%s\n", error->message);
if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
g_printerr("%s\n", error->message);
g_clear_error(&error);
goto out;
}
......
......@@ -37,9 +37,6 @@ void _gjs_context_schedule_gc_if_needed (GjsContext *js_context);
void _gjs_context_exit(GjsContext *js_context,
uint8_t exit_code);
bool _gjs_context_should_exit(GjsContext *js_context,
uint8_t *exit_code_p);
G_END_DECLS
#endif /* __GJS_CONTEXT_PRIVATE_H__ */
......@@ -568,15 +568,22 @@ _gjs_context_exit(GjsContext *js_context,
js_context->exit_code = exit_code;
}
bool
_gjs_context_should_exit(GjsContext *js_context,
uint8_t *exit_code_p)
static bool
context_should_exit(GjsContext *js_context,
uint8_t *exit_code_p)
{
if (exit_code_p != NULL)
*exit_code_p = js_context->exit_code;
return js_context->should_exit;
}
static void
context_reset_exit(GjsContext *js_context)
{
js_context->should_exit = false;
js_context->exit_code = 0;
}
/**
* gjs_context_maybe_gc:
* @context: a #GjsContext
......@@ -671,14 +678,22 @@ gjs_context_eval(GjsContext *js_context,
if (!gjs_eval_with_scope(js_context->context, JS::NullPtr(), script,
script_len, filename, &retval)) {
uint8_t code;
if (_gjs_context_should_exit(js_context, &code))
exit(code);
if (context_should_exit(js_context, &code)) {
/* exit_status_p is public API so can't be changed, but should be
* uint8_t, not int */
*exit_status_p = code;
g_set_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT,
"Exit with code %d", code);
goto out; /* Don't log anything */
}
gjs_log_exception(js_context->context);
g_set_error(error,
GJS_ERROR,
GJS_ERROR_FAILED,
"JS_EvaluateScript() failed");
/* No exit code from script, but we don't want to exit(0) */
*exit_status_p = 1;
goto out;
}
......@@ -698,6 +713,7 @@ gjs_context_eval(GjsContext *js_context,
out:
g_object_unref(G_OBJECT(js_context));
context_reset_exit(js_context);
return ret;
}
......
......@@ -25,5 +25,6 @@
#define __GJS_GJS_H__
#include <gjs/context.h>
#include <util/error.h>
#endif /* __GJS_GJS_H__ */
......@@ -222,6 +222,24 @@ gjs_finalize_callback(JSFreeOp *fop,
data->in_gc_sweep = false;
}
class GjsInit {
public:
GjsInit() {
if (!JS_Init())
g_error("Could not initialize Javascript");
}
~GjsInit() {
JS_ShutDown();
}
operator bool() {
return true;
}
};
static GjsInit gjs_is_inited;
JSRuntime *
gjs_runtime_for_current_thread(void)
{
......@@ -229,6 +247,7 @@ gjs_runtime_for_current_thread(void)
RuntimeData *data;
if (!runtime) {
g_assert(gjs_is_inited);
runtime = JS_NewRuntime(32*1024*1024 /* max bytes */, JS_USE_HELPER_THREADS);
if (runtime == NULL)
g_error("Failed to create javascript runtime");
......
const System = imports.system;
System.exit(0);
......@@ -53,6 +53,7 @@
#include <glib/gprintf.h>
#include "console.h"
#include "gjs/context.h"
#include "gjs/jsapi-private.h"
#include "gjs/jsapi-wrapper.h"
......@@ -195,7 +196,18 @@ gjs_console_interact(JSContext *context,
JS::CompileOptions options(context);
options.setUTF8(true)
.setFileAndLine("typein", startline);
JS::Evaluate(context, object, options, buffer->str, buffer->len, &result);
if (!JS::Evaluate(context, object, options, buffer->str, buffer->len,
&result)) {
/* If this was an uncatchable exception, throw another uncatchable
* exception on up to the surrounding JS::Evaluate() in main(). This
* happens when you run gjs-console and type imports.system.exit(0);
* at the prompt. If we don't throw another uncatchable exception
* here, then it's swallowed and main() won't exit. */
if (!JS_IsExceptionPending(context)) {
argv.rval().set(result);
return false;
}
}
gjs_schedule_gc_if_needed(context);
......
......@@ -30,6 +30,7 @@
#include "gjs/jsapi-util.h"
#include "gjs/jsapi-wrapper.h"
#include "gjs-test-utils.h"
#include "util/error.h"
static void
gjstest_test_func_gjs_context_construct_destroy(void)
......@@ -59,6 +60,31 @@ gjstest_test_func_gjs_context_construct_eval(void)
g_object_unref (context);
}
static void
gjstest_test_func_gjs_context_exit(void)
{
GjsContext *context = gjs_context_new();
GError *error = NULL;
int status;
bool ok = gjs_context_eval(context, "imports.system.exit(0);", -1,
"<input>", &status, &error);
g_assert_false(ok);
g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT);
g_assert_cmpuint(status, ==, 0);
g_clear_error(&error);
ok = gjs_context_eval(context, "imports.system.exit(42);", -1, "<input>",
&status, &error);
g_assert_false(ok);
g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT);
g_assert_cmpuint(status, ==, 42);
g_clear_error(&error);
g_object_unref(context);
}
#define JS_CLASS "\
const Lang = imports.lang; \
const GObject = imports.gi.GObject; \
......@@ -254,6 +280,7 @@ main(int argc,
g_test_add_func("/gjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy);
g_test_add_func("/gjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval);
g_test_add_func("/gjs/context/exit", gjstest_test_func_gjs_context_exit);
g_test_add_func("/gjs/gobject/js_defined_type", gjstest_test_func_gjs_gobject_js_defined_type);
g_test_add_func("/gjs/jsutil/strip_shebang/no_shebang", gjstest_test_strip_shebang_no_advance_for_no_shebang);
g_test_add_func("/gjs/jsutil/strip_shebang/have_shebang", gjstest_test_strip_shebang_advance_for_shebang);
......
......@@ -35,6 +35,13 @@ report_xfail () {
fi
}
# Test that System.exit() works in gjs-console
"$gjs" -c 'imports.system.exit(0)' || \
fail "System.exit(0) should exit successfully"
if "gjs" -c 'imports.system.exit(42)' -ne 42; then
fail "System.exit(42) should exit with the correct exit code"
fi
# gjs --help prints GJS help
"$gjs" --help >/dev/null
report "--help should succeed"
......
......@@ -32,7 +32,8 @@ GQuark gjs_error_quark(void);
#define GJS_ERROR gjs_error_quark()
typedef enum {
GJS_ERROR_FAILED
GJS_ERROR_FAILED,
GJS_ERROR_SYSTEM_EXIT,
} GjsError;
G_END_DECLS
......
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