diff -Nru gjs-1.76.2/build/maintainer-upload-release.sh gjs-1.78.0/build/maintainer-upload-release.sh --- gjs-1.76.2/build/maintainer-upload-release.sh 1970-01-01 00:00:00.000000000 +0000 +++ gjs-1.78.0/build/maintainer-upload-release.sh 2023-09-17 02:27:20.000000000 +0000 @@ -0,0 +1,57 @@ +#!/bin/bash + +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: Will Thompson + +# Automate the release process for stable branches. +# https://blogs.gnome.org/wjjt/2022/06/07/release-semi-automation +# gnome-initial-setup/build-aux/maintainer-upload-release + +set -ex +: "${MESON_BUILD_ROOT:?}" +: "${MESON_SOURCE_ROOT:?}" +project_name="${1:?project name is required}" +project_version="${2:?project version is required}" +tarball_basename="${project_name}-${project_version}.tar.xz" +tarball_path="${MESON_BUILD_ROOT}/meson-dist/${tarball_basename}" +[[ -e "$tarball_path" ]] # ninja dist must have been successful + +# Don't forget to write release notes +head -n1 "${MESON_SOURCE_ROOT}/NEWS" | grep "$project_version" + +case $project_version in + 1.7[12].*) gnome_series=42 ;; + 1.7[34].*) gnome_series=43 ;; + 1.7[56].*) gnome_series=44 ;; + 1.7[78].*) gnome_series=45 ;; + *) + echo "Version $project_version not handled by this script" + exit 1 + ;; +esac +expected_branch=gnome-${gnome_series} + +pushd "$MESON_SOURCE_ROOT" + branch=$(git rev-parse --abbrev-ref HEAD) + if [[ "$branch" != "master" ]] && [[ "$branch" != "$expected_branch" ]]; then + echo "Project version $project_version does not match branch $branch" >&2 + exit 1 + fi + if git show-ref --tags "$project_version" --quiet; then + # Tag already exists; verify that it points to HEAD + [ "$(git rev-parse "$project_version"^{})" = "$(git rev-parse HEAD)" ] + else + if type git-evtag &>/dev/null; then + # Can't specify tag message on command line + # https://github.com/cgwalters/git-evtag/issues/9 + EDITOR=true git evtag sign "$project_version" + else + git tag -s "$project_version" -m "Version $project_version" + fi + fi + git push --atomic origin "$branch" "$project_version" +popd + +scp "$tarball_path" "master.gnome.org:" +# shellcheck disable=SC2029 +ssh -t "master.gnome.org" ftpadmin install "$tarball_basename" diff -Nru gjs-1.76.2/debian/changelog gjs-1.78.0/debian/changelog --- gjs-1.76.2/debian/changelog 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/changelog 2023-09-26 17:21:46.000000000 +0000 @@ -1,3 +1,12 @@ +gjs (1.78.0-0ubuntu1) mantic; urgency=medium + + * New upstream release + * Switch from mozjs102 to 115 (LP: #2034276) + * Drop all patches: applied in new release + * debian/libgjs0g.symbols: Add new symbol + + -- Jeremy Bícha Tue, 26 Sep 2023 13:21:46 -0400 + gjs (1.76.2-4) unstable; urgency=medium * Team upload diff -Nru gjs-1.76.2/debian/control gjs-1.78.0/debian/control --- gjs-1.76.2/debian/control 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/control 2023-09-26 17:21:46.000000000 +0000 @@ -5,7 +5,8 @@ Source: gjs Section: interpreters Priority: optional -Maintainer: Debian GNOME Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian GNOME Maintainers Uploaders: Jeremy Bícha , Marco Trevisan (Treviño) Build-Depends: debhelper-compat (= 13), dh-sequence-gir, @@ -17,7 +18,7 @@ libgirepository1.0-dev (>= 1.71), gir1.2-gtk-3.0, gobject-introspection (>= 1.71), - libmozjs-102-dev, + libmozjs-115-dev, libreadline-dev, meson (>= 0.54.0), dbus-daemon , @@ -89,7 +90,7 @@ libgjs0g (= ${binary:Version}), libgirepository1.0-dev (>= 1.64), libcairo2-dev, - libmozjs-102-dev, + libmozjs-115-dev, Description: Mozilla-based javascript bindings for the GNOME platform Makes it possible for applications to use all of GNOME's platform libraries using the JavaScript language. It's mainly based on the diff -Nru gjs-1.76.2/debian/control.in gjs-1.78.0/debian/control.in --- gjs-1.76.2/debian/control.in 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/control.in 2023-09-26 17:21:46.000000000 +0000 @@ -1,7 +1,8 @@ Source: gjs Section: interpreters Priority: optional -Maintainer: Debian GNOME Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian GNOME Maintainers Uploaders: @GNOME_TEAM@ Build-Depends: debhelper-compat (= 13), dh-sequence-gir, @@ -13,7 +14,7 @@ libgirepository1.0-dev (>= 1.71), gir1.2-gtk-3.0, gobject-introspection (>= 1.71), - libmozjs-102-dev, + libmozjs-115-dev, libreadline-dev, meson (>= 0.54.0), dbus-daemon , @@ -85,7 +86,7 @@ libgjs0g (= ${binary:Version}), libgirepository1.0-dev (>= 1.64), libcairo2-dev, - libmozjs-102-dev, + libmozjs-115-dev, Description: Mozilla-based javascript bindings for the GNOME platform Makes it possible for applications to use all of GNOME's platform libraries using the JavaScript language. It's mainly based on the diff -Nru gjs-1.76.2/debian/gbp.conf gjs-1.78.0/debian/gbp.conf --- gjs-1.76.2/debian/gbp.conf 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/gbp.conf 2023-09-26 17:21:46.000000000 +0000 @@ -1,6 +1,6 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = debian/latest upstream-branch = upstream/latest upstream-vcs-tag = %(version)s diff -Nru gjs-1.76.2/debian/libgjs0g.symbols gjs-1.78.0/debian/libgjs0g.symbols --- gjs-1.76.2/debian/libgjs0g.symbols 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/libgjs0g.symbols 2023-09-26 17:21:46.000000000 +0000 @@ -22,6 +22,7 @@ gjs_context_new_with_search_path@Base 1.63.90 gjs_context_print_stack_stderr@Base 1.63.90 gjs_context_register_module@Base 1.67.2 + gjs_context_run_in_realm@Base 1.77.90 gjs_context_set_argv@Base 1.67.2 gjs_context_setup_debugger_console@Base 1.63.90 gjs_coverage_enable@Base 1.65.90 diff -Nru gjs-1.76.2/debian/patches/function-Always-initialize-callback-return-value.patch gjs-1.78.0/debian/patches/function-Always-initialize-callback-return-value.patch --- gjs-1.76.2/debian/patches/function-Always-initialize-callback-return-value.patch 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/patches/function-Always-initialize-callback-return-value.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -From: Sebastian Keller -Date: Thu, 16 Mar 2023 22:35:49 +0100 -Subject: function: Always initialize callback return value - -When callback_closure() exits early, for example due to being called -during GC, the return value would not be initialized. This value is -often non-zero. If the callback is a source func of an idle or a timeout -this would then get interpreted as G_SOURCE_CONTINUE and the same would -repeat in the next iteration. If this happens fast enough, this results -in the entire process being seemingly frozen while spamming the log with -error messages. - -To fix this always initialize the return value to 0 or a comparable -neutral value. - -Related: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1868 -Bug-Debian: https://bugs.debian.org/1034356 -Forwarded: https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/832 -Applied-upstream: 1.77.1, commit:c925d91e5d018f38b0f66d0ac592274d4b007efb ---- - gi/function.cpp | 18 ++++++++---------- - 1 file changed, 8 insertions(+), 10 deletions(-) - -diff --git a/gi/function.cpp b/gi/function.cpp -index 2c0ad67..9effe0a 100644 ---- a/gi/function.cpp -+++ b/gi/function.cpp -@@ -289,6 +289,14 @@ void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when, - void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { - GITypeInfo ret_type; - -+ // Fill in the result with some hopefully neutral value -+ g_callable_info_load_return_type(m_info, &ret_type); -+ if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) { -+ GIArgument argument = {}; -+ gjs_gi_argument_init_default(&ret_type, &argument); -+ set_return_ffi_arg_from_giargument(&ret_type, result, &argument); -+ } -+ - if (G_UNLIKELY(!is_valid())) { - warn_about_illegal_js_callback( - "during shutdown", -@@ -365,8 +373,6 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { - - JS::RootedValue rval(context); - -- g_callable_info_load_return_type(m_info, &ret_type); -- - if (!callback_closure_inner(context, this_object, gobj, &rval, args, - &ret_type, n_args, c_args_offset, result)) { - if (!JS_IsExceptionPending(context)) { -@@ -389,14 +395,6 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { - descr.c_str(), m_info.ns(), m_info.name()); - } - -- // Fill in the result with some hopefully neutral value -- if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) { -- GIArgument argument = {}; -- g_callable_info_load_return_type(m_info, &ret_type); -- gjs_gi_argument_init_default(&ret_type, &argument); -- set_return_ffi_arg_from_giargument(&ret_type, result, &argument); -- } -- - // If the callback has a GError** argument, then make a GError from the - // value that was thrown. Otherwise, log it as "uncaught" (critical - // instead of warning) diff -Nru gjs-1.76.2/debian/patches/series gjs-1.78.0/debian/patches/series --- gjs-1.76.2/debian/patches/series 2023-08-17 10:34:22.000000000 +0000 +++ gjs-1.78.0/debian/patches/series 2023-09-26 17:21:46.000000000 +0000 @@ -1 +0,0 @@ -function-Always-initialize-callback-return-value.patch diff -Nru gjs-1.76.2/doc/Hacking.md gjs-1.78.0/doc/Hacking.md --- gjs-1.76.2/doc/Hacking.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/doc/Hacking.md 2023-09-17 02:27:20.000000000 +0000 @@ -37,7 +37,7 @@ ## Dependencies GJS requires five other libraries to be installed: GLib, libffi, -gobject-introspection, SpiderMonkey (also called "mozjs102" on some +gobject-introspection, SpiderMonkey (also called "mozjs115" on some systems.) and the build tool Meson. The readline library is not required, but strongly recommended. We recommend installing your system's development packages for GLib, @@ -70,15 +70,15 @@ need to build it yourself. Install SpiderMonkey using your system's package manager instead: - +
Fedora - sudo dnf install mozjs102-devel + sudo dnf install mozjs115-devel
If you _are_ writing C++ code, then please build SpiderMonkey yourself @@ -86,7 +86,7 @@ This can save you time later when you submit your merge request, because the code will be checked using the debugging features. -To build SpiderMonkey, follow the instructions on [this page](https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr102/docs/Building%20SpiderMonkey.md) to download the source code and build the library. +To build SpiderMonkey, follow the instructions on [this page](https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr115/docs/Building%20SpiderMonkey.md) to download the source code and build the library. If you are using `-Dprefix` to build GJS into a different path, then make sure to use the same build prefix for SpiderMonkey with `--prefix`. @@ -94,11 +94,11 @@ To build GJS, change to your `gjs` directory, and run: ```sh -meson _build +meson setup _build ninja -C _build ``` -Add any options with `-D` arguments to the `meson _build` command. +Add any options with `-D` arguments to the `meson setup _build` command. For a list of available options, run `meson configure`. That's it! You can now run your build of gjs for testing and hacking with @@ -152,7 +152,7 @@ To see which GC zeal options are available: ```sh -JS_GC_ZEAL=-1 js102 +JS_GC_ZEAL=-1 js115 ``` We include three test setups, `extra_gc`, `pre_verify`, and @@ -201,7 +201,7 @@ To build GJS with support for the ASan and UBSan sanitizers, configure meson like this: ```sh -meson _build -Db_sanitize=address,undefined +meson setup _build -Db_sanitize=address,undefined ``` and then run the tests as normal. @@ -216,7 +216,7 @@ instrumentation enabled, run the test suite to collect the coverage data, and open the generated HTML report. -[embedder](https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr102/docs/Building%20SpiderMonkey.md) +[embedder](https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr115/docs/Building%20SpiderMonkey.md) ## Troubleshooting diff -Nru gjs-1.76.2/doc/Mapping.md gjs-1.78.0/doc/Mapping.md --- gjs-1.76.2/doc/Mapping.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/doc/Mapping.md 2023-09-17 02:27:20.000000000 +0000 @@ -183,7 +183,7 @@ ## GType Objects -> See also: [`GObject.Object.$gtype`][gobject-gtype] and +> See also: [`GObject.Object.$gtype`][gobject-object-gtype] and > [`GObject.registerClass()`][gobject-registerclass] This is the object that represents a type in the GObject type system. Internally @@ -228,7 +228,7 @@ log(true); ``` -[gobject-gtype]: https://gjs-docs.gnome.org/gjs/overrides.md#gobject-gtype +[gobject-object-gtype]: https://gjs-docs.gnome.org/gjs/overrides.md#gobject-object-gtype [gobject-registerclass]: https://gjs-docs.gnome.org/gjs/overrides.md#gobject-registerclass [mdn-instanceof]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/instanceof diff -Nru gjs-1.76.2/doc/Overrides.md gjs-1.78.0/doc/Overrides.md --- gjs-1.76.2/doc/Overrides.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/doc/Overrides.md 2023-09-17 02:27:20.000000000 +0000 @@ -200,6 +200,21 @@ } ``` +Note that for "finish" methods that normally return an array with a success +boolean, a wrapped function will automatically remove it from the return value: + +```js +Gio._promisify(Gio.File.prototype, 'load_contents_async', + 'load_contents_finish'); + +try { + const file = Gio.File.new_for_path('file.txt'); + const [contents, len, etag] = await file.load_contents_async(null); +} catch (e) { + logError(e, 'Failed to load file contents'); +} +``` + ### Gio.FileEnumerator[Symbol.asyncIterator] [Gio.FileEnumerator](gio-fileenumerator) are [async iterators](async-iterators). @@ -312,6 +327,19 @@ [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes [ginputstream]: https://gjs-docs.gnome.org/gio20/gio.inputstream +### Gio.Application.runAsync() + +Returns: +* (`Promise`) + +Similar to [`Gio.Application.run`][gio-application-run] but return a Promise which resolves when +the main loop ends, instead of blocking while the main loop runs. + +This helps avoid the situation where Promises never resolved if you didn't +run the application inside a callback. + +[gio-application-run]: https://gjs-docs.gnome.org/gio20~2.0/gio.application#method-run + ## [GLib](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/GLib.js) The `GLib` override includes a number of utilities and conveniences for working @@ -379,6 +407,18 @@ (e.g. `Number`), so some type information may not be fully represented in the result. +### GLib.MainLoop.runAsync() + +Returns: +* (`Promise`) + +Similar to [`GLib.MainLoop.run`][glib-mainloop-run] but return a Promise which resolves when +the main loop ends, instead of blocking while the main loop runs. + +This helps avoid the situation where Promises never resolved if you didn't +run the main loop inside a callback. + +[glib-mainloop-run]: https://gjs-docs.gnome.org/glib20/glib.mainloop#method-run ## [GObject](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/GObject.js) @@ -420,6 +460,12 @@ `Gjs_` (i.e. `Gjs_MyObject`), unless the `GTypeName` class property is specified when calling [`GObject.registerClass()`](#gobject-registerclass). +Some applications, notably GNOME Shell, may set +[`GObject.gtypeNameBasedOnJSPath`](#gobject-gtypenamebasedonjspath) to `true` +which changes the prefix from `Gjs_` to `Gjs_`. For example, the +GNOME Shell class `Notification` in `ui/messageTray.js` has the GType name +`Gjs_ui_messageTray_Notification`. + [gtypefromname]: https://gjs-docs.gnome.org/gobject20/gobject.type_from_name [gtype-objects]: https://gjs-docs.gnome.org/gjs/mapping.md#gtype-objects @@ -788,6 +834,29 @@ [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object +### GObject.gtypeNameBasedOnJSPath + +> Warning: This property is for advanced use cases. Never set this property in +> a GNOME Shell Extension, or a loadable script in a GJS application. + +Type: +* `Boolean` + +Flags: +* Read / Write + +The property controls the default prefix for the [GType name](#gtype-objects) of +a user-defined class, if not set manually. + +By default this property is set to `false`, and any class that does not define +`GTypeName` when calling [`GObject.registerClass()`](#gobject-registerclass) +will be assigned a GType name of `Gjs_`. + +If set to `true`, the prefix will include the import path, which can avoid +conflicts if the application has multiple modules containing classes with the +same name. For example, the GNOME Shell class `Notification` in +`ui/messageTray.js` has the GType name `Gjs_ui_messageTray_Notification`. + ## [Gtk](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/Gtk.js) diff -Nru gjs-1.76.2/doc/README.md gjs-1.78.0/doc/README.md --- gjs-1.76.2/doc/README.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/doc/README.md 2023-09-17 02:27:20.000000000 +0000 @@ -25,30 +25,31 @@ core GNOME APIs. The repository also has [code examples][gjs-examples] and thorough coverage of language features in the [test suite][gjs-tests]. -[GTK4 + GJS Book](https://rmnvgr.gitlab.io/gtk4-gjs-book/) is a start to finish +[GTK4 + GJS Book][gtk4-gjs-book] is a start to finish walkthrough for creating GTK4 applications with GJS. The [GNOME developer portal][gnome-developer] contains examples of a variety of GNOME technologies written GJS, alongside other languages you may know. +[Workbench] is a code sandbox for GJS, CSS and GTK. +It features live preview and a library of examples and demos. + [gjs-docs]: https://gjs-docs.gnome.org/ [gjs-examples]: https://gitlab.gnome.org/GNOME/gjs/tree/HEAD/examples [gjs-tests]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/installed-tests/js [gjs-guide]: https://gjs.guide -[gtk4-book]: https://rmnvgr.gitlab.io/gtk4-gjs-book/ +[gtk4-gjs-book]: https://rmnvgr.gitlab.io/gtk4-gjs-book/ [gnome-developer]: https://developer.gnome.org/ +[workbench]: https://apps.gnome.org/app/re.sonny.Workbench/ ## Applications GJS is a great option to write applications for the GNOME Desktop. The easiest way to get started is to use [GNOME Builder][gnome-builder], start a -new project and select `JavaScript` language. There is a also a -[package specification] and [template repository][template] available. +new project and select `JavaScript` language. [gnome-builder]: https://apps.gnome.org/app/org.gnome.Builder/ -[package specification]: https://gitlab.gnome.org/GNOME/gjs/-/blob/HEAD/doc/Package/Specification.md -[template]: https://github.com/gcampax/gtk-js-app Here is a non-exhaustive list of applications written in GJS: @@ -66,10 +67,10 @@ * [Oh My SVG](https://github.com/sonnyp/OhMySVG) * [Workbench](https://github.com/sonnyp/Workbench) * [GNOME Sound Recorder](https://gitlab.gnome.org/GNOME/gnome-sound-recorder) (TypeScript) +* [Zap](https://apps.gnome.org/app/fr.romainvigier.zap/) Others - * [Quick Lookup](https://github.com/johnfactotum/quick-lookup) * [Foliate](https://github.com/johnfactotum/foliate) * [Clapper](https://github.com/Rafostar/clapper/) @@ -83,6 +84,11 @@ * [Spiel](https://gitlab.gnome.org/feaneron/spiel) * [Retro](https://github.com/sonnyp/Retro) * [libportal test](https://github.com/flatpak/libportal/tree/main/portal-test/gtk4) +* [Sticky](https://github.com/vixalien/sticky) +* [Playhouse](https://github.com/sonnyp/Playhouse) +* [Flatpak Manifest Editor](https://gitlab.gnome.org/feaneron/flatpak-manifest-editor) +* [Forge Sparks](https://github.com/rafaelmardojai/forge-sparks) +* [Diccionario de la Lengua](https://codeberg.org/rafaelmardojai/diccionario-lengua) Archived diff -Nru gjs-1.76.2/doc/Signals.md gjs-1.78.0/doc/Signals.md --- gjs-1.76.2/doc/Signals.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/doc/Signals.md 2023-09-17 02:27:20.000000000 +0000 @@ -1,4 +1,4 @@ -## Signals +# Signals The `Signals` module provides a GObject-like signal framework for native JavaScript classes and objects. diff -Nru gjs-1.76.2/.eslintrc.yml gjs-1.78.0/.eslintrc.yml --- gjs-1.76.2/.eslintrc.yml 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/.eslintrc.yml 2023-09-17 02:27:20.000000000 +0000 @@ -30,6 +30,7 @@ - error - arrays: always-multiline objects: always-multiline + imports: always-multiline functions: never comma-spacing: - error diff -Nru gjs-1.76.2/examples/http-client.js gjs-1.78.0/examples/http-client.js --- gjs-1.76.2/examples/http-client.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/examples/http-client.js 2023-09-17 02:27:20.000000000 +0000 @@ -23,7 +23,7 @@ let data; try { - outputStream.splice_finish(outputStream, result); + outputStream.splice_finish(result); data = outputStream.steal_as_bytes(); } catch (err) { logError(err); diff -Nru gjs-1.76.2/gi/arg-cache.cpp gjs-1.78.0/gi/arg-cache.cpp --- gjs-1.76.2/gi/arg-cache.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/arg-cache.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -121,6 +121,14 @@ return false; } +// Overload operator| so that Visual Studio won't complain +// when converting unsigned char to GjsArgumentFlags +GjsArgumentFlags operator|( + GjsArgumentFlags const& v1, GjsArgumentFlags const& v2) { + return static_cast(std::underlying_type::type(v1) | + std::underlying_type::type(v2)); +} + namespace Gjs { namespace Arg { @@ -170,14 +178,6 @@ } }; -// Overload operator| so that Visual Studio won't complain -// when converting unsigned char to GjsArgumentFlags -GjsArgumentFlags operator|( - GjsArgumentFlags const& v1, GjsArgumentFlags const& v2) { - return static_cast(std::underlying_type::type(v1) | - std::underlying_type::type(v2)); -} - struct Positioned { void set_arg_pos(int pos) { g_assert(pos <= Argument::MAX_ARGS && @@ -197,11 +197,15 @@ struct Array : BasicType { uint8_t m_length_pos = 0; + GIDirection m_length_direction : 2; + + Array() : BasicType(), m_length_direction(GI_DIRECTION_IN) {} - void set_array_length(int pos, GITypeTag tag) { + void set_array_length(int pos, GITypeTag tag, GIDirection direction) { g_assert(pos >= 0 && pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); m_length_pos = pos; + m_length_direction = direction; m_tag = tag; } }; @@ -403,6 +407,13 @@ struct ReturnArray : ExplicitArrayOut { bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { + if (m_length_direction != GI_DIRECTION_OUT) { + gjs_throw(cx, + "Using different length argument direction for array %s" + "is not supported for out arrays", + m_arg_name); + return false; + } return GenericOut::in(cx, state, arg, value); }; }; @@ -722,6 +733,78 @@ GIArgument*) override; }; +struct ZeroTerminatedArrayInOut : GenericInOut { + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, + GIArgument* out_arg) override { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); + if (!gjs_g_argument_release_in_array(cx, transfer, &m_type_info, + original_out_arg)) + return false; + + transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; + return gjs_g_argument_release_out_array(cx, transfer, &m_type_info, + out_arg); + } +}; + +struct ZeroTerminatedArrayIn : GenericIn, Nullable { + bool out(JSContext*, GjsFunctionCallState*, GIArgument*, + JS::MutableHandleValue) override { + return skip(); + } + + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument*) override { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + + return gjs_g_argument_release_in_array(cx, transfer, &m_type_info, + in_arg); + } + + GjsArgumentFlags flags() const override { + return Argument::flags() | Nullable::flags(); + } +}; + +struct FixedSizeArrayIn : GenericIn { + bool out(JSContext*, GjsFunctionCallState*, GIArgument*, + JS::MutableHandleValue) override { + return skip(); + } + + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument*) override { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + + int size = g_type_info_get_array_fixed_size(&m_type_info); + return gjs_g_argument_release_in_array(cx, transfer, &m_type_info, size, + in_arg); + } +}; + +struct FixedSizeArrayInOut : GenericInOut { + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, + GIArgument* out_arg) override { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); + int size = g_type_info_get_array_fixed_size(&m_type_info); + if (!gjs_g_argument_release_in_array(cx, transfer, &m_type_info, size, + original_out_arg)) + return false; + + transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; + return gjs_g_argument_release_out_array(cx, transfer, &m_type_info, + size, out_arg); + } +}; + GJS_JSAPI_RETURN_CONVENTION bool NotIntrospectable::in(JSContext* cx, GjsFunctionCallState* state, GIArgument*, JS::HandleValue) { @@ -792,6 +875,13 @@ void* data; size_t length; + if (m_length_direction != GI_DIRECTION_INOUT && + m_length_direction != GI_DIRECTION_IN) { + gjs_throw(cx, "Using different length argument direction for array %s" + "is not supported for in arrays", m_arg_name); + return false; + } + if (!gjs_array_to_explicit_array(cx, value, &m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), &data, &length)) @@ -822,11 +912,13 @@ gjs_arg_unset(&state->out_cvalue(ix)); gjs_arg_unset(&state->inout_original_cvalue(ix)); } else { - state->out_cvalue(length_pos) = - state->inout_original_cvalue(length_pos) = - state->in_cvalue(length_pos); - gjs_arg_set(&state->in_cvalue(length_pos), - &state->out_cvalue(length_pos)); + if G_LIKELY (m_length_direction == GI_DIRECTION_INOUT) { + state->out_cvalue(length_pos) = + state->inout_original_cvalue(length_pos) = + state->in_cvalue(length_pos); + gjs_arg_set(&state->in_cvalue(length_pos), + &state->out_cvalue(length_pos)); + } state->out_cvalue(ix) = state->inout_original_cvalue(ix) = *arg; gjs_arg_set(arg, &state->out_cvalue(ix)); @@ -1346,7 +1438,13 @@ GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::MutableHandleValue value) { - GIArgument* length_arg = &(state->out_cvalue(m_length_pos)); + GIArgument* length_arg; + + if (m_length_direction != GI_DIRECTION_IN) + length_arg = &(state->out_cvalue(m_length_pos)); + else + length_arg = &(state->in_cvalue(m_length_pos)); + size_t length = gjs_g_argument_get_array_length(m_tag, length_arg); return gjs_value_from_explicit_array(cx, value, &m_type_info, m_transfer, @@ -1412,7 +1510,7 @@ bool ExplicitArrayInOut::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { - GIArgument* length_arg = &state->in_cvalue(m_length_pos); + GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_g_argument_get_array_length(m_tag, length_arg); // For inout, transfer refers to what we get back from the function; for @@ -1420,12 +1518,20 @@ // freeing it. GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); - if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg) && - !gjs_g_argument_release_in_array(cx, GI_TRANSFER_NOTHING, &m_type_info, - length, original_out_arg)) - return false; + // Due to https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/192 + // Here we've to guess what to do, but in general is "better" to leak than + // crash, so let's assume that in/out transfer is matching. + if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg)) { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + if (!gjs_g_argument_release_in_array(cx, transfer, &m_type_info, length, + original_out_arg)) + return false; + } - return gjs_g_argument_release_out_array(cx, m_transfer, &m_type_info, + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + return gjs_g_argument_release_out_array(cx, transfer, &m_type_info, length, out_arg); } @@ -1545,16 +1651,16 @@ #endif template -std::unique_ptr Argument::make(uint8_t index, const char* name, - GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args) { +GjsAutoCppPointer Argument::make(uint8_t index, const char* name, + GITypeInfo* type_info, GITransfer transfer, + GjsArgumentFlags flags, Args&&... args) { #ifdef GJS_DO_ARGUMENTS_SIZE_CHECK static_assert( sizeof(T) <= argument_maximum_size(), "Think very hard before increasing the size of Gjs::Arguments. " "One is allocated for every argument to every introspected function."); #endif - auto arg = std::make_unique(args...); + auto arg = new T(args...); if constexpr (ArgKind == Arg::Kind::INSTANCE) { g_assert(index == Argument::ABSENT && @@ -1590,10 +1696,6 @@ return arg; } -// Needed for unique_ptr with incomplete type -ArgsCache::ArgsCache() = default; -ArgsCache::~ArgsCache() = default; - bool ArgsCache::initialize(JSContext* cx, GICallableInfo* callable) { if (!callable) { gjs_throw(cx, "Invalid callable provided"); @@ -1624,38 +1726,35 @@ return false; } - m_args = std::make_unique(size); + m_args = new ArgumentPtr[size]{}; return true; } -void ArgsCache::clear() { - m_args.reset(); -} - template -T* ArgsCache::set_argument(uint8_t index, const char* name, - GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args) { - std::unique_ptr arg = Argument::make( +constexpr T* ArgsCache::set_argument(uint8_t index, const char* name, + GITypeInfo* type_info, GITransfer transfer, + GjsArgumentFlags flags, Args&&... args) { + GjsAutoCppPointer arg = Argument::make( index, name, type_info, transfer, flags, args...); - arg_get(index) = std::move(arg); + arg_get(index) = arg.release(); return static_cast(arg_get(index).get()); } template -T* ArgsCache::set_argument(uint8_t index, const char* name, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args) { +constexpr T* ArgsCache::set_argument(uint8_t index, const char* name, + GITransfer transfer, + GjsArgumentFlags flags, Args&&... args) { return set_argument(index, name, nullptr, transfer, flags, args...); } template -T* ArgsCache::set_argument_auto(Args&&... args) { +constexpr T* ArgsCache::set_argument_auto(Args&&... args) { return set_argument(std::forward(args)...); } template -T* ArgsCache::set_argument_auto(Tuple&& tuple, Args&&... args) { +constexpr T* ArgsCache::set_argument_auto(Tuple&& tuple, Args&&... args) { // TODO(3v1n0): Would be nice to have a simple way to check we're handling a // tuple return std::apply( @@ -1667,25 +1766,19 @@ } template -T* ArgsCache::set_return(GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags) { +constexpr T* ArgsCache::set_return(GITypeInfo* type_info, GITransfer transfer, + GjsArgumentFlags flags) { return set_argument(Argument::ABSENT, nullptr, type_info, transfer, flags); } template -T* ArgsCache::set_instance(GITransfer transfer, GjsArgumentFlags flags) { +constexpr T* ArgsCache::set_instance(GITransfer transfer, + GjsArgumentFlags flags) { return set_argument(Argument::ABSENT, nullptr, transfer, flags); } -Argument* ArgsCache::instance() const { - if (!m_is_method) - return nullptr; - - return arg_get().get(); -} - GType ArgsCache::instance_type() const { if (!m_is_method) return G_TYPE_NONE; @@ -1693,13 +1786,6 @@ return instance()->as_instance()->gtype(); } -Argument* ArgsCache::return_value() const { - if (!m_has_return) - return nullptr; - - return arg_get().get(); -} - GITypeInfo* ArgsCache::return_type() const { Argument* rval = return_value(); if (!rval) @@ -1708,7 +1794,7 @@ return const_cast(rval->as_return_value()->type_info()); } -void ArgsCache::set_skip_all(uint8_t index, const char* name) { +constexpr void ArgsCache::set_skip_all(uint8_t index, const char* name) { set_argument(index, name, GI_TRANSFER_NOTHING, GjsArgumentFlags::SKIP_ALL); } @@ -1754,7 +1840,8 @@ static_cast(flags | GjsArgumentFlags::SKIP_ALL)); } - array->set_array_length(length_pos, g_type_info_get_tag(&length_type)); + array->set_array_length(length_pos, g_type_info_get_tag(&length_type), + g_arg_info_get_direction(&length_arg)); } void ArgsCache::build_return(GICallableInfo* callable, bool* inc_counter_out) { @@ -2127,19 +2214,9 @@ GjsArgumentFlags::NONE); } -static size_t get_type_info_interface_size(GITypeInfo* type_info) { - GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); - g_assert(interface_info); - - GIInfoType interface_type = g_base_info_get_type(interface_info); - - if (interface_type == GI_INFO_TYPE_STRUCT) { - return g_struct_info_get_size(interface_info); - } else if (interface_type == GI_INFO_TYPE_UNION) { - return g_union_info_get_size(interface_info); - } - - return 0; +static constexpr bool type_tag_is_scalar(GITypeTag tag) { + return GI_TYPE_TAG_IS_NUMERIC(tag) || tag == GI_TYPE_TAG_BOOLEAN || + tag == GI_TYPE_TAG_GTYPE; } void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, @@ -2187,20 +2264,19 @@ param_info = g_type_info_get_param_type(&type_info, 0); GITypeTag param_tag = g_type_info_get_tag(param_info); - if (param_tag == GI_TYPE_TAG_INTERFACE) { - size = get_type_info_interface_size(param_info); - } else { - size = gjs_array_get_element_size(param_tag); - } - + size = gjs_type_get_element_size(param_tag, param_info); size *= n_elements; break; } default: break; } - } else if (type_tag == GI_TYPE_TAG_INTERFACE) { - size = get_type_info_interface_size(&type_info); + } else if (!type_tag_is_scalar(type_tag) && + !g_type_info_is_pointer(&type_info)) { + // Scalar out parameters should not be annotated with + // caller-allocates, which is for structured types that need to be + // allocated in order for the function to fill them in. + size = gjs_type_get_element_size(type_tag, &type_info); } if (!size) { @@ -2294,6 +2370,22 @@ } return; + } else if (g_type_info_is_zero_terminated(&type_info)) { + if (direction == GI_DIRECTION_IN) { + set_argument_auto(common_args); + return; + } else if (direction == GI_DIRECTION_INOUT) { + set_argument_auto(common_args); + return; + } + } else if (g_type_info_get_array_fixed_size(&type_info) >= 0) { + if (direction == GI_DIRECTION_IN) { + set_argument_auto(common_args); + return; + } else if (direction == GI_DIRECTION_INOUT) { + set_argument_auto(common_args); + return; + } } } diff -Nru gjs-1.76.2/gi/arg-cache.h gjs-1.78.0/gi/arg-cache.h --- gjs-1.76.2/gi/arg-cache.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/arg-cache.h 2023-09-17 02:27:20.000000000 +0000 @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -20,6 +19,7 @@ #include "gi/arg.h" #include "gjs/enum-utils.h" +#include "gjs/jsapi-util.h" #include "gjs/macros.h" class GjsFunctionCallState; @@ -51,7 +51,6 @@ } // namespace Arg struct Argument { - using UniquePtr = std::unique_ptr; virtual ~Argument() = default; GJS_JSAPI_RETURN_CONVENTION @@ -115,11 +114,13 @@ friend struct ArgsCache; template - static std::unique_ptr make(uint8_t index, const char* name, - GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args); + static GjsAutoCppPointer make(uint8_t index, const char* name, + GITypeInfo* type_info, GITransfer transfer, + GjsArgumentFlags flags, Args&&... args); }; +using ArgumentPtr = GjsAutoCppPointer; + // This is a trick to print out the sizes of the structs at compile time, in // an error message: // template struct Measure; @@ -141,11 +142,11 @@ GJS_JSAPI_RETURN_CONVENTION bool initialize(JSContext* cx, GICallableInfo* callable); - ArgsCache(); - ~ArgsCache(); + // COMPAT: in C++20, use default initializers for these bitfields + ArgsCache() : m_is_method(false), m_has_return(false) {} - void clear(); - bool initialized() { return m_args != nullptr; } + constexpr bool initialized() { return m_args != nullptr; } + constexpr void clear() { m_args.reset(); } void build_arg(uint8_t gi_index, GIDirection, GIArgInfo*, GICallableInfo*, bool* inc_counter_out); @@ -154,13 +155,8 @@ void build_instance(GICallableInfo* callable); - Argument* argument(uint8_t index) const { return arg_get(index).get(); } - - Argument* instance() const; GType instance_type() const; - GITypeInfo* return_type() const; - Argument* return_value() const; private: void build_normal_in_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, @@ -172,21 +168,22 @@ template - T* set_argument(uint8_t index, const char* name, GITypeInfo*, GITransfer, - GjsArgumentFlags flags, Args&&... args); + constexpr T* set_argument(uint8_t index, const char* name, GITypeInfo*, + GITransfer, GjsArgumentFlags flags, + Args&&... args); template - T* set_argument(uint8_t index, const char* name, GITransfer, - GjsArgumentFlags flags, Args&&... args); + constexpr T* set_argument(uint8_t index, const char* name, GITransfer, + GjsArgumentFlags flags, Args&&... args); template - T* set_argument_auto(Args&&... args); + constexpr T* set_argument_auto(Args&&... args); template - T* set_argument_auto(Tuple&& tuple, Args&&... args); + constexpr T* set_argument_auto(Tuple&& tuple, Args&&... args); template void set_array_argument(GICallableInfo* callable, uint8_t gi_index, @@ -194,13 +191,13 @@ GjsArgumentFlags flags, int length_pos); template - T* set_return(GITypeInfo*, GITransfer, GjsArgumentFlags); + constexpr T* set_return(GITypeInfo*, GITransfer, GjsArgumentFlags); template - T* set_instance(GITransfer, - GjsArgumentFlags flags = GjsArgumentFlags::NONE); + constexpr T* set_instance(GITransfer, + GjsArgumentFlags flags = GjsArgumentFlags::NONE); - void set_skip_all(uint8_t index, const char* name = nullptr); + constexpr void set_skip_all(uint8_t index, const char* name = nullptr); template constexpr uint8_t arg_index(uint8_t index @@ -214,13 +211,31 @@ } template - inline Argument::UniquePtr& arg_get( - uint8_t index = Argument::MAX_ARGS) const { + constexpr ArgumentPtr& arg_get(uint8_t index = Argument::MAX_ARGS) const { return m_args[arg_index(index)]; } + public: + constexpr Argument* argument(uint8_t index) const { + return arg_get(index).get(); + } + + constexpr Argument* instance() const { + if (!m_is_method) + return nullptr; + + return arg_get().get(); + } + + constexpr Argument* return_value() const { + if (!m_has_return) + return nullptr; + + return arg_get().get(); + } + private: - std::unique_ptr m_args; + GjsAutoCppPointer m_args; bool m_is_method : 1; bool m_has_return : 1; diff -Nru gjs-1.76.2/gi/arg.cpp gjs-1.78.0/gi/arg.cpp --- gjs-1.76.2/gi/arg.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/arg.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -9,6 +9,7 @@ #include // for strcmp, strlen, memcpy #include +#include #include #include @@ -176,8 +177,7 @@ break; case GI_INFO_TYPE_VALUE: // Special case for GValues - gtype = G_TYPE_VALUE; - break; + return true; default: gtype = G_TYPE_NONE; } @@ -185,7 +185,7 @@ if (g_type_is_a(gtype, G_TYPE_CLOSURE)) return true; else if (g_type_is_a(gtype, G_TYPE_VALUE)) - return g_type_info_is_pointer(type_info); + return true; else return false; } @@ -249,23 +249,18 @@ // While a list can be NULL in C, that means empty array in JavaScript, it // doesn't mean null in JavaScript. - if (!value.isObject()) + bool is_array; + if (!JS::IsArrayObject(cx, value, &is_array)) return false; - - bool found_length; - const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); - JS::RootedObject array_obj(cx, &value.toObject()); - - if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) - return false; - if (!found_length) { + if (!is_array) { throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } + JS::RootedObject array_obj(cx, &value.toObject()); + uint32_t length; - if (!gjs_object_require_converted_property(cx, array_obj, nullptr, - atoms.length(), &length)) { + if (!JS::GetArrayLength(cx, array_obj, &length)) { throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } @@ -753,22 +748,13 @@ } GJS_JSAPI_RETURN_CONVENTION -static bool gjs_array_to_flat_struct_array(JSContext* cx, - JS::HandleValue array_value, - unsigned length, - GITypeInfo* param_info, - GIBaseInfo* interface_info, - GIInfoType info_type, void** arr_p) { - g_assert( - (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) && - "Only flat arrays of unboxed structs or unions are supported"); - size_t struct_size; - if (info_type == GI_INFO_TYPE_UNION) - struct_size = g_union_info_get_size(interface_info); - else - struct_size = g_struct_info_get_size(interface_info); +static bool gjs_array_to_flat_array(JSContext* cx, JS::HandleValue array_value, + unsigned length, GITypeInfo* param_info, + size_t param_size, void** arr_p) { + g_assert((param_size > 0) && + "Only flat arrays of elements of known size are supported"); - GjsAutoPointer flat_array = g_new0(uint8_t, struct_size * length); + GjsAutoPointer flat_array = g_new0(uint8_t, param_size * length); JS::RootedObject array(cx, &array_value.toObject()); JS::RootedValue elem(cx); @@ -786,57 +772,14 @@ GI_TRANSFER_NOTHING, &arg)) return false; - memcpy(&flat_array[struct_size * i], gjs_arg_get(&arg), - struct_size); + memcpy(&flat_array[param_size * i], gjs_arg_get(&arg), + param_size); } *arr_p = flat_array.release(); return true; } -GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_from_flat_gvalue_array(JSContext *context, - gpointer array, - unsigned length, - JS::MutableHandleValue value) -{ - GValue *values = (GValue *)array; - - // a null array pointer takes precedence over whatever `length` says - if (!values) { - JSObject* jsarray = JS::NewArrayObject(context, 0); - if (!jsarray) - return false; - value.setObject(*jsarray); - return true; - } - - unsigned int i; - JS::RootedValueVector elems(context); - if (!elems.resize(length)) { - JS_ReportOutOfMemory(context); - return false; - } - - bool result = true; - - for (i = 0; i < length; i ++) { - GValue *gvalue = &values[i]; - result = gjs_value_from_g_value(context, elems[i], gvalue); - if (!result) - break; - } - - if (result) { - JSObject *jsarray; - jsarray = JS::NewArrayObject(context, elems); - value.setObjectOrNull(jsarray); - } - - return result; -} - [[nodiscard]] static bool is_gvalue(GIBaseInfo* info, GIInfoType info_type) { switch (info_type) { case GI_INFO_TYPE_VALUE: @@ -855,32 +798,12 @@ } } -[[nodiscard]] static bool is_gvalue_flat_array(GITypeInfo* param_info, - GITypeTag element_type) { - GIInfoType info_type; - - if (element_type != GI_TYPE_TAG_INTERFACE) - return false; - - GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); - info_type = g_base_info_get_type(interface_info); - - /* Special case for GValue "flat arrays" */ - return (is_gvalue(interface_info, info_type) && - !g_type_info_is_pointer(param_info)); -} - GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, size_t length, GITransfer transfer, GITypeInfo* param_info, void** arr_p) { GITypeTag element_type = g_type_info_get_storage_type(param_info); - /* Special case for GValue "flat arrays" */ - if (is_gvalue_flat_array(param_info, element_type)) - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - switch (element_type) { case GI_TYPE_TAG_UTF8: return gjs_array_to_strv (context, array_value, length, arr_p); @@ -930,13 +853,20 @@ GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); GIInfoType info_type = g_base_info_get_type(interface_info); - if (info_type == GI_INFO_TYPE_STRUCT || - info_type == GI_INFO_TYPE_UNION) { - // Ignore transfer in the case of a flat struct array. Structs - // are copied by value. - return gjs_array_to_flat_struct_array( - context, array_value, length, param_info, interface_info, - info_type, arr_p); + + if (is_gvalue(interface_info, info_type)) { + // Special case for GValue "flat arrays", this could also + // using the generic case, but if we do so we're leaking atm. + return gjs_array_to_auto_array(context, array_value, + length, arr_p); + } + + size_t element_size = gjs_type_get_element_size( + g_type_info_get_tag(param_info), param_info); + + if (element_size) { + return gjs_array_to_flat_array(context, array_value, length, + param_info, element_size, arr_p); } } [[fallthrough]]; @@ -960,62 +890,171 @@ } } -size_t gjs_array_get_element_size(GITypeTag element_type) { - size_t element_size; +size_t gjs_type_get_element_size(GITypeTag element_type, + GITypeInfo* type_info) { + if (g_type_info_is_pointer(type_info) && + element_type != GI_TYPE_TAG_ARRAY) + return sizeof(void*); switch (element_type) { case GI_TYPE_TAG_BOOLEAN: - element_size = sizeof(gboolean); - break; - case GI_TYPE_TAG_UNICHAR: - element_size = sizeof(char32_t); - break; - case GI_TYPE_TAG_UINT8: + return sizeof(gboolean); case GI_TYPE_TAG_INT8: - element_size = sizeof(uint8_t); - break; - case GI_TYPE_TAG_UINT16: + return sizeof(int8_t); + case GI_TYPE_TAG_UINT8: + return sizeof(uint8_t); case GI_TYPE_TAG_INT16: - element_size = sizeof(uint16_t); - break; - case GI_TYPE_TAG_UINT32: + return sizeof(int16_t); + case GI_TYPE_TAG_UINT16: + return sizeof(uint16_t); case GI_TYPE_TAG_INT32: - element_size = sizeof(uint32_t); - break; - case GI_TYPE_TAG_UINT64: + return sizeof(int32_t); + case GI_TYPE_TAG_UINT32: + return sizeof(uint32_t); case GI_TYPE_TAG_INT64: - element_size = sizeof(uint64_t); - break; + return sizeof(int64_t); + case GI_TYPE_TAG_UINT64: + return sizeof(uint64_t); case GI_TYPE_TAG_FLOAT: - element_size = sizeof(float); - break; + return sizeof(float); case GI_TYPE_TAG_DOUBLE: - element_size = sizeof(double); - break; + return sizeof(double); case GI_TYPE_TAG_GTYPE: - element_size = sizeof(GType); - break; - case GI_TYPE_TAG_INTERFACE: - case GI_TYPE_TAG_UTF8: - case GI_TYPE_TAG_FILENAME: - case GI_TYPE_TAG_ARRAY: + return sizeof(GType); + case GI_TYPE_TAG_UNICHAR: + return sizeof(char32_t); case GI_TYPE_TAG_GLIST: + return sizeof(GSList); case GI_TYPE_TAG_GSLIST: - case GI_TYPE_TAG_GHASH: + return sizeof(GList); case GI_TYPE_TAG_ERROR: - element_size = sizeof(void*); - break; + return sizeof(GError); + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + return sizeof(char*); + case GI_TYPE_TAG_INTERFACE: { + GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); + + switch (g_base_info_get_type(interface_info)) { + case GI_INFO_TYPE_ENUM: + case GI_INFO_TYPE_FLAGS: + return sizeof(unsigned int); + + case GI_INFO_TYPE_STRUCT: + return g_struct_info_get_size(interface_info); + case GI_INFO_TYPE_UNION: + return g_union_info_get_size(interface_info); + case GI_INFO_TYPE_VALUE: + return sizeof(GValue); + default: + return 0; + } + } + + case GI_TYPE_TAG_GHASH: + return sizeof(void*); + + case GI_TYPE_TAG_ARRAY: + if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { + int length = g_type_info_get_array_length(type_info); + if (length < 0) + return sizeof(void*); + + GjsAutoBaseInfo param_info = + g_type_info_get_param_type(type_info, 0); + GITypeTag param_tag = g_type_info_get_tag(param_info); + size_t param_size = + gjs_type_get_element_size(param_tag, param_info); + + if (param_size) + return param_size * length; + } + + return sizeof(void*); + case GI_TYPE_TAG_VOID: - default: - g_assert_not_reached(); + break; } - return element_size; + g_return_val_if_reached(0); +} + +enum class ArrayReleaseType { + EXPLICIT_LENGTH, + ZERO_TERMINATED, +}; + +template +static inline bool gjs_g_argument_release_array_internal( + JSContext* cx, GITransfer element_transfer, GjsArgumentFlags flags, + GITypeInfo* param_type, unsigned length, GIArgument* arg) { + GjsAutoPointer arg_array = + gjs_arg_steal(arg); + + if (!arg_array) + return true; + + if (element_transfer != GI_TRANSFER_EVERYTHING) + return true; + + if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { + if (length == 0) + return true; + } + + GITypeTag type_tag = g_type_info_get_tag(param_type); + + if (flags & GjsArgumentFlags::ARG_IN && + !type_needs_release(param_type, type_tag)) + return true; + + if (flags & GjsArgumentFlags::ARG_OUT && + !type_needs_out_release(param_type, type_tag)) + return true; + + size_t element_size = gjs_type_get_element_size(type_tag, param_type); + if G_UNLIKELY (element_size == 0) + return true; + + bool is_pointer = g_type_info_is_pointer(param_type); + for (size_t i = 0;; i++) { + GIArgument elem; + auto* element_start = &arg_array[i * element_size]; + auto* pointer = + is_pointer ? *reinterpret_cast(element_start) : nullptr; + + if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { + if (is_pointer) { + if (!pointer) + break; + } else if (*element_start == 0 && + memcmp(element_start, element_start + 1, + element_size - 1) == 0) { + break; + } + } + + gjs_arg_set(&elem, is_pointer ? pointer : element_start); + JS::AutoSaveExceptionState saved_exc(cx); + if (!gjs_g_arg_release_internal(cx, element_transfer, param_type, + type_tag, GJS_ARGUMENT_ARRAY_ELEMENT, + flags, &elem)) { + return false; + } + + if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { + if (i == length - 1) + break; + } + } + + return true; } static GArray* garray_new_for_storage_type(unsigned length, - GITypeTag storage_type) { - size_t element_size = gjs_array_get_element_size(storage_type); + GITypeTag storage_type, + GITypeInfo* type_info) { + size_t element_size = gjs_type_get_element_size(storage_type, type_info); return g_array_sized_new(true, false, element_size, length); } @@ -1712,7 +1751,8 @@ gjs_arg_set(arg, data.release()); } else if (array_type == GI_ARRAY_TYPE_ARRAY) { GITypeTag storage_type = g_type_info_get_storage_type(param_info); - GArray* array = garray_new_for_storage_type(length, storage_type); + GArray* array = + garray_new_for_storage_type(length, storage_type, param_info); if (data) g_array_append_vals(array, data, length); @@ -1963,9 +2003,6 @@ element_type = g_type_info_get_tag(param_info); - if (is_gvalue_flat_array(param_info, element_type)) - return gjs_array_from_flat_gvalue_array(context, array, length, value_p); - /* Special case array(guint8) */ if (element_type == GI_TYPE_TAG_UINT8) { JSObject* obj = gjs_byte_array_from_data(context, length, array); @@ -2056,7 +2093,8 @@ if (array_type != GI_ARRAY_TYPE_PTR_ARRAY && (info_type == GI_INFO_TYPE_STRUCT || - info_type == GI_INFO_TYPE_UNION) && + info_type == GI_INFO_TYPE_UNION || + info_type == GI_INFO_TYPE_VALUE) && !g_type_info_is_pointer(param_info)) { size_t struct_size; @@ -2212,8 +2250,21 @@ GITransfer transfer = GI_TRANSFER_EVERYTHING) { T* array = static_cast(c_array); - for (size_t i = 0; array[i]; i++) { - gjs_arg_set(arg, array[i]); + for (size_t i = 0;; i++) { + if constexpr (std::is_scalar_v) { + if (!array[i]) + break; + + gjs_arg_set(arg, array[i]); + } else { + uint8_t* element_start = reinterpret_cast(&array[i]); + if (*element_start == 0 && + // cppcheck-suppress pointerSize + memcmp(element_start, element_start + 1, sizeof(T) - 1) == 0) + break; + + gjs_arg_set(arg, element_start); + } if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); @@ -2304,11 +2355,25 @@ context, elems, param_info, &arg, c_array)) return false; break; + case GI_TYPE_TAG_INTERFACE: { + GjsAutoBaseInfo interface_info = + g_type_info_get_interface(param_info); + + if (!g_type_info_is_pointer(param_info) && + is_gvalue(interface_info, + g_base_info_get_type(interface_info))) { + if (!fill_vector_from_zero_terminated_carray( + context, elems, param_info, &arg, c_array)) + return false; + break; + } + + [[fallthrough]]; + } case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: - case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: @@ -2337,12 +2402,10 @@ return true; } -GJS_JSAPI_RETURN_CONVENTION -static bool gjs_object_from_g_hash(JSContext* context, - JS::MutableHandleValue value_p, - GITypeInfo* key_param_info, - GITypeInfo* val_param_info, - GITransfer transfer, GHashTable* hash) { +bool gjs_object_from_g_hash(JSContext* context, JS::MutableHandleValue value_p, + GITypeInfo* key_param_info, + GITypeInfo* val_param_info, GITransfer transfer, + GHashTable* hash) { GHashTableIter iter; GArgument keyarg, valarg; @@ -2871,8 +2934,7 @@ GITypeTag type_tag, [[maybe_unused]] GjsArgumentType argument_type, GjsArgumentFlags flags, GIArgument* arg) { g_assert(transfer != GI_TRANSFER_NOTHING || - flags != GjsArgumentFlags::NONE || - argument_type != GJS_ARGUMENT_ARGUMENT); + flags != GjsArgumentFlags::NONE); switch (type_tag) { case GI_TYPE_TAG_VOID: @@ -2994,28 +3056,6 @@ element_type = g_type_info_get_tag(param_info); - if (is_gvalue_flat_array(param_info, element_type)) { - if (transfer != GI_TRANSFER_CONTAINER) { - gint len = g_type_info_get_array_fixed_size(type_info); - gint i; - - if (len < 0) { - gjs_throw(context, - "Releasing a flat GValue array that was not fixed-size or was nested" - "inside another container. This is not supported (and will leak)"); - return false; - } - - for (i = 0; i < len; i++) { - GValue* v = gjs_arg_get(arg) + i; - g_value_unset(v); - } - } - - g_clear_pointer(&gjs_arg_member(arg), g_free); - return true; - } - switch (element_type) { case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: @@ -3060,40 +3100,24 @@ case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: { - GjsAutoPointer arg_array = - gjs_arg_steal(arg); - if (transfer != GI_TRANSFER_CONTAINER - && type_needs_out_release(param_info, element_type)) { - if (g_type_info_is_zero_terminated (type_info)) { - gpointer *array; - GArgument elem; - - for (array = arg_array; *array; array++) { - gjs_arg_set(&elem, *array); - if (!gjs_g_arg_release_internal( - context, transfer, param_info, element_type, - GJS_ARGUMENT_ARRAY_ELEMENT, flags, &elem)) { - return false; - } - } - } else { - gint len = g_type_info_get_array_fixed_size(type_info); - gint i; - GArgument elem; - - g_assert(len != -1); - - for (i = 0; i < len; i++) { - gjs_arg_set(&elem, arg_array[i]); - if (!gjs_g_arg_release_internal( - context, transfer, param_info, element_type, - GJS_ARGUMENT_ARRAY_ELEMENT, flags, &elem)) { - return false; - } - } - } + GITransfer element_transfer = transfer; + + if (argument_type != GJS_ARGUMENT_ARGUMENT && + transfer != GI_TRANSFER_EVERYTHING) + element_transfer = GI_TRANSFER_NOTHING; + + if (g_type_info_is_zero_terminated(type_info)) { + return gjs_g_argument_release_array_internal< + ArrayReleaseType::ZERO_TERMINATED>( + context, element_transfer, + flags | GjsArgumentFlags::ARG_OUT, param_info, 0, arg); + } else { + return gjs_g_argument_release_array_internal< + ArrayReleaseType::EXPLICIT_LENGTH>( + context, element_transfer, + flags | GjsArgumentFlags::ARG_OUT, param_info, + g_type_info_get_array_fixed_size(type_info), arg); } - break; } case GI_TYPE_TAG_VOID: @@ -3205,7 +3229,7 @@ GjsAutoPointer hash_table = gjs_arg_steal(arg); if (transfer == GI_TRANSFER_CONTAINER) - g_hash_table_steal_all(hash_table); + g_hash_table_remove_all(hash_table); else { GHR_closure c = {context, nullptr, nullptr, transfer, flags, false}; @@ -3281,72 +3305,69 @@ bool gjs_g_argument_release_in_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { - GArgument elem; - guint i; - GITypeTag type_tag; - if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument array in param"); - GjsAutoPointer array = gjs_arg_steal(arg); GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); - type_tag = g_type_info_get_tag(param_type); + return gjs_g_argument_release_array_internal< + ArrayReleaseType::EXPLICIT_LENGTH>(context, GI_TRANSFER_EVERYTHING, + GjsArgumentFlags::ARG_IN, param_type, + length, arg); +} - if (is_gvalue_flat_array(param_type, type_tag)) { - for (i = 0; i < length; i++) { - GValue* v = reinterpret_cast(array.get()) + i; - g_value_unset(v); - } - } +bool gjs_g_argument_release_in_array(JSContext* context, GITransfer transfer, + GITypeInfo* type_info, GIArgument* arg) { + if (transfer != GI_TRANSFER_NOTHING) + return true; - if (type_needs_release(param_type, type_tag)) { - for (i = 0; i < length; i++) { - gjs_arg_set(&elem, array[i]); - if (!gjs_g_arg_release_internal(context, GI_TRANSFER_NOTHING, - param_type, type_tag, - GJS_ARGUMENT_ARRAY_ELEMENT, - GjsArgumentFlags::ARG_IN, &elem)) { - return false; - } - } - } + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GArgument array in param"); - return true; + GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); + return gjs_g_argument_release_array_internal< + ArrayReleaseType::ZERO_TERMINATED>(context, GI_TRANSFER_EVERYTHING, + GjsArgumentFlags::ARG_IN, param_type, + 0, arg); } bool gjs_g_argument_release_out_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { - GArgument elem; - guint i; - GITypeTag type_tag; - if (transfer == GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument array out param"); - GjsAutoPointer array = gjs_arg_steal(arg); + GITransfer element_transfer = transfer == GI_TRANSFER_CONTAINER + ? GI_TRANSFER_NOTHING + : GI_TRANSFER_EVERYTHING; + GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); - type_tag = g_type_info_get_tag(param_type); + return gjs_g_argument_release_array_internal< + ArrayReleaseType::EXPLICIT_LENGTH>(context, element_transfer, + GjsArgumentFlags::ARG_OUT, + param_type, length, arg); +} - if (transfer != GI_TRANSFER_CONTAINER && - type_needs_out_release(param_type, type_tag)) { - for (i = 0; i < length; i++) { - gjs_arg_set(&elem, array[i]); - JS::AutoSaveExceptionState saved_exc(context); - if (!gjs_g_arg_release_internal(context, transfer, param_type, - type_tag, - GJS_ARGUMENT_ARRAY_ELEMENT, - GjsArgumentFlags::ARG_OUT, &elem)) { - return false; - } - } - } +bool gjs_g_argument_release_out_array(JSContext* context, GITransfer transfer, + GITypeInfo* type_info, GIArgument* arg) { + if (transfer == GI_TRANSFER_NOTHING) + return true; - return true; + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GArgument array out param"); + + GITransfer element_transfer = transfer == GI_TRANSFER_CONTAINER + ? GI_TRANSFER_NOTHING + : GI_TRANSFER_EVERYTHING; + + GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); + return gjs_g_argument_release_array_internal< + ArrayReleaseType::ZERO_TERMINATED>(context, element_transfer, + GjsArgumentFlags::ARG_OUT, + param_type, 0, arg); } diff -Nru gjs-1.76.2/gi/arg.h gjs-1.78.0/gi/arg.h --- gjs-1.76.2/gi/arg.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/arg.h 2023-09-17 02:27:20.000000000 +0000 @@ -12,6 +12,7 @@ #include #include +#include // for GHashTable #include #include @@ -41,6 +42,10 @@ ARG_INOUT = ARG_IN | ARG_OUT, }; +// Overload operator| so that Visual Studio won't complain +// when converting unsigned char to GjsArgumentFlags +GjsArgumentFlags operator|(GjsArgumentFlags const& v1, GjsArgumentFlags const& v2); + [[nodiscard]] char* gjs_argument_display_name(const char* arg_name, GjsArgumentType arg_type); @@ -55,7 +60,7 @@ GjsArgumentFlags flags, void** contents, size_t* length_p); -size_t gjs_array_get_element_size(GITypeTag element_type); +size_t gjs_type_get_element_size(GITypeTag element_type, GITypeInfo* type_info); void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg); @@ -121,10 +126,16 @@ GITypeInfo* type_info, unsigned length, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION +bool gjs_g_argument_release_out_array(JSContext* cx, GITransfer transfer, + GITypeInfo* type_info, GIArgument* arg); +GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release_in_array(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION +bool gjs_g_argument_release_in_array(JSContext* cx, GITransfer transfer, + GITypeInfo* type_info, GIArgument* arg); +GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release_in_arg(JSContext*, GITransfer, GITypeInfo*, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION @@ -156,4 +167,10 @@ GITypeInfo* param_info, GITransfer, const GValue* gvalue); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_object_from_g_hash(JSContext* cx, JS::MutableHandleValue, + GITypeInfo* key_param_info, + GITypeInfo* val_param_info, GITransfer transfer, + GHashTable* hash); + #endif // GI_ARG_H_ diff -Nru gjs-1.76.2/gi/boxed.cpp gjs-1.78.0/gi/boxed.cpp --- gjs-1.76.2/gi/boxed.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/boxed.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -7,7 +7,6 @@ #include #include // for memcpy, size_t, strcmp -#include #include // for move, forward #include @@ -360,11 +359,11 @@ GjsAutoFunctionInfo func_info = proto->zero_args_constructor_info(); GIArgument rval_arg; - GError *error = NULL; + GjsAutoError error; if (!g_function_info_invoke(func_info, NULL, 0, NULL, 0, &rval_arg, &error)) { - gjs_throw(context, "Failed to invoke boxed constructor: %s", error->message); - g_clear_error(&error); + gjs_throw(context, "Failed to invoke boxed constructor: %s", + error->message); return false; } @@ -769,7 +768,7 @@ &BoxedBase::trace }; -/* We allocate 1 reserved slot; this is typically unused, but if the +/* We allocate 1 extra reserved slot; this is typically unused, but if the * boxed is for a nested structure inside a parent structure, the * reserved slot is used to hold onto the parent Javascript object and * make sure it doesn't get freed. diff -Nru gjs-1.76.2/gi/closure.cpp gjs-1.78.0/gi/closure.cpp --- gjs-1.76.2/gi/closure.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/closure.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -184,8 +184,6 @@ bool ok = JS::Call(m_cx, this_obj, v_callable, args, retval); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx); - if (gjs->should_exit(nullptr)) - gjs->warn_about_unhandled_promise_rejections(); if (!ok) { /* Exception thrown... */ gjs_debug_closure( diff -Nru gjs-1.76.2/gi/cwrapper.h gjs-1.78.0/gi/cwrapper.h --- gjs-1.76.2/gi/cwrapper.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/cwrapper.h 2023-09-17 02:27:20.000000000 +0000 @@ -9,13 +9,13 @@ #include #include // for size_t -#include #include // for integral_constant #include // for GType #include #include +#include // for JSEXN_TYPEERR #include // for CurrentGlobalOrNull #include #include // for GetClass @@ -24,7 +24,7 @@ #include #include #include // for JSFUN_CONSTRUCTOR, JS_NewPlainObject, JS_GetFuncti... -#include // for JSProto_Object, JSProtoKey, JSProto_TypeError +#include // for JSProto_Object, JSProtoKey #include "gjs/jsapi-util.h" #include "gjs/macros.h" @@ -122,7 +122,7 @@ Wrapped** out) { if (!typecheck(cx, wrapper)) { const JSClass* obj_class = JS::GetClass(wrapper); - gjs_throw_custom(cx, JSProto_TypeError, nullptr, + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object %p is not a subclass of %s, it's a %s", wrapper.get(), Base::klass.name, obj_class->name); return false; @@ -504,7 +504,7 @@ if (ctor_obj) { JS::RootedObject in_obj(cx, module); if (!in_obj) - in_obj = gjs_get_import_global(cx); + in_obj = global; JS::RootedId class_name( cx, gjs_intern_string_to_id(cx, Base::klass.name)); if (class_name.isVoid() || diff -Nru gjs-1.76.2/gi/function.cpp gjs-1.78.0/gi/function.cpp --- gjs-1.76.2/gi/function.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/function.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -4,10 +4,12 @@ #include +#include // for NULL, size_t #include -#include // for exit +#include #include // for unique_ptr +#include #include #include @@ -28,7 +30,6 @@ #include #include // for GetRealmFunctionPrototype #include -#include #include #include #include @@ -47,7 +48,6 @@ #include "gi/object.h" #include "gi/utils-inl.h" #include "gjs/context-private.h" -#include "gjs/context.h" #include "gjs/global.h" #include "gjs/jsapi-util.h" #include "gjs/macros.h" @@ -131,8 +131,7 @@ static JSObject* inherit_builtin_function(JSContext* cx, JSProtoKey) { JS::RootedObject builtin_function_proto( cx, JS::GetRealmFunctionPrototype(cx)); - return JS_NewObjectWithGivenProto(cx, &Function::klass, - builtin_function_proto); + return JS_NewObjectWithGivenProto(cx, nullptr, builtin_function_proto); } static const JSClassOps class_ops; @@ -270,13 +269,21 @@ } void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when, - const char* reason) { - g_critical("Attempting to run a JS callback %s. This is most likely caused " - "by %s. Because it would crash the application, it has been " - "blocked.", when, reason); - if (m_info) - g_critical("The offending callback was %s()%s.", m_info.name(), - m_is_vfunc ? ", a vfunc" : ""); + const char* reason, + bool dump_stack) { + std::ostringstream message; + + message << "Attempting to run a JS callback " << when << ". " + << "This is most likely caused by " << reason << ". " + << "Because it would crash the application, it has been blocked."; + if (m_info) { + message << "\nThe offending callback was " << m_info.name() << "()" + << (m_is_vfunc ? ", a vfunc." : "."); + } + if (dump_stack) { + message << "\n" << gjs_dumpstack_string(); + } + g_critical("%s", message.str().c_str()); } /* This is our main entry point for ffi_closure callbacks. @@ -289,12 +296,20 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { GITypeInfo ret_type; + // Fill in the result with some hopefully neutral value + g_callable_info_load_return_type(m_info, &ret_type); + if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) { + GIArgument argument = {}; + gjs_gi_argument_init_default(&ret_type, &argument); + set_return_ffi_arg_from_giargument(&ret_type, result, &argument); + } + if (G_UNLIKELY(!is_valid())) { warn_about_illegal_js_callback( "during shutdown", "destroying a Clutter actor or GTK widget with ::destroy signal " - "connected, or using the destroy(), dispose(), or remove() vfuncs"); - gjs_dumpstack(); + "connected, or using the destroy(), dispose(), or remove() vfuncs", + true); return; } @@ -304,14 +319,15 @@ warn_about_illegal_js_callback( "during garbage collection", "destroying a Clutter actor or GTK widget with ::destroy signal " - "connected, or using the destroy(), dispose(), or remove() vfuncs"); - gjs_dumpstack(); + "connected, or using the destroy(), dispose(), or remove() vfuncs", + true); return; } if (G_UNLIKELY(!gjs->is_owner_thread())) { warn_about_illegal_js_callback("on a different thread", - "an API not intended to be used in JS"); + "an API not intended to be used in JS", + false); return; } @@ -351,7 +367,8 @@ if (g_object_get_qdata(gobj, ObjectBase::disposed_quark())) { warn_about_illegal_js_callback( "on disposed object", - "using the destroy(), dispose(), or remove() vfuncs"); + "using the destroy(), dispose(), or remove() vfuncs", + false); } gjs_log_exception(context); return; @@ -365,8 +382,6 @@ JS::RootedValue rval(context); - g_callable_info_load_return_type(m_info, &ret_type); - if (!callback_closure_inner(context, this_object, gobj, &rval, args, &ret_type, n_args, c_args_offset, result)) { if (!JS_IsExceptionPending(context)) { @@ -375,10 +390,8 @@ // to exit here instead of propagating the exception back to the // original calling JS code. uint8_t code; - if (gjs->should_exit(&code)) { - gjs->warn_about_unhandled_promise_rejections(); - exit(code); - } + if (gjs->should_exit(&code)) + gjs->exit_immediately(code); // Some other uncatchable exception, e.g. out of memory JSFunction* fn = JS_GetObjectFunction(callable()); @@ -389,14 +402,6 @@ descr.c_str(), m_info.ns(), m_info.name()); } - // Fill in the result with some hopefully neutral value - if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) { - GIArgument argument = {}; - g_callable_info_load_return_type(m_info, &ret_type); - gjs_gi_argument_init_default(&ret_type, &argument); - set_return_ffi_arg_from_giargument(&ret_type, result, &argument); - } - // If the callback has a GError** argument, then make a GError from the // value that was thrown. Otherwise, log it as "uncaught" (critical // instead of warning) @@ -982,6 +987,9 @@ dynamicString += format_name(); AutoProfilerLabel label(context, "", dynamicString.c_str()); + g_assert(ffi_arg_pos + state.gi_argc < + std::numeric_limits::max()); + state.processed_c_args = ffi_arg_pos; for (gi_arg_pos = 0; gi_arg_pos < state.gi_argc; gi_arg_pos++, ffi_arg_pos++) { @@ -1025,7 +1033,7 @@ } // This pointer needs to exist on the stack across the ffi_call() call - GError** errorp = state.local_error.out(); + GError** errorp = &state.local_error; /* Did argument conversion fail? In that case, skip invocation and jump to release * processing. */ @@ -1224,9 +1232,7 @@ gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p", callee.get(), priv); - if (priv == NULL) - return true; // we are the prototype - + g_assert(priv); return priv->invoke(context, js_argv); } @@ -1236,8 +1242,7 @@ } void Function::finalize_impl(JS::GCContext*, Function* priv) { - if (priv == NULL) - return; /* we are the prototype, not a real instance, so constructor never called */ + g_assert(priv); delete priv; } @@ -1252,15 +1257,6 @@ bool Function::to_string(JSContext* context, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(context, argc, vp, rec, this_obj, Function, priv); - - if (priv == NULL) { - JSString* retval = JS_NewStringCopyZ(context, "function () {\n}"); - if (!retval) - return false; - rec.rval().setString(retval); - return true; - } - return priv->to_string_impl(context, rec.rval()); } @@ -1323,20 +1319,19 @@ bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) { guint8 i; - GError *error = NULL; + GjsAutoError error; if (m_info.type() == GI_INFO_TYPE_FUNCTION) { if (!g_function_info_prep_invoker(m_info, &m_invoker, &error)) return gjs_throw_gerror(context, error); } else if (m_info.type() == GI_INFO_TYPE_VFUNC) { void* addr = g_vfunc_info_get_address(m_info, gtype, &error); - if (error != NULL) { + if (error) { if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND) return gjs_throw_gerror(context, error); gjs_throw(context, "Virtual function not implemented: %s", error->message); - g_clear_error(&error); return false; } diff -Nru gjs-1.76.2/gi/function.h gjs-1.78.0/gi/function.h --- gjs-1.76.2/gi/function.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/function.h 2023-09-17 02:27:20.000000000 +0000 @@ -79,7 +79,8 @@ GObject* gobject, JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type, int n_args, int c_args_offset, void* result); - void warn_about_illegal_js_callback(const char* when, const char* reason); + void warn_about_illegal_js_callback(const char* when, const char* reason, + bool dump_stack); static std::vector s_forever_closure_list; @@ -94,9 +95,9 @@ // Stack allocation only! class GjsFunctionCallState { - GIArgument* m_in_cvalues; - GIArgument* m_out_cvalues; - GIArgument* m_inout_original_cvalues; + GjsAutoCppPointer m_in_cvalues; + GjsAutoCppPointer m_out_cvalues; + GjsAutoCppPointer m_inout_original_cvalues; public: std::unordered_set ignore_release; @@ -105,7 +106,7 @@ GjsAutoError local_error; GICallableInfo* info; uint8_t gi_argc = 0; - unsigned processed_c_args = 0; + uint8_t processed_c_args = 0; bool failed : 1; bool can_throw_gerror : 1; bool is_method : 1; @@ -124,12 +125,6 @@ m_inout_original_cvalues = new GIArgument[size]; } - ~GjsFunctionCallState() { - delete[] m_in_cvalues; - delete[] m_out_cvalues; - delete[] m_inout_original_cvalues; - } - GjsFunctionCallState(const GjsFunctionCallState&) = delete; GjsFunctionCallState& operator=(const GjsFunctionCallState&) = delete; @@ -160,7 +155,7 @@ constexpr bool call_completed() { return !failed && !did_throw_gerror(); } - constexpr uint8_t last_processed_index() { + constexpr unsigned last_processed_index() { return first_arg_offset() + processed_c_args; } diff -Nru gjs-1.76.2/gi/fundamental.cpp gjs-1.78.0/gi/fundamental.cpp --- gjs-1.76.2/gi/fundamental.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/fundamental.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -12,6 +12,7 @@ #include #include // for JS_ReportOutOfMemory #include // for WeakCache +#include // for DefaultHasher via WeakCache #include // for GetClass #include #include diff -Nru gjs-1.76.2/gi/gerror.cpp gjs-1.78.0/gi/gerror.cpp --- gjs-1.76.2/gi/gerror.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/gerror.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -6,8 +6,6 @@ #include -#include - #include #include @@ -532,7 +530,7 @@ * * Returns: false, for convenience in returning from the calling function. */ -bool gjs_throw_gerror(JSContext* cx, GError* error) { +bool gjs_throw_gerror(JSContext* cx, GjsAutoError const& error) { // return false even if the GError is null, as presumably something failed // in the calling code, and the caller expects to throw. g_return_val_if_fail(error, false); @@ -541,8 +539,6 @@ if (!err_obj || !gjs_define_error_properties(cx, err_obj)) return false; - g_error_free(error); - JS::RootedValue err(cx, JS::ObjectValue(*err_obj)); JS_SetPendingException(cx, err); diff -Nru gjs-1.76.2/gi/gerror.h gjs-1.78.0/gi/gerror.h --- gjs-1.76.2/gi/gerror.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/gerror.h 2023-09-17 02:27:20.000000000 +0000 @@ -163,6 +163,6 @@ GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj); -bool gjs_throw_gerror(JSContext* cx, GError* error); +bool gjs_throw_gerror(JSContext* cx, GjsAutoError const&); #endif // GI_GERROR_H_ diff -Nru gjs-1.76.2/gi/gobject.cpp gjs-1.78.0/gi/gobject.cpp --- gjs-1.76.2/gi/gobject.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/gobject.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -161,7 +161,7 @@ * Construct the JS object from the constructor, then use the GObject * that was associated in gjs_object_custom_init() */ - JSAutoRealm ar(cx, gjs_get_import_global(cx)); + Gjs::AutoMainRealm ar{gjs}; JS::RootedObject constructor( cx, gjs_lookup_object_constructor_from_info(cx, nullptr, type)); diff -Nru gjs-1.76.2/gi/gtype.cpp gjs-1.78.0/gi/gtype.cpp --- gjs-1.76.2/gi/gtype.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/gtype.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include // for WeakCache +#include // for DefaultHasher via WeakCache #include #include // for JSPROP_PERMANENT #include diff -Nru gjs-1.76.2/gi/object.cpp gjs-1.78.0/gi/object.cpp --- gjs-1.76.2/gi/object.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/object.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -58,7 +58,6 @@ #include "gi/wrapperutils.h" #include "gjs/atoms.h" #include "gjs/context-private.h" -#include "gjs/context.h" #include "gjs/deprecation.h" #include "gjs/jsapi-class.h" #include "gjs/jsapi-util.h" @@ -165,10 +164,9 @@ "Object %s.%s (%p), has been already %s — impossible to %s " "it. This might be caused by the object having been destroyed from C " "code using something such as destroy(), dispose(), or remove() " - "vfuncs.", + "vfuncs.\n%s", ns(), name(), m_ptr.get(), m_gobj_finalized ? "finalized" : "disposed", - for_what); - gjs_dumpstack(); + for_what, gjs_dumpstack_string().c_str()); return false; } @@ -315,7 +313,8 @@ JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); - std::string fullName = priv->format_name() + "." + gjs_debug_string(name); + std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + + "]"}; AutoProfilerLabel label(cx, "property getter", fullName.c_str()); priv->debug_jsprop("Property getter", name, obj); @@ -452,7 +451,8 @@ JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); - std::string fullName = priv->format_name() + "." + gjs_debug_string(name); + std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + + "]"}; AutoProfilerLabel label(cx, "property setter", fullName.c_str()); priv->debug_jsprop("Property setter", name, obj); @@ -487,8 +487,12 @@ /* prevent setting the prop even in JS */ return gjs_wrapper_throw_readonly_field(cx, gtype(), param_spec->name); - if (param_spec->flags & G_PARAM_DEPRECATED) - _gjs_warn_deprecated_once_per_callsite(cx, DeprecatedGObjectProperty); + if (param_spec->flags & G_PARAM_DEPRECATED) { + const std::string& class_name = format_name(); + _gjs_warn_deprecated_once_per_callsite( + cx, DeprecatedGObjectProperty, + {class_name.c_str(), param_spec->name}); + } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s", param_spec->name); @@ -508,7 +512,8 @@ JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); - std::string fullName = priv->format_name() + "." + gjs_debug_string(name); + std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + + "]"}; AutoProfilerLabel label(cx, "field setter", fullName.c_str()); priv->debug_jsprop("Field setter", name, obj); @@ -548,21 +553,17 @@ } bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) { + GjsAutoError error; GType ptype = g_type_parent(m_gtype); - GError *error = NULL; gpointer addr1, addr2; addr1 = g_vfunc_info_get_address(info, m_gtype, &error); - if (error) { - g_clear_error(&error); + if (error) return false; - } addr2 = g_vfunc_info_get_address(info, ptype, &error); - if (error) { - g_clear_error(&error); + if (error) return false; - } return addr1 == addr2; } @@ -2083,7 +2084,7 @@ } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, - "Looking up cached field info for '%s' in '%s' prototype", + "Looking up cached field info for %s in '%s' prototype", gjs_debug_string(key).c_str(), g_type_name(m_gtype)); auto entry = m_field_cache.lookupForAdd(key); if (entry) diff -Nru gjs-1.76.2/gi/param.cpp gjs-1.78.0/gi/param.cpp --- gjs-1.76.2/gi/param.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/param.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -11,14 +11,14 @@ #include #include +#include // for JSEXN_TYPEERR #include // for GetClass #include #include #include #include // for UniqueChars #include -#include // for JS_NewObjectForConstructor, JS_NewObjectWithG... -#include // for JSProto_TypeError +#include // for JS_NewObjectForConstructor, JS_NewObjectWithG... #include "gi/cwrapper.h" #include "gi/function.h" @@ -262,9 +262,10 @@ GParamSpec* param = param_value(context, object); if (!param) { if (throw_error) { - gjs_throw_custom(context, JSProto_TypeError, nullptr, - "Object is GObject.ParamSpec.prototype, not an object instance - " - "cannot convert to a GObject.ParamSpec instance"); + gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, + "Object is GObject.ParamSpec.prototype, not an " + "object instance - cannot convert to a GObject." + "ParamSpec instance"); } return false; @@ -276,7 +277,7 @@ result = true; if (!result && throw_error) { - gjs_throw_custom(context, JSProto_TypeError, nullptr, + gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, "Object is of type %s - cannot convert to %s", g_type_name(G_TYPE_FROM_INSTANCE(param)), g_type_name(expected_type)); diff -Nru gjs-1.76.2/gi/repo.cpp gjs-1.78.0/gi/repo.cpp --- gjs-1.76.2/gi/repo.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/repo.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -4,7 +4,6 @@ #include -#include #include // for strlen #if GJS_VERBOSE_ENABLE_GI_USAGE @@ -83,8 +82,6 @@ static bool resolve_namespace_object(JSContext* context, JS::HandleObject repo_obj, JS::HandleId ns_id) { - GError *error; - JS::UniqueChars version; if (!get_version_for_ns(context, repo_obj, ns_id, &version)) return false; @@ -108,14 +105,12 @@ ns_name.get(), nversions)) return false; - error = NULL; + GjsAutoError error; g_irepository_require(nullptr, ns_name.get(), version.get(), GIRepositoryLoadFlags(0), &error); - if (error != NULL) { + if (error) { gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(), version ? version.get() : "none", error->message); - - g_error_free(error); return false; } @@ -491,31 +486,23 @@ return gjs_lookup_namespace_object_by_name(context, ns_name); } -/* Check if an exception's 'name' property is equal to compare_name. Ignores +/* Check if an exception's 'name' property is equal to ImportError. Ignores * all errors that might arise. */ -[[nodiscard]] static bool error_has_name(JSContext* cx, - JS::HandleValue thrown_value, - JSString* compare_name) { +[[nodiscard]] static bool is_import_error(JSContext* cx, + JS::HandleValue thrown_value) { if (!thrown_value.isObject()) return false; JS::AutoSaveExceptionState saved_exc(cx); JS::RootedObject exc(cx, &thrown_value.toObject()); JS::RootedValue exc_name(cx); - bool retval = false; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + bool eq; + bool retval = + JS_GetPropertyById(cx, exc, atoms.name(), &exc_name) && + JS_StringEqualsLiteral(cx, exc_name.toString(), "ImportError", &eq) && + eq; - if (!JS_GetPropertyById(cx, exc, atoms.name(), &exc_name)) - goto out; - - int32_t cmp_result; - if (!JS_CompareStrings(cx, exc_name.toString(), compare_name, &cmp_result)) - goto out; - - if (cmp_result == 0) - retval = true; - -out: saved_exc.restore(); return retval; } @@ -528,7 +515,7 @@ { JS::AutoSaveExceptionState saved_exc(cx); - JS::RootedObject global(cx, gjs_get_import_global(cx)); + JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)}; JS::RootedValue importer( cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS)); g_assert(importer.isObject()); @@ -538,7 +525,7 @@ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, importer_obj, "importer", atoms.overrides(), &overridespkg)) - goto fail; + return false; if (!gjs_object_require_property(cx, overridespkg, "GI repository object", ns_name, @@ -548,12 +535,12 @@ /* If the exception was an ImportError (i.e., module not found) then * we simply didn't have an override, don't throw an exception */ - if (error_has_name(cx, exc, JS_AtomizeAndPinString(cx, "ImportError"))) { + if (is_import_error(cx, exc)) { saved_exc.restore(); return true; } - goto fail; + return false; } // If the override module is present, it must have a callable _init(). An @@ -563,13 +550,9 @@ atoms.init(), function) || !function.isObject() || !JS::IsCallable(&function.toObject())) { gjs_throw(cx, "Unexpected value for _init in overrides module"); - goto fail; + return false; } return true; - -fail: - saved_exc.drop(); - return false; } GJS_JSAPI_RETURN_CONVENTION diff -Nru gjs-1.76.2/gi/value.cpp gjs-1.78.0/gi/value.cpp --- gjs-1.76.2/gi/value.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/value.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -4,21 +4,22 @@ #include +#include // for NULL #include -#include // for exit +#include #include #include #include #include +#include #include #include #include #include #include // for RootedVector -#include #include #include #include @@ -43,51 +44,44 @@ #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" -#include "gjs/atoms.h" #include "gjs/byteArray.h" #include "gjs/context-private.h" -#include "gjs/context.h" #include "gjs/jsapi-util.h" #include "gjs/macros.h" #include "gjs/objectbox.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION -static bool gjs_value_from_g_value_internal(JSContext *context, - JS::MutableHandleValue value_p, - const GValue *gvalue, - bool no_copy, - GSignalQuery *signal_query, - int arg_n); +static bool gjs_value_from_g_value_internal(JSContext*, JS::MutableHandleValue, + const GValue*, bool no_copy = false, + GjsAutoSignalInfo const& = nullptr, + GjsAutoArgInfo const& = nullptr, + GITypeInfo* = nullptr); /* * Gets signal introspection info about closure, or NULL if not found. Currently * only works for signals on introspected GObjects, not signals on GJS-defined * GObjects nor standalone closures. The return value must be unreffed. */ -[[nodiscard]] static GISignalInfo* get_signal_info_if_available( +[[nodiscard]] static GjsAutoSignalInfo get_signal_info_if_available( GSignalQuery* signal_query) { - GIBaseInfo *obj; GIInfoType info_type; - GISignalInfo *signal_info = NULL; if (!signal_query->itype) - return NULL; + return nullptr; - obj = g_irepository_find_by_gtype(NULL, signal_query->itype); + GjsAutoBaseInfo obj = + g_irepository_find_by_gtype(nullptr, signal_query->itype); if (!obj) - return NULL; + return nullptr; info_type = g_base_info_get_type (obj); if (info_type == GI_INFO_TYPE_OBJECT) - signal_info = g_object_info_find_signal((GIObjectInfo*)obj, - signal_query->signal_name); + return g_object_info_find_signal(obj, signal_query->signal_name); else if (info_type == GI_INFO_TYPE_INTERFACE) - signal_info = g_interface_info_find_signal((GIInterfaceInfo*)obj, - signal_query->signal_name); - g_base_info_unref((GIBaseInfo*)obj); + return g_interface_info_find_signal(obj, signal_query->signal_name); - return signal_info; + return nullptr; } /* @@ -95,25 +89,21 @@ * in array_value, with its length stored in array_length_value. */ GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_value_from_array_and_length_values(JSContext *context, - JS::MutableHandleValue value_p, - GITypeInfo *array_type_info, - const GValue *array_value, - const GValue *array_length_value, - bool no_copy, - GSignalQuery *signal_query, - int array_length_arg_n) -{ +static bool gjs_value_from_array_and_length_values( + JSContext* context, GjsAutoSignalInfo const& signal_info, + JS::MutableHandleValue value_p, GITypeInfo* array_type_info, + const GValue* array_value, GjsAutoArgInfo const& array_length_arg_info, + GITypeInfo* array_length_type_info, const GValue* array_length_value, + bool no_copy) { JS::RootedValue array_length(context); GArgument array_arg; g_assert(G_VALUE_HOLDS_POINTER(array_value)); g_assert(G_VALUE_HOLDS_INT(array_length_value)); - if (!gjs_value_from_g_value_internal(context, &array_length, - array_length_value, no_copy, - signal_query, array_length_arg_n)) + if (!gjs_value_from_g_value_internal( + context, &array_length, array_length_value, no_copy, signal_info, + array_length_arg_info, array_length_type_info)) return false; gjs_arg_set(&array_arg, g_value_get_pointer(array_value)); @@ -131,7 +121,6 @@ JSContext *context; unsigned i; GSignalQuery signal_query = { 0, }; - GISignalInfo *signal_info; gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", this); @@ -144,22 +133,26 @@ GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (G_UNLIKELY(gjs->sweeping())) { GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint; + std::ostringstream message; - g_critical("Attempting to call back into JSAPI during the sweeping phase of GC. " - "This is most likely caused by not destroying a Clutter actor or Gtk+ " - "widget with ::destroy signals connected, but can also be caused by " - "using the destroy(), dispose(), or remove() vfuncs. " - "Because it would crash the application, it has been " - "blocked and the JS callback not invoked."); + message << "Attempting to call back into JSAPI during the sweeping " + "phase of GC. This is most likely caused by not destroying " + "a Clutter actor or Gtk+ widget with ::destroy signals " + "connected, but can also be caused by using the destroy(), " + "dispose(), or remove() vfuncs. Because it would crash the " + "application, it has been blocked and the JS callback not " + "invoked."; if (hint) { gpointer instance; g_signal_query(hint->signal_id, &signal_query); instance = g_value_peek_pointer(¶m_values[0]); - g_critical("The offending signal was %s on %s %p.", signal_query.signal_name, - g_type_name(G_TYPE_FROM_INSTANCE(instance)), instance); + message << "\nThe offending signal was " << signal_query.signal_name + << " on " << g_type_name(G_TYPE_FROM_INSTANCE(instance)) + << " " << instance << "."; } - gjs_dumpstack(); + message << "\n" << gjs_dumpstack_string(); + g_critical("%s", message.str().c_str()); return; } @@ -189,33 +182,31 @@ /* Check if any parameters, such as array lengths, need to be eliminated * before we invoke the closure. */ - GjsAutoPointer skip = g_new0(bool, n_param_values); - GjsAutoPointer array_len_indices_for = g_new(int, n_param_values); - for(i = 0; i < n_param_values; i++) - array_len_indices_for[i] = -1; - GjsAutoPointer type_info_for = - g_new0(GITypeInfo, n_param_values); + struct ArgumentDetails { + int array_len_index_for = -1; + bool skip = false; + GITypeInfo type_info; + GjsAutoArgInfo arg_info; + }; + std::vector args_details(n_param_values); - signal_info = get_signal_info_if_available(&signal_query); + GjsAutoSignalInfo signal_info = get_signal_info_if_available(&signal_query); if (signal_info) { /* Start at argument 1, skip the instance parameter */ for (i = 1; i < n_param_values; ++i) { - GIArgInfo *arg_info; int array_len_pos; - arg_info = g_callable_info_get_arg(signal_info, i - 1); - g_arg_info_load_type(arg_info, &type_info_for[i]); + ArgumentDetails& arg_details = args_details[i]; + arg_details.arg_info = g_callable_info_get_arg(signal_info, i - 1); + g_arg_info_load_type(arg_details.arg_info, &arg_details.type_info); - array_len_pos = g_type_info_get_array_length(&type_info_for[i]); + array_len_pos = + g_type_info_get_array_length(&arg_details.type_info); if (array_len_pos != -1) { - skip[array_len_pos + 1] = true; - array_len_indices_for[i] = array_len_pos + 1; + args_details[array_len_pos + 1].skip = true; + arg_details.array_len_index_for = array_len_pos + 1; } - - g_base_info_unref((GIBaseInfo *)arg_info); } - - g_base_info_unref((GIBaseInfo *)signal_info); } JS::RootedValueVector argv(context); @@ -224,12 +215,12 @@ g_error("Unable to reserve space"); JS::RootedValue argv_to_append(context); for (i = 0; i < n_param_values; ++i) { - const GValue *gval = ¶m_values[i]; + const GValue* gval = ¶m_values[i]; + ArgumentDetails& arg_details = args_details[i]; bool no_copy; - int array_len_index; bool res; - if (skip[i]) + if (arg_details.skip) continue; no_copy = false; @@ -238,17 +229,19 @@ no_copy = (signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0; } - array_len_index = array_len_indices_for[i]; - if (array_len_index != -1) { - const GValue *array_len_gval = ¶m_values[array_len_index]; + if (arg_details.array_len_index_for != -1) { + const GValue* array_len_gval = + ¶m_values[arg_details.array_len_index_for]; + ArgumentDetails& array_len_details = + args_details[arg_details.array_len_index_for]; res = gjs_value_from_array_and_length_values( - context, &argv_to_append, &type_info_for[i], gval, - array_len_gval, no_copy, &signal_query, array_len_index); - } else { - res = gjs_value_from_g_value_internal(context, - &argv_to_append, - gval, no_copy, &signal_query, - i); + context, signal_info, &argv_to_append, &arg_details.type_info, + gval, array_len_details.arg_info, &array_len_details.type_info, + array_len_gval, no_copy); + } else { + res = gjs_value_from_g_value_internal( + context, &argv_to_append, gval, no_copy, signal_info, + arg_details.arg_info, &arg_details.type_info); } if (!res) { @@ -272,7 +265,7 @@ // matches the closure exit handling in function.cpp uint8_t code; if (gjs->should_exit(&code)) - exit(code); + gjs->exit_immediately(code); // Some other uncatchable exception, e.g. out of memory JSFunction* fn = JS_GetObjectFunction(callable()); @@ -551,41 +544,25 @@ g_value_set_object(gvalue, gobj); } else if (gtype == G_TYPE_STRV) { - if (value.isNull()) { - /* do nothing */ - } else if (value.isObject()) { - bool found_length; + if (value.isNull()) + return true; - const GjsAtoms& atoms = GjsContextPrivate::atoms(context); - JS::RootedObject array_obj(context, &value.toObject()); - if (!JS_HasPropertyById(context, array_obj, atoms.length(), - &found_length)) - return false; - if (found_length) { - guint32 length; + bool is_array; + if (!JS::IsArrayObject(context, value, &is_array)) + return false; + if (!is_array) + return throw_expect_type(context, value, "strv"); - if (!gjs_object_require_converted_property( - context, array_obj, nullptr, atoms.length(), &length)) { - JS_ClearPendingException(context); - return throw_expect_type(context, value, "strv"); - } else { - void *result; - char **strv; - - if (!gjs_array_to_strv (context, - value, - length, &result)) - return false; - /* cast to strv in a separate step to avoid type-punning */ - strv = (char**) result; - g_value_take_boxed (gvalue, strv); - } - } else { - return throw_expect_type(context, value, "strv"); - } - } else { + JS::RootedObject array_obj(context, &value.toObject()); + uint32_t length; + if (!JS::GetArrayLength(context, array_obj, &length)) return throw_expect_type(context, value, "strv"); - } + + void* result; + if (!gjs_array_to_strv(context, value, length, &result)) + return false; + + g_value_take_boxed(gvalue, static_cast(result)); } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { void *gboxed; @@ -594,7 +571,7 @@ return true; /* special case GValue */ - if (g_type_is_a(gtype, G_TYPE_VALUE)) { + if (gtype == G_TYPE_VALUE) { /* explicitly handle values that are already GValues to avoid infinite recursion */ if (value.isObject()) { @@ -622,15 +599,15 @@ if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); - if (g_type_is_a(gtype, ObjectBox::gtype())) { + if (gtype == ObjectBox::gtype()) { g_value_set_boxed(gvalue, ObjectBox::boxed(context, obj).get()); return true; - } else if (g_type_is_a(gtype, G_TYPE_ERROR)) { + } else if (gtype == G_TYPE_ERROR) { /* special case GError */ gboxed = ErrorBase::to_c_ptr(context, obj); if (!gboxed) return false; - } else if (g_type_is_a(gtype, G_TYPE_BYTE_ARRAY)) { + } else if (gtype == G_TYPE_BYTE_ARRAY) { /* special case GByteArray */ JS::RootedObject obj(context, &value.toObject()); if (JS_IsUint8Array(obj)) { @@ -638,8 +615,22 @@ gjs_byte_array_get_byte_array(obj)); return true; } + } else if (gtype == G_TYPE_ARRAY) { + gjs_throw(context, "Converting %s to GArray is not supported", + JS::InformalValueTypeName(value)); + return false; + } else if (gtype == G_TYPE_PTR_ARRAY) { + gjs_throw(context, "Converting %s to GArray is not supported", + JS::InformalValueTypeName(value)); + return false; + } else if (gtype == G_TYPE_HASH_TABLE) { + gjs_throw(context, + "Converting %s to GHashTable is not supported", + JS::InformalValueTypeName(value)); + return false; } else { - GIBaseInfo *registered = g_irepository_find_by_gtype (NULL, gtype); + GjsAutoBaseInfo registered = + g_irepository_find_by_gtype(nullptr, gtype); /* We don't necessarily have the typelib loaded when we first see the structure... */ @@ -689,7 +680,7 @@ g_value_set_static_boxed(gvalue, gboxed); else g_value_set_boxed(gvalue, gboxed); - } else if (g_type_is_a(gtype, G_TYPE_VARIANT)) { + } else if (gtype == G_TYPE_VARIANT) { GVariant *variant = NULL; if (value.isNull()) { @@ -758,7 +749,7 @@ } g_value_set_param(gvalue, (GParamSpec*) gparam); - } else if (g_type_is_a(gtype, G_TYPE_GTYPE)) { + } else if (gtype == G_TYPE_GTYPE) { GType type; if (!value.isObject()) @@ -856,14 +847,10 @@ } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_value_from_g_value_internal(JSContext *context, - JS::MutableHandleValue value_p, - const GValue *gvalue, - bool no_copy, - GSignalQuery *signal_query, - int arg_n) -{ +static bool gjs_value_from_g_value_internal( + JSContext* context, JS::MutableHandleValue value_p, const GValue* gvalue, + bool no_copy, GjsAutoSignalInfo const& signal_info, + GjsAutoArgInfo const& arg_info, GITypeInfo* type_info) { GType gtype; gtype = G_VALUE_TYPE(gvalue); @@ -872,17 +859,21 @@ "Converting gtype %s to JS::Value", g_type_name(gtype)); + if (gtype != G_TYPE_STRV && g_value_fits_pointer(gvalue) && + g_value_peek_pointer(gvalue) == nullptr) { + // In theory here we should throw if !g_arg_info_may_be_null(arg_info) + // however most signals don't explicitly mark themselves as nullable, + // so better to avoid this. + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Converting NULL %s to JS::NullValue()", + g_type_name(gtype)); + value_p.setNull(); + return true; + } + if (gtype == G_TYPE_STRING) { - const char *v; - v = g_value_get_string(gvalue); - if (v == NULL) { - gjs_debug_marshal(GJS_DEBUG_GCLOSURE, - "Converting NULL string to JS::NullValue()"); - value_p.setNull(); - } else { - if (!gjs_string_from_utf8(context, v, value_p)) - return false; - } + return gjs_string_from_utf8(context, g_value_get_string(gvalue), + value_p); } else if (gtype == G_TYPE_CHAR) { signed char v; v = g_value_get_schar(gvalue); @@ -922,11 +913,9 @@ gjs_throw(context, "Failed to convert strv to array"); return false; } - } else if (g_type_is_a(gtype, G_TYPE_ARRAY) || - g_type_is_a(gtype, G_TYPE_BYTE_ARRAY) || - g_type_is_a(gtype, G_TYPE_PTR_ARRAY)) { - - if (g_type_is_a(gtype, G_TYPE_BYTE_ARRAY)) { + } else if (gtype == G_TYPE_ARRAY || gtype == G_TYPE_BYTE_ARRAY || + gtype == G_TYPE_PTR_ARRAY) { + if (gtype == G_TYPE_BYTE_ARRAY) { auto* byte_array = static_cast(g_value_get_boxed(gvalue)); JSObject* array = gjs_byte_array_from_byte_array(context, byte_array); @@ -939,35 +928,35 @@ return true; } - if (!signal_query) { - gjs_throw(context, "Can't convert untyped array to JS value"); - return false; - } - - GISignalInfo* signal_info = get_signal_info_if_available(signal_query); - if (!signal_info) { + if (!signal_info || !arg_info) { gjs_throw(context, "Unknown signal"); return false; } - // Look for element-type - GITypeInfo type_info; - GIArgInfo* arg_info = g_callable_info_get_arg(signal_info, arg_n - 1); - g_arg_info_load_type(arg_info, &type_info); - GITypeInfo* element_info = g_type_info_get_param_type(&type_info, 0); - + GjsAutoTypeInfo element_info = g_type_info_get_param_type(type_info, 0); if (!gjs_array_from_g_value_array( context, value_p, element_info, g_arg_info_get_ownership_transfer(arg_info), gvalue)) { gjs_throw(context, "Failed to convert array"); return false; } - } else if (g_type_is_a(gtype, G_TYPE_HASH_TABLE)) { - gjs_throw(context, - "Unable to introspect element-type of container in GValue"); - return false; - } else if (g_type_is_a(gtype, G_TYPE_BOXED) || - g_type_is_a(gtype, G_TYPE_VARIANT)) { + } else if (gtype == G_TYPE_HASH_TABLE) { + if (!arg_info) { + gjs_throw(context, "Failed to get GValue from Hash Table without" + "signal information"); + return false; + } + GjsAutoTypeInfo key_info = g_type_info_get_param_type(type_info, 0); + GjsAutoTypeInfo value_info = g_type_info_get_param_type(type_info, 1); + GITransfer transfer = g_arg_info_get_ownership_transfer(arg_info); + + if (!gjs_object_from_g_hash( + context, value_p, key_info, value_info, transfer, + static_cast(g_value_get_boxed(gvalue)))) { + gjs_throw(context, "Failed to convert Hash Table"); + return false; + } + } else if (g_type_is_a(gtype, G_TYPE_BOXED) || gtype == G_TYPE_VARIANT) { void *gboxed; JSObject *obj; @@ -976,14 +965,7 @@ else gboxed = g_value_get_variant(gvalue); - if (!gboxed) { - gjs_debug_marshal(GJS_DEBUG_GCLOSURE, - "Converting null boxed pointer to JS::Value"); - value_p.setNull(); - return true; - } - - if (g_type_is_a(gtype, ObjectBox::gtype())) { + if (gtype == ObjectBox::gtype()) { obj = ObjectBox::object_for_c_ptr(context, static_cast(gboxed)); if (!obj) @@ -993,7 +975,7 @@ } /* special case GError */ - if (g_type_is_a(gtype, G_TYPE_ERROR)) { + if (gtype == G_TYPE_ERROR) { obj = ErrorInstance::object_for_c_ptr(context, static_cast(gboxed)); if (!obj) @@ -1003,7 +985,7 @@ } /* special case GValue */ - if (g_type_is_a(gtype, G_TYPE_VALUE)) { + if (gtype == G_TYPE_VALUE) { return gjs_value_from_g_value(context, value_p, static_cast(gboxed)); } @@ -1052,33 +1034,22 @@ obj = gjs_param_from_g_param(context, gparam); value_p.setObjectOrNull(obj); - } else if (signal_query && g_type_is_a(gtype, G_TYPE_POINTER)) { - bool res; + } else if (signal_info && g_type_is_a(gtype, G_TYPE_POINTER)) { GArgument arg; - GIArgInfo *arg_info; - GISignalInfo *signal_info; - GITypeInfo type_info; - signal_info = get_signal_info_if_available(signal_query); - if (!signal_info) { + if (!arg_info) { gjs_throw(context, "Unknown signal."); return false; } - arg_info = g_callable_info_get_arg(signal_info, arg_n - 1); - g_arg_info_load_type(arg_info, &type_info); - - g_assert(((void) "Check gjs_value_from_array_and_length_values() before" - " calling gjs_value_from_g_value_internal()", - g_type_info_get_array_length(&type_info) == -1)); + g_assert(((void)"Check gjs_value_from_array_and_length_values() before" + " calling gjs_value_from_g_value_internal()", + g_type_info_get_array_length(type_info) == -1)); gjs_arg_set(&arg, g_value_get_pointer(gvalue)); - res = gjs_value_from_g_argument(context, value_p, &type_info, &arg, true); - - g_base_info_unref((GIBaseInfo*)arg_info); - g_base_info_unref((GIBaseInfo*)signal_info); - return res; + return gjs_value_from_g_argument(context, value_p, type_info, &arg, + true); } else if (gtype == G_TYPE_GTYPE) { GType gvalue_gtype = g_value_get_gtype(gvalue); @@ -1094,13 +1065,7 @@ value_p.setObject(*obj); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { - gpointer pointer; - - pointer = g_value_get_pointer(gvalue); - - if (pointer == NULL) { - value_p.setNull(); - } else { + if (g_value_get_pointer(gvalue) != nullptr) { gjs_throw(context, "Can't convert non-null pointer to JS value"); return false; @@ -1143,5 +1108,5 @@ JS::MutableHandleValue value_p, const GValue *gvalue) { - return gjs_value_from_g_value_internal(context, value_p, gvalue, false, NULL, 0); + return gjs_value_from_g_value_internal(context, value_p, gvalue, false); } diff -Nru gjs-1.76.2/gi/value.h gjs-1.78.0/gi/value.h --- gjs-1.76.2/gi/value.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/value.h 2023-09-17 02:27:20.000000000 +0000 @@ -18,9 +18,8 @@ namespace Gjs { struct AutoGValue : GValue { - AutoGValue() { + AutoGValue() : GValue(G_VALUE_INIT) { static_assert(sizeof(AutoGValue) == sizeof(GValue)); - *static_cast(this) = G_VALUE_INIT; } explicit AutoGValue(GType gtype) : AutoGValue() { g_value_init(this, gtype); diff -Nru gjs-1.76.2/gi/wrapperutils.h gjs-1.78.0/gi/wrapperutils.h --- gjs-1.76.2/gi/wrapperutils.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gi/wrapperutils.h 2023-09-17 02:27:20.000000000 +0000 @@ -19,6 +19,7 @@ #include #include +#include // for JSEXN_TYPEERR #include #include #include @@ -26,8 +27,7 @@ #include #include #include -#include // for JS_GetPrototype -#include // for JSProto_TypeError +#include // for JS_GetPrototype #include "gi/arg-inl.h" #include "gi/cwrapper.h" @@ -635,12 +635,12 @@ if (expected_info) { gjs_throw_custom( - cx, JSProto_TypeError, nullptr, + cx, JSEXN_TYPEERR, nullptr, "Object is of type %s.%s - cannot convert to %s.%s", priv->ns(), priv->name(), g_base_info_get_namespace(expected_info), g_base_info_get_name(expected_info)); } else { - gjs_throw_custom(cx, JSProto_TypeError, nullptr, + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object is of type %s.%s - cannot convert to %s", priv->ns(), priv->name(), g_type_name(expected_gtype)); diff -Nru gjs-1.76.2/.gitlab-ci.yml gjs-1.78.0/.gitlab-ci.yml --- gjs-1.76.2/.gitlab-ci.yml 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/.gitlab-ci.yml 2023-09-17 02:27:20.000000000 +0000 @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2017 Claudio André --- include: - - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/c5626190ec14b475271288dda7a7dae8dbe0cd76/templates/alpine.yml' + - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/b791bd48996e3ced9ca13f1c5ee82be8540b8adb/templates/alpine.yml' stages: - prepare @@ -31,7 +31,7 @@ mkdir -p /cwd .coverage: &coverage - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: coverage: '/^ lines.*(\d+\.\d+\%)/' script: @@ -62,7 +62,6 @@ paths: - _build/compile_commands.json - _build/meson-logs/*log*.txt - - scripts.log script: - test/test-ci.sh SETUP - test/test-ci.sh BUILD @@ -75,7 +74,7 @@ build_recommended: <<: *build stage: source_check - image: registry.gitlab.gnome.org/gnome/gjs:job-2382991_fedora.mozjs102-debug # pinned on purpose + image: registry.gitlab.gnome.org/gnome/gjs:job-3012153_fedora.mozjs115-debug # pinned on purpose variables: TEST_OPTS: --verbose --no-stdsplit --print-errorlogs --setup=verbose except: @@ -86,10 +85,13 @@ stage: test tags: - asan # LSAN needs CAP_SYS_PTRACE - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: CONFIG_OPTS: -Db_sanitize=address,undefined - TEST_OPTS: --timeout-multiplier=3 --setup=verbose + TEST_OPTS: --timeout-multiplier=3 + # Override these during build, but they are overridden by meson anyways + ASAN_OPTIONS: start_deactivated=true,detect_leaks=0 + USE_UNSTABLE_GNOME_PREFIX: 'true' except: - schedules @@ -100,10 +102,11 @@ allow_failure: true tags: - asan # TSAN needs CAP_SYS_PTRACE - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: CONFIG_OPTS: -Db_sanitize=thread TEST_OPTS: --timeout-multiplier=3 --setup=verbose + USE_UNSTABLE_GNOME_PREFIX: 'true' except: - schedules @@ -111,12 +114,13 @@ # These sometimes get invalid expressions in them, leading to annoyance the # next time you try to use debug logging. build_maximal: - <<: *build + when: on_success stage: test - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: CC: clang CXX: clang++ + USE_UNSTABLE_GNOME_PREFIX: 'true' CONFIG_OPTS: >- -Ddtrace=true -Dsystemtap=true -Dverbose_logs=true -Db_pch=false ENABLE_GTK: "yes" @@ -126,11 +130,20 @@ - test/test-ci.sh SETUP - test/test-ci.sh BUILD - test/test-ci.sh SH_CHECKS + artifacts: + reports: + junit: _build/meson-logs/testlog*.junit.xml + name: log + when: always + paths: + - _build/compile_commands.json + - _build/meson-logs/*log*.txt + - scripts.log build_minimal: <<: *build stage: test - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102 + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115 variables: CONFIG_OPTS: >- -Dbuildtype=release @@ -142,7 +155,7 @@ build_unity: <<: *build stage: test - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102 + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115 variables: # unity-size here is forced to use an high number to check whether we can # join all the sources together, but should not be used in real world to @@ -278,7 +291,7 @@ iwyu: when: on_success stage: source_check - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug script: - test/test-ci.sh UPSTREAM_BASE - meson setup _build -Db_pch=false @@ -302,7 +315,7 @@ - | codespell -S "*.png,*.po,*.jpg,*.wrap,.git,LICENSES" -f \ --builtin "code,usage,clear" \ - --skip="./installed-tests/js/jasmine.js,./README.md,./build/flatpak/*.json,./tools/package-lock.json" \ + --skip="./build/maintainer-upload-release.sh,./installed-tests/js/jasmine.js,./README.md,./build/flatpak/*.json,./tools/package-lock.json" \ --ignore-words-list="afterall,deque,falsy,files',filetest,gir,inout,musl,nmake,stdio,uint,upto,xdescribe" except: - schedules @@ -333,7 +346,7 @@ iwyu-full: when: manual stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug script: - meson setup _build - ./tools/run_iwyu.sh @@ -345,10 +358,13 @@ stage: manual tags: - asan # LSAN needs CAP_SYS_PTRACE - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: CC: clang CXX: clang++ + USE_UNSTABLE_GNOME_PREFIX: 'true' + # Override these during build, but they are overridden by meson anyways + ASAN_OPTIONS: start_deactivated=true,detect_leaks=0 CONFIG_OPTS: -Db_sanitize=address,undefined -Db_lundef=false TEST_OPTS: --timeout-multiplier=3 --setup=verbose when: manual @@ -358,7 +374,7 @@ installed_tests: <<: *build stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: CONFIG_OPTS: -Dinstalled_tests=true -Dprefix=/usr TEST: skip @@ -374,8 +390,9 @@ valgrind: <<: *build stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: + USE_UNSTABLE_GNOME_PREFIX: 'true' TEST_OPTS: --setup=valgrind allow_failure: true when: manual @@ -386,7 +403,7 @@ zeal_2: <<: *build stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: TEST_OPTS: --setup=extra_gc when: manual @@ -396,7 +413,7 @@ zeal_4: <<: *build stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: TEST_OPTS: --setup=pre_verify when: manual @@ -406,7 +423,7 @@ zeal_11: <<: *build stage: manual - image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs102-debug + image: registry.gitlab.gnome.org/gnome/gjs:fedora.mozjs115-debug variables: TEST_OPTS: --setup=post_verify when: manual @@ -417,7 +434,7 @@ # Create CI Docker Images # ############################################# .Docker image template: &create_docker_image - image: quay.io/freedesktop.org/ci-templates:container-build-base-2022-05-25.0 + image: quay.io/freedesktop.org/ci-templates:container-build-base-2023-06-27.1 stage: deploy only: variables: @@ -459,3 +476,17 @@ variables: <<: *docker_variables DOCKERFILE: test/extra/Dockerfile.debug + +fedora.mozjs115: + <<: *create_docker_image + variables: + <<: *docker_variables + DOCKERFILE: test/extra/Dockerfile + ARGS: --build-arg MOZJS_BRANCH=mozjs115 --build-arg MOZJS_BUILDDEPS=mozjs102 + +fedora.mozjs115-debug: + <<: *create_docker_image + variables: + <<: *docker_variables + DOCKERFILE: test/extra/Dockerfile.debug + ARGS: --build-arg MOZJS_BRANCH=mozjs115 --build-arg MOZJS_BUILDDEPS=mozjs102 diff -Nru gjs-1.76.2/gjs/console.cpp gjs-1.78.0/gjs/console.cpp --- gjs-1.76.2/gjs/console.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/console.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -127,7 +127,7 @@ check_script_args_for_stray_gjs_args(int argc, char * const *argv) { - GjsAutoError error = NULL; + GjsAutoError error; GjsAutoStrv new_coverage_prefixes; GjsAutoChar new_coverage_output_path; GjsAutoStrv new_include_paths; @@ -155,8 +155,7 @@ g_option_context_set_ignore_unknown_options(script_options, true); g_option_context_set_help_enabled(script_options, false); g_option_context_add_main_entries(script_options, script_check_entries, NULL); - if (!g_option_context_parse_strv(script_options, argv_copy.out(), - error.out())) { + if (!g_option_context_parse_strv(script_options, argv_copy.out(), &error)) { g_warning("Scanning script arguments failed: %s", error->message); return; } @@ -190,7 +189,7 @@ size_t len, const char* filename) { gjs_context_set_argv(js_context, argc, const_cast(argv)); - GError* error = nullptr; + GjsAutoError error; /* evaluate the script */ int code = 0; if (exec_as_module) { @@ -198,7 +197,6 @@ GjsAutoChar uri = g_file_get_uri(output); if (!gjs_context_register_module(js_context, uri, uri, &error)) { g_critical("%s", error->message); - g_clear_error(&error); code = 1; } @@ -209,13 +207,11 @@ if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT)) g_critical("%s", error->message); - g_clear_error(&error); } } else if (!gjs_context_eval(js_context, script, len, filename, &code, &error)) { if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT)) g_critical("%s", error->message); - g_clear_error(&error); } return code; } @@ -240,7 +236,7 @@ char** argv_copy = argv_copy_addr; g_option_context_add_main_entries(context, entries, NULL); - if (!g_option_context_parse_strv(context, &argv_copy, error.out())) + if (!g_option_context_parse_strv(context, &argv_copy, &error)) g_error("option parsing failed: %s", error->message); /* Split options so we pass unknown ones through to the JS script */ @@ -276,7 +272,7 @@ exec_as_module = false; g_option_context_set_ignore_unknown_options(context, false); g_option_context_set_help_enabled(context, true); - if (!g_option_context_parse_strv(context, &gjs_argv, error.out())) { + if (!g_option_context_parse_strv(context, &gjs_argv, &error)) { GjsAutoChar help_text = g_option_context_get_help(context, true, nullptr); g_printerr("%s\n\n%s\n", error->message, help_text.get()); @@ -316,10 +312,9 @@ } else { /* All unprocessed options should be in script_argv */ g_assert(gjs_argc == 2); - error = NULL; GjsAutoUnref input = g_file_new_for_commandline_arg(gjs_argv[1]); if (!g_file_load_contents(input, nullptr, script.out(), &len, nullptr, - error.out())) { + &error)) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } diff -Nru gjs-1.76.2/gjs/context.cpp gjs-1.78.0/gjs/context.cpp --- gjs-1.76.2/gjs/context.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/context.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -6,8 +6,9 @@ #include // for sigaction, SIGUSR1, sa_handler #include -#include // for FILE, fclose, size_t -#include // for memset +#include // for FILE, fclose, size_t +#include // for exit +#include // for memset #ifdef HAVE_UNISTD_H # include // for getpid @@ -18,7 +19,6 @@ #ifdef DEBUG # include // for find #endif -#include #include #include // for u16string #include // for get_id @@ -48,6 +48,7 @@ #include // for WeakCache #include // for RootedVector #include // for CurrentGlobalOrNull +#include // for DefaultHasher via WeakCache #include #include #include // for JobQueue::SavedJobQueue @@ -60,6 +61,7 @@ #include #include #include +#include // for DeletePolicy via WeakCache #include #include #include @@ -91,8 +93,13 @@ #include "gjs/profiler.h" #include "gjs/promise.h" #include "gjs/text-encoding.h" -#include "modules/modules.h" +#include "modules/console.h" +#include "modules/print.h" +#include "modules/system.h" #include "util/log.h" +#ifdef ENABLE_CAIRO +# include "modules/cairo-module.h" +#endif namespace mozilla { union Utf8Unit; @@ -123,14 +130,8 @@ GObject parent; }; -struct _GjsContextClass { - GObjectClass parent; -}; - G_DEFINE_TYPE_WITH_PRIVATE(GjsContext, gjs_context, G_TYPE_OBJECT); -Gjs::NativeModuleRegistry& registry = Gjs::NativeModuleRegistry::get(); - GjsContextPrivate* GjsContextPrivate::from_object(GObject* js_context) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr); return static_cast( @@ -338,13 +339,18 @@ #endif g_irepository_prepend_search_path(priv_typelib_dir); } + auto& registry = Gjs::NativeModuleDefineFuncs::get(); registry.add("_promiseNative", gjs_define_native_promise_stuff); registry.add("_byteArrayNative", gjs_define_byte_array_stuff); registry.add("_encodingNative", gjs_define_text_encoding_stuff); registry.add("_gi", gjs_define_private_gi_stuff); registry.add("gi", gjs_define_repo); - - gjs_register_static_modules(); +#ifdef ENABLE_CAIRO + registry.add("cairoNative", gjs_js_define_cairo_stuff); +#endif + registry.add("system", gjs_js_define_system_stuff); + registry.add("console", gjs_define_console_stuff); + registry.add("_print", gjs_define_print_stuff); } void GjsContextPrivate::trace(JSTracer* trc, void* data) { @@ -604,7 +610,7 @@ g_error("Failed to load %s module.", debug_identifier); } - if (!JS::ModuleInstantiate(cx, loader)) { + if (!JS::ModuleLink(cx, loader)) { gjs_log_exception(cx); g_error("Failed to instantiate %s module.", debug_identifier); } @@ -647,6 +653,7 @@ m_cx(cx), m_owner_thread(std::this_thread::get_id()), m_dispatcher(this), + m_memory_monitor(g_memory_monitor_dup_default()), m_environment_preparer(cx) { JS_SetGCCallback( cx, @@ -688,9 +695,8 @@ g_error("Failed to initialize internal global object"); } - JSAutoRealm ar(m_cx, internal_global); - m_internal_global = internal_global; + Gjs::AutoInternalRealm ar{this}; JS_AddExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this); if (!m_atoms->init_atoms(m_cx)) { @@ -717,7 +723,7 @@ m_global = global; { - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{this}; std::vector paths; if (m_search_path) @@ -759,7 +765,7 @@ } { - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{this}; load_context_module( cx, "resource:///org/gnome/gjs/modules/esm/_bootstrap/default.js", "ESM bootstrap"); @@ -768,6 +774,19 @@ [[maybe_unused]] bool success = m_main_loop.spin(this); g_assert(success && "bootstrap should not call system.exit()"); + g_signal_connect_object( + m_memory_monitor, "low-memory-warning", + G_CALLBACK(+[](GjsContext* js_cx, GMemoryMonitorWarningLevel level) { + auto* cx = + static_cast(gjs_context_get_native_context(js_cx)); + JS::PrepareForFullGC(cx); + JS::GCOptions gc_strength = JS::GCOptions::Normal; + if (level > G_MEMORY_MONITOR_WARNING_LEVEL_LOW) + gc_strength = JS::GCOptions::Shrink; + JS::NonIncrementalGC(cx, gc_strength, Gjs::GCReason::LOW_MEMORY); + }), + m_public_context, G_CONNECT_SWAPPED); + start_draining_job_queue(); } @@ -1009,6 +1028,12 @@ return m_should_exit; } +void GjsContextPrivate::exit_immediately(uint8_t exit_code) { + warn_about_unhandled_promise_rejections(); + + ::exit(exit_code); +} + void GjsContextPrivate::start_draining_job_queue(void) { m_dispatcher.start(); } void GjsContextPrivate::stop_draining_job_queue(void) { @@ -1314,7 +1339,7 @@ (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2)) auto_profile = false; - JSAutoRealm ar(m_cx, m_global); + Gjs::AutoMainRealm ar{this}; if (auto_profile) gjs_profiler_start(m_profiler); @@ -1411,7 +1436,7 @@ bool auto_profile = auto_profile_enter(); - JSAutoRealm ar(m_cx, m_global); + Gjs::AutoMainRealm ar{this}; JS::RootedValue retval(m_cx); bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval); @@ -1477,7 +1502,7 @@ bool auto_profile = auto_profile_enter(); - JSAutoRealm ac(m_cx, m_global); + Gjs::AutoMainRealm ar{this}; JS::RootedObject registry(m_cx, gjs_get_module_registry(m_global)); JS::RootedId key(m_cx, gjs_intern_string_to_id(m_cx, identifier)); @@ -1491,7 +1516,7 @@ return false; } - if (!JS::ModuleInstantiate(m_cx, obj)) { + if (!JS::ModuleLink(m_cx, obj)) { gjs_log_exception(m_cx); g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "Failed to resolve imports for module: '%s'", identifier); @@ -1554,7 +1579,7 @@ bool GjsContextPrivate::register_module(const char* identifier, const char* uri, GError** error) { - JSAutoRealm ar(m_cx, m_global); + Gjs::AutoMainRealm ar{this}; if (gjs_module_load(m_cx, identifier, uri)) return true; @@ -1707,7 +1732,7 @@ g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); - JSAutoRealm ar(gjs->context(), gjs->global()); + Gjs::AutoMainRealm ar{gjs}; std::vector strings; if (array_values) { @@ -1761,6 +1786,44 @@ current_context = context; } +namespace Gjs { +/* + * Gjs::AutoMainRealm: + * @gjs: a #GjsContextPrivate + * + * Enters the realm of the "main global" for the context, and leaves when the + * object is destructed at the end of the scope. The main global is the global + * object under which all user JS code is executed. It is used as the root + * object for the scope of modules loaded by GJS in this context. + * + * Only code in a different realm, such as the debugger code or the module + * loader code, uses a different global object. + */ +AutoMainRealm::AutoMainRealm(GjsContextPrivate* gjs) + : JSAutoRealm(gjs->context(), gjs->global()) {} + +AutoMainRealm::AutoMainRealm(JSContext* cx) + : AutoMainRealm(static_cast(JS_GetContextPrivate(cx))) { +} + +/* + * Gjs::AutoInternalRealm: + * @gjs: a #GjsContextPrivate + * + * Enters the realm of the "internal global" for the context, and leaves when + * the object is destructed at the end of the scope. The internal global is only + * used for executing the module loader code, to keep it separate from user + * code. + */ +AutoInternalRealm::AutoInternalRealm(GjsContextPrivate* gjs) + : JSAutoRealm(gjs->context(), gjs->internal_global()) {} + +AutoInternalRealm::AutoInternalRealm(JSContext* cx) + : AutoInternalRealm( + static_cast(JS_GetContextPrivate(cx))) {} + +} // namespace Gjs + /** * gjs_context_get_profiler: * @self: the #GjsContext @@ -1788,3 +1851,25 @@ { return JS_GetImplementationVersion(); } + +/** + * gjs_context_run_in_realm: + * @self: the #GjsContext + * @func: (scope call): function to run + * @user_data: (closure func): pointer to pass to @func + * + * Runs @func immediately, not asynchronously, after entering the JS context's + * main realm. After @func completes, leaves the realm again. + * + * You only need this if you are using JSAPI calls from the SpiderMonkey API + * directly. + */ +void gjs_context_run_in_realm(GjsContext* self, GjsContextInRealmFunc func, + void* user_data) { + g_return_if_fail(GJS_IS_CONTEXT(self)); + g_return_if_fail(func); + + GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); + Gjs::AutoMainRealm ar{gjs}; + func(self, user_data); +} diff -Nru gjs-1.76.2/gjs/context.h gjs-1.78.0/gjs/context.h --- gjs-1.76.2/gjs/context.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/context.h 2023-09-17 02:27:20.000000000 +0000 @@ -27,17 +27,18 @@ G_BEGIN_DECLS -typedef struct _GjsContext GjsContext; -typedef struct _GjsContextClass GjsContextClass; - #define GJS_TYPE_CONTEXT (gjs_context_get_type ()) -#define GJS_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GJS_TYPE_CONTEXT, GjsContext)) + +GJS_EXPORT GJS_USE G_DECLARE_FINAL_TYPE(GjsContext, gjs_context, GJS, CONTEXT, + GObject); + +/* These class macros are not defined by G_DECLARE_FINAL_TYPE, but are kept for + * backwards compatibility */ #define GJS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GJS_TYPE_CONTEXT, GjsContextClass)) -#define GJS_IS_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GJS_TYPE_CONTEXT)) #define GJS_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GJS_TYPE_CONTEXT)) #define GJS_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GJS_TYPE_CONTEXT, GjsContextClass)) -GJS_EXPORT GJS_USE GType gjs_context_get_type(void) G_GNUC_CONST; +typedef void (*GjsContextInRealmFunc)(GjsContext*, void*); GJS_EXPORT GJS_USE GjsContext* gjs_context_new(void); GJS_EXPORT GJS_USE GjsContext* gjs_context_new_with_search_path( @@ -79,6 +80,10 @@ GJS_EXPORT void* gjs_context_get_native_context (GjsContext *js_context); +GJS_EXPORT void gjs_context_run_in_realm(GjsContext* gjs, + GjsContextInRealmFunc func, + void* user_data); + GJS_EXPORT void gjs_context_print_stack_stderr (GjsContext *js_context); diff -Nru gjs-1.76.2/gjs/context-private.h gjs-1.78.0/gjs/context-private.h --- gjs-1.76.2/gjs/context-private.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/context-private.h 2023-09-17 02:27:20.000000000 +0000 @@ -17,6 +17,7 @@ #include // for pair #include +#include // for GMemoryMonitor #include #include @@ -27,12 +28,12 @@ #include #include // for DefaultHasher #include +#include #include #include #include #include #include // for ScriptEnvironmentPreparer -#include #include "gi/closure.h" #include "gjs/context.h" @@ -82,6 +83,7 @@ JobQueueStorage m_job_queue; Gjs::PromiseJobDispatcher m_dispatcher; Gjs::MainLoop m_main_loop; + GjsAutoUnref m_memory_monitor; std::vector> m_destroy_notifications; std::vector m_async_closures; @@ -139,6 +141,8 @@ void start_draining_job_queue(void); void stop_draining_job_queue(void); + void warn_about_unhandled_promise_rejections(); + GJS_JSAPI_RETURN_CONVENTION bool run_main_loop_hook(); [[nodiscard]] bool handle_exit_code(bool no_sync_error_pending, const char* source_type, @@ -216,6 +220,9 @@ [[nodiscard]] static const GjsAtoms& atoms(JSContext* cx) { return *(from_cx(cx)->m_atoms); } + [[nodiscard]] static JSObject* global(JSContext* cx) { + return from_cx(cx)->global(); + } [[nodiscard]] bool eval(const char* script, size_t script_len, const char* filename, int* exit_status_p, @@ -237,6 +244,7 @@ void report_unhandled_exception() { m_unhandled_exception = true; } void exit(uint8_t exit_code); [[nodiscard]] bool should_exit(uint8_t* exit_code_p) const; + [[noreturn]] void exit_immediately(uint8_t exit_code); // Implementations of JS::JobQueue virtual functions GJS_JSAPI_RETURN_CONVENTION @@ -254,7 +262,6 @@ GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(); void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack); void unregister_unhandled_promise_rejection(uint64_t id); - void warn_about_unhandled_promise_rejections(); void register_notifier(DestroyNotify notify_func, void* data); void unregister_notifier(DestroyNotify notify_func, void* data); @@ -271,4 +278,21 @@ void free_profiler(void); void dispose(void); }; + +std::string gjs_dumpstack_string(); + +namespace Gjs { +class AutoMainRealm : public JSAutoRealm { + public: + explicit AutoMainRealm(GjsContextPrivate* gjs); + explicit AutoMainRealm(JSContext* cx); +}; + +class AutoInternalRealm : public JSAutoRealm { + public: + explicit AutoInternalRealm(GjsContextPrivate* gjs); + explicit AutoInternalRealm(JSContext* cx); +}; +} // namespace Gjs + #endif // GJS_CONTEXT_PRIVATE_H_ diff -Nru gjs-1.76.2/gjs/coverage.cpp gjs-1.78.0/gjs/coverage.cpp --- gjs-1.76.2/gjs/coverage.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/coverage.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -273,15 +273,14 @@ gjs_coverage_write_statistics(GjsCoverage *coverage) { auto priv = static_cast(gjs_coverage_get_instance_private(coverage)); - GError *error = nullptr; + GjsAutoError error; auto cx = static_cast(gjs_context_get_native_context(priv->context)); - JSAutoRealm ar(cx, gjs_get_import_global(cx)); + Gjs::AutoMainRealm ar{cx}; GjsAutoUnref output_file = write_statistics_internal(coverage, cx, &error); if (!output_file) { g_critical("Error writing coverage data: %s", error->message); - g_error_free(error); return; } @@ -311,21 +310,20 @@ { GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); - JSContext *context = (JSContext *) gjs_context_get_native_context(priv->context); + auto* gjs = GjsContextPrivate::from_object(priv->context); + JSContext* context = gjs->context(); - JSObject *debuggee = gjs_get_import_global(context); JS::RootedObject debugger_global( context, gjs_create_global_object(context, GjsGlobalType::DEBUGGER)); { JSAutoRealm ar(context, debugger_global); - JS::RootedObject debuggeeWrapper(context, debuggee); - if (!JS_WrapObject(context, &debuggeeWrapper)) + JS::RootedObject debuggee{context, gjs->global()}; + if (!JS_WrapObject(context, &debuggee)) return false; - const GjsAtoms& atoms = GjsContextPrivate::atoms(context); - JS::RootedValue debuggeeWrapperValue(context, JS::ObjectValue(*debuggeeWrapper)); - if (!JS_SetPropertyById(context, debugger_global, atoms.debuggee(), - debuggeeWrapperValue) || + JS::RootedValue v_debuggee{context, JS::ObjectValue(*debuggee)}; + if (!JS_SetPropertyById(context, debugger_global, + gjs->atoms().debuggee(), v_debuggee) || !gjs_define_global_properties(context, debugger_global, GjsGlobalType::DEBUGGER, "GJS coverage", "coverage")) @@ -351,7 +349,7 @@ if (!bootstrap_coverage(coverage)) { JSContext *context = static_cast(gjs_context_get_native_context(priv->context)); - JSAutoRealm ar(context, gjs_get_import_global(context)); + Gjs::AutoMainRealm ar{context}; gjs_log_exception(context); } } diff -Nru gjs-1.76.2/gjs/debugger.cpp gjs-1.78.0/gjs/debugger.cpp --- gjs-1.76.2/gjs/debugger.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/debugger.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -107,24 +107,24 @@ }; // clang-format on -void gjs_context_setup_debugger_console(GjsContext* gjs) { - auto cx = static_cast(gjs_context_get_native_context(gjs)); +void gjs_context_setup_debugger_console(GjsContext* self) { + auto* gjs = GjsContextPrivate::from_object(self); + JSContext* cx = gjs->context(); - JS::RootedObject debuggee(cx, gjs_get_import_global(cx)); JS::RootedObject debugger_global( cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER)); // Enter realm of the debugger and initialize it with the debuggee JSAutoRealm ar(cx, debugger_global); - JS::RootedObject debuggee_wrapper(cx, debuggee); - if (!JS_WrapObject(cx, &debuggee_wrapper)) { + JS::RootedObject debuggee{cx, gjs->global()}; + if (!JS_WrapObject(cx, &debuggee)) { gjs_log_exception(cx); return; } - const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); - JS::RootedValue v_wrapper(cx, JS::ObjectValue(*debuggee_wrapper)); - if (!JS_SetPropertyById(cx, debugger_global, atoms.debuggee(), v_wrapper) || + JS::RootedValue v_debuggee(cx, JS::ObjectValue(*debuggee)); + if (!JS_SetPropertyById(cx, debugger_global, gjs->atoms().debuggee(), + v_debuggee) || !JS_DefineFunctions(cx, debugger_global, debugger_funcs) || !gjs_define_global_properties(cx, debugger_global, GjsGlobalType::DEBUGGER, "GJS debugger", diff -Nru gjs-1.76.2/gjs/deprecation.cpp gjs-1.78.0/gjs/deprecation.cpp --- gjs-1.76.2/gjs/deprecation.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/deprecation.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -6,10 +6,12 @@ #include // for size_t #include // for hash +#include #include // for string -#include // for hash +#include #include // for unordered_set #include // for move +#include #include // for g_warning @@ -38,7 +40,15 @@ "(Note that array.toString() may have been called implicitly.)", // DeprecatedGObjectProperty: - "Some code tried to set a deprecated GObject property.", + "The GObject property {}.{} is deprecated.", + + // ModuleExportedLetOrConst: + "Some code accessed the property '{}' on the module '{}'. That property " + "was defined with 'let' or 'const' inside the module. This was previously " + "supported, but is not correct according to the ES6 standard. Any symbols " + "to be exported from a module must be defined with 'var'. The property " + "access will work as previously for the time being, but please fix your " + "code anyway.", }; struct DeprecationEntry { @@ -80,17 +90,57 @@ return JS_EncodeStringToUTF8(cx, frame_string); } -/* Note, this can only be called from the JS thread because it uses the full - * stack dump API and not the "safe" gjs_dumpstack() which can only print to - * stdout or stderr. Do not use this function during GC, for example. */ -void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, - const GjsDeprecationMessageId id) { +static void warn_deprecated_unsafe_internal(JSContext* cx, + const GjsDeprecationMessageId id, + const char* msg) { JS::UniqueChars callsite(get_callsite(cx)); DeprecationEntry entry(id, callsite.get()); if (!logged_messages.count(entry)) { JS::UniqueChars stack_dump = JS::FormatStackDump(cx, false, false, false); - g_warning("%s\n%s", messages[id], stack_dump.get()); + g_warning("%s\n%s", msg, stack_dump.get()); logged_messages.insert(std::move(entry)); } } + +/* Note, this can only be called from the JS thread because it uses the full + * stack dump API and not the "safe" gjs_dumpstack() which can only print to + * stdout or stderr. Do not use this function during GC, for example. */ +void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, + const GjsDeprecationMessageId id) { + warn_deprecated_unsafe_internal(cx, id, messages[id]); +} + +void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, + GjsDeprecationMessageId id, + std::vector args) { + // In C++20, use std::format() for this + std::string_view format_string{messages[id]}; + std::stringstream message; + + size_t pos = 0; + size_t copied = 0; + size_t args_ptr = 0; + size_t nargs_given = args.size(); + + while ((pos = format_string.find("{}", pos)) != std::string::npos) { + if (args_ptr >= nargs_given) { + g_critical("Only %zu format args passed for message ID %u", + nargs_given, id); + return; + } + + message << format_string.substr(copied, pos - copied); + message << args[args_ptr++]; + pos = copied = pos + 2; // skip over braces + } + if (args_ptr != nargs_given) { + g_critical("Excess %zu format args passed for message ID %u", + nargs_given, id); + return; + } + + message << format_string.substr(copied, std::string::npos); + + warn_deprecated_unsafe_internal(cx, id, message.str().c_str()); +} diff -Nru gjs-1.76.2/gjs/deprecation.h gjs-1.78.0/gjs/deprecation.h --- gjs-1.76.2/gjs/deprecation.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/deprecation.h 2023-09-17 02:27:20.000000000 +0000 @@ -5,15 +5,22 @@ #ifndef GJS_DEPRECATION_H_ #define GJS_DEPRECATION_H_ +#include + struct JSContext; enum GjsDeprecationMessageId { None, ByteArrayInstanceToString, DeprecatedGObjectProperty, + ModuleExportedLetOrConst, }; void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, GjsDeprecationMessageId message); +void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, + GjsDeprecationMessageId id, + std::vector args); + #endif // GJS_DEPRECATION_H_ diff -Nru gjs-1.76.2/gjs/engine.cpp gjs-1.78.0/gjs/engine.cpp --- gjs-1.76.2/gjs/engine.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/engine.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -58,7 +58,7 @@ bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src, size_t* length) { - GError* error = nullptr; + GjsAutoError error; const char* path = filename + 11; // len("resource://") GBytes* script_bytes = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); diff -Nru gjs-1.76.2/gjs/gjs_pch.hh gjs-1.78.0/gjs/gjs_pch.hh --- gjs-1.76.2/gjs/gjs_pch.hh 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/gjs_pch.hh 2023-09-17 02:27:20.000000000 +0000 @@ -80,6 +80,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ #include #include #include +#include #include #include #include @@ -117,11 +119,12 @@ #include #include #include +#include +#include +#include #include -#include #include #include -#include #ifdef HAVE_READLINE_READLINE_H #include #include diff -Nru gjs-1.76.2/gjs/global.cpp gjs-1.78.0/gjs/global.cpp --- gjs-1.76.2/gjs/global.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/global.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -51,8 +51,9 @@ // Enable WeakRef without the cleanupSome specification // Re-evaluate if cleanupSome is standardized // See: https://github.com/tc39/proposal-cleanup-some - options.setWeakRefsEnabled( - JS::WeakRefSpecifier::EnabledWithoutCleanupSome); + options + .setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithoutCleanupSome) + .setChangeArrayByCopyEnabled(true); JS::RealmBehaviors behaviors; JS::RealmOptions compartment_options(options, behaviors); @@ -132,8 +133,8 @@ JS::RootedObject native_obj(m_cx); - if (!Gjs::NativeModuleRegistry::get().load(m_cx, id.get(), - &native_obj)) { + if (!Gjs::NativeModuleDefineFuncs::get().define(m_cx, id.get(), + &native_obj)) { gjs_throw(m_cx, "Failed to load native module: %s", id.get()); return false; } @@ -450,10 +451,10 @@ /** * gjs_global_registry_get: * - * @brief This function inserts a module object into a global registry. + * @brief This function retrieves a module record from the global registry, + * or %NULL if the module record is not present. * Global registries are JS Map objects for easy reuse and access - * within internal JS. This function will assert if a module has - * already been inserted at the given key. + * within internal JS. * @param cx the current #JSContext * @param registry a JS Map object diff -Nru gjs-1.76.2/gjs/importer.cpp gjs-1.78.0/gjs/importer.cpp --- gjs-1.76.2/gjs/importer.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/importer.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -22,7 +22,7 @@ #include #include #include -#include // for JS_ReportOutOfMemory +#include // for JS_ReportOutOfMemory, JSEXN_ERR #include #include // for CurrentGlobalOrNull #include // for PropertyKey @@ -36,8 +36,7 @@ #include #include // for UniqueChars #include -#include // for JS_NewPlainObject, IdVector, JS_... -#include // for JSProto_Error +#include // for JS_NewPlainObject, IdVector, JS_... #include #include @@ -266,26 +265,21 @@ * gjs_import_native_module: * @cx: the #JSContext * @importer: the root importer - * @parse_name: Name under which the module was registered with - * add(), should be in the format as returned by - * g_file_get_parse_name() + * @id_str: Name under which the module was registered with add() * * Imports a builtin native-code module so that it is available to JS code as - * `imports[parse_name]`. + * `imports[id_str]`. * * Returns: true on success, false if an exception was thrown. */ -bool -gjs_import_native_module(JSContext *cx, - JS::HandleObject importer, - const char *parse_name) -{ - gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", parse_name); +bool gjs_import_native_module(JSContext* cx, JS::HandleObject importer, + const char* id_str) { + gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", id_str); JS::RootedObject native_registry( - cx, gjs_get_native_registry(gjs_get_import_global(cx))); + cx, gjs_get_native_registry(JS::CurrentGlobalOrNull(cx))); - JS::RootedId id(cx, gjs_intern_string_to_id(cx, parse_name)); + JS::RootedId id(cx, gjs_intern_string_to_id(cx, id_str)); if (id.isVoid()) return false; @@ -294,12 +288,12 @@ return false; if (!module && - (!Gjs::NativeModuleRegistry::get().load(cx, parse_name, &module) || + (!Gjs::NativeModuleDefineFuncs::get().define(cx, id_str, &module) || !gjs_global_registry_set(cx, native_registry, id, module))) return false; - return define_meta_properties(cx, module, nullptr, parse_name, importer) && - JS_DefineProperty(cx, importer, parse_name, module, + return define_meta_properties(cx, module, nullptr, id_str, importer) && + JS_DefineProperty(cx, importer, id_str, module, GJS_MODULE_PROP_FLAGS); } @@ -310,7 +304,7 @@ JS::HandleObject module_obj) { gsize script_len = 0; - GError *error = NULL; + GjsAutoError error; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); JS::RootedValue ignored(context); @@ -325,7 +319,6 @@ return false; } - g_error_free(error); return true; } g_assert(script); @@ -498,7 +491,7 @@ /* First try importing an internal module like gi */ if (parent.isNull() && - Gjs::NativeModuleRegistry::get().is_registered(name.get())) { + Gjs::NativeModuleDefineFuncs::get().is_registered(name.get())) { if (!gjs_import_native_module(context, obj, name.get())) return false; @@ -607,7 +600,7 @@ /* If no exception occurred, the problem is just that we got to the * end of the path. Be sure an exception is set. */ g_assert(!JS_IsExceptionPending(context)); - gjs_throw_custom(context, JSProto_Error, "ImportError", + gjs_throw_custom(context, JSEXN_ERR, "ImportError", "No JS module '%s' found in search path", name.get()); return false; } @@ -701,8 +694,7 @@ JS_ReportOutOfMemory(context); return false; } - } else if (g_str_has_suffix(filename, "." G_MODULE_SUFFIX) || - g_str_has_suffix(filename, ".js")) { + } else if (g_str_has_suffix(filename, ".js")) { GjsAutoChar filename_noext = g_strndup(filename, strlen(filename) - 3); jsid id = gjs_intern_string_to_id(context, filename_noext); diff -Nru gjs-1.76.2/gjs/internal.cpp gjs-1.78.0/gjs/internal.cpp --- gjs-1.76.2/gjs/internal.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/internal.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -19,6 +19,7 @@ #include #include #include +#include // for JSEXN_ERR #include #include // for JS_AddExtraGCRootsTracer #include @@ -36,7 +37,6 @@ #include #include // for JS_NewPlainObject, JS_ObjectIsFunction #include // for JS_GetObjectFunction, SetFunctionNativeReserved -#include // for JSProto_Error #include "gjs/context-private.h" #include "gjs/engine.h" @@ -89,8 +89,7 @@ options.setFileAndLine(full_path, 1); options.setSelfHostingMode(false); - JS::RootedObject internal_global(cx, gjs_get_internal_global(cx)); - JSAutoRealm ar(cx, internal_global); + Gjs::AutoInternalRealm ar{cx}; JS::RootedValue ignored(cx); return JS::Evaluate(cx, options, buf, &ignored); @@ -181,8 +180,7 @@ JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - JS::RootedObject global(cx, gjs_get_internal_global(cx)); - JSAutoRealm ar(cx, global); + Gjs::AutoInternalRealm ar{cx}; JS::UniqueChars uri; JS::RootedString source(cx); @@ -196,10 +194,10 @@ /** * gjs_internal_compile_module: * - * @brief Compiles a module source text within the import global's realm. + * @brief Compiles a module source text within the main realm. * * NOTE: Modules compiled with this function can only be executed - * within the import global's realm. + * within the main realm. * * @param cx the current JSContext * @param argc @@ -210,8 +208,7 @@ bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - JS::RootedObject global(cx, gjs_get_import_global(cx)); - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{cx}; JS::UniqueChars uri; JS::RootedString source(cx); @@ -274,7 +271,6 @@ using AutoHashTable = GjsAutoPointer; using AutoURI = GjsAutoPointer; - GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); JS::CallArgs args = CallArgsFromVp(argc, vp); JS::RootedString string_arg(cx); @@ -285,15 +281,14 @@ if (!uri) return false; - GError* error = nullptr; + GjsAutoError error; AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error); if (!parsed) { - JSAutoRealm ar(cx, gjs->global()); + Gjs::AutoMainRealm ar{cx}; - gjs_throw_custom(cx, JSProto_Error, "ImportError", + gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Attempted to import invalid URI: %s (%s)", uri.get(), error->message); - g_clear_error(&error); return false; } @@ -306,12 +301,11 @@ AutoHashTable query = g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error); if (!query) { - JSAutoRealm ar(cx, gjs->global()); + Gjs::AutoMainRealm ar{cx}; - gjs_throw_custom(cx, JSProto_Error, "ImportError", + gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Attempted to import invalid URI: %s (%s)", uri.get(), error->message); - g_clear_error(&error); return false; } @@ -407,16 +401,14 @@ char* contents; size_t length; - GError* error = nullptr; + GjsAutoError error; if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents, &length, /* etag_out = */ nullptr, &error)) { - GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); - JSAutoRealm ar(cx, gjs->global()); + Gjs::AutoMainRealm ar{cx}; - gjs_throw_custom(cx, JSProto_Error, "ImportError", + gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Unable to load file from: %s (%s)", uri.get(), error->message); - g_clear_error(&error); return false; } @@ -510,18 +502,17 @@ GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx); gjs->main_loop_release(); - JSAutoRealm ar(promise->cx, gjs->global()); + Gjs::AutoMainRealm ar{gjs}; char* contents; size_t length; - GError* error = nullptr; + GjsAutoError error; if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length, /* etag_out = */ nullptr, &error)) { GjsAutoChar uri = g_file_get_uri(G_FILE(file)); - gjs_throw_custom(promise->cx, JSProto_Error, "ImportError", + gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError", "Unable to load file from: %s (%s)", uri.get(), error->message); - g_clear_error(&error); promise->reject_with_pending_exception(); return; } diff -Nru gjs-1.76.2/gjs/jsapi-dynamic-class.cpp gjs-1.78.0/gjs/jsapi-dynamic-class.cpp --- gjs-1.76.2/gjs/jsapi-dynamic-class.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-dynamic-class.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -13,6 +13,7 @@ #include // for JSNative #include #include +#include // for JSEXN_TYPEERR #include // for GetClass #include // for JS_DefineFunctions, JS_DefinePro... #include // for GetRealmObjectPrototype @@ -21,7 +22,6 @@ #include #include // for JS_GetFunctionObject, JS_GetPrototype #include // for GetFunctionNativeReserved, NewFun... -#include // for JSProto_TypeError #include "gjs/atoms.h" #include "gjs/context-private.h" @@ -121,7 +121,7 @@ if (throw_error) { const JSClass* obj_class = JS::GetClass(obj); - gjs_throw_custom(context, JSProto_TypeError, nullptr, + gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, "Object %p is not a subclass of %s, it's a %s", obj.get(), static_clasp->name, format_dynamic_class_name(obj_class->name)); diff -Nru gjs-1.76.2/gjs/jsapi-util-args.h gjs-1.78.0/gjs/jsapi-util-args.h --- gjs-1.76.2/gjs/jsapi-util-args.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-util-args.h 2023-09-17 02:27:20.000000000 +0000 @@ -18,13 +18,18 @@ #include #include #include +#include #include #include #include // for UniqueChars +#include // for GenericErrorResult +#include // IWYU pragma: keep #include "gjs/jsapi-util.h" #include "gjs/macros.h" +namespace detail { + [[nodiscard]] GJS_ALWAYS_INLINE static inline bool check_nullable( const char*& fchar, const char*& fmt_string) { if (*fchar != '?') @@ -37,157 +42,165 @@ return true; } +class ParseArgsErr { + GjsAutoChar m_message; + + public: + explicit ParseArgsErr(const char* literal_msg) + : m_message(literal_msg, GjsAutoTakeOwnership{}) {} + template + ParseArgsErr(const char* format_string, F param) + : m_message(g_strdup_printf(format_string, param)) {} + + const char* message() const { return m_message.get(); } +}; + +template +inline constexpr auto Err(Args... args) { + return mozilla::GenericErrorResult{ + ParseArgsErr{std::forward(args)...}}; +} + +using ParseArgsResult = JS::Result; + /* This preserves the previous behaviour of gjs_parse_args(), but maybe we want * to use JS::ToBoolean instead? */ GJS_ALWAYS_INLINE -static inline void assign(JSContext*, char c, bool nullable, - JS::HandleValue value, bool* ref) { +static inline ParseArgsResult assign(JSContext*, char c, bool nullable, + JS::HandleValue value, bool* ref) { if (c != 'b') - throw g_strdup_printf("Wrong type for %c, got bool*", c); + return Err("Wrong type for %c, got bool*", c); if (!value.isBoolean()) - throw g_strdup("Not a boolean"); + return Err("Not a boolean"); if (nullable) - throw g_strdup("Invalid format string combination ?b"); + return Err("Invalid format string combination ?b"); *ref = value.toBoolean(); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void assign(JSContext*, char c, bool nullable, - JS::HandleValue value, JS::MutableHandleObject ref) { +static inline ParseArgsResult assign(JSContext*, char c, bool nullable, + JS::HandleValue value, + JS::MutableHandleObject ref) { if (c != 'o') - throw g_strdup_printf("Wrong type for %c, got JS::MutableHandleObject", c); + return Err("Wrong type for %c, got JS::MutableHandleObject", c); if (nullable && value.isNull()) { ref.set(nullptr); - return; + return JS::Ok(); } if (!value.isObject()) - throw g_strdup("Not an object"); + return Err("Not an object"); ref.set(&value.toObject()); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void assign(JSContext* cx, char c, bool nullable, - JS::HandleValue value, JS::UniqueChars* ref) { +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, + JS::UniqueChars* ref) { if (c != 's') - throw g_strdup_printf("Wrong type for %c, got JS::UniqueChars*", c); + return Err("Wrong type for %c, got JS::UniqueChars*", c); if (nullable && value.isNull()) { ref->reset(); - return; + return JS::Ok(); } JS::UniqueChars tmp = gjs_string_to_utf8(cx, value); if (!tmp) - throw g_strdup("Couldn't convert to string"); + return Err("Couldn't convert to string"); *ref = std::move(tmp); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void -assign(JSContext *cx, - char c, - bool nullable, - JS::HandleValue value, - GjsAutoChar *ref) -{ +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, GjsAutoChar* ref) { if (c != 'F') - throw g_strdup_printf("Wrong type for %c, got GjsAutoChar*", c); + return Err("Wrong type for %c, got GjsAutoChar*", c); if (nullable && value.isNull()) { ref->release(); - return; + return JS::Ok(); } if (!gjs_string_to_filename(cx, value, ref)) - throw g_strdup("Couldn't convert to filename"); + return Err("Couldn't convert to filename"); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void assign(JSContext*, char c, bool nullable, - JS::HandleValue value, JS::MutableHandleString ref) { +static inline ParseArgsResult assign(JSContext*, char c, bool nullable, + JS::HandleValue value, + JS::MutableHandleString ref) { if (c != 'S') - throw g_strdup_printf("Wrong type for %c, got JS::MutableHandleString", - c); + return Err("Wrong type for %c, got JS::MutableHandleString", c); if (nullable && value.isNull()) { ref.set(nullptr); - return; + return JS::Ok(); } if (!value.isString()) - throw g_strdup("Not a string"); + return Err("Not a string"); ref.set(value.toString()); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void -assign(JSContext *cx, - char c, - bool nullable, - JS::HandleValue value, - int32_t *ref) -{ +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, int32_t* ref) { if (c != 'i') - throw g_strdup_printf("Wrong type for %c, got int32_t*", c); + return Err("Wrong type for %c, got int32_t*", c); if (nullable) - throw g_strdup("Invalid format string combination ?i"); + return Err("Invalid format string combination ?i"); if (!JS::ToInt32(cx, value, ref)) - throw g_strdup("Couldn't convert to integer"); + return Err("Couldn't convert to integer"); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void -assign(JSContext *cx, - char c, - bool nullable, - JS::HandleValue value, - uint32_t *ref) -{ +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, uint32_t* ref) { double num; if (c != 'u') - throw g_strdup_printf("Wrong type for %c, got uint32_t*", c); + return Err("Wrong type for %c, got uint32_t*", c); if (nullable) - throw g_strdup("Invalid format string combination ?u"); + return Err("Invalid format string combination ?u"); if (!value.isNumber() || !JS::ToNumber(cx, value, &num)) - throw g_strdup("Couldn't convert to unsigned integer"); + return Err("Couldn't convert to unsigned integer"); if (num > G_MAXUINT32 || num < 0) - throw g_strdup_printf("Value %f is out of range", num); + return Err("Value %f is out of range", num); *ref = num; + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void -assign(JSContext *cx, - char c, - bool nullable, - JS::HandleValue value, - int64_t *ref) -{ +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, int64_t* ref) { if (c != 't') - throw g_strdup_printf("Wrong type for %c, got int64_t*", c); + return Err("Wrong type for %c, got int64_t*", c); if (nullable) - throw g_strdup("Invalid format string combination ?t"); + return Err("Invalid format string combination ?t"); if (!JS::ToInt64(cx, value, ref)) - throw g_strdup("Couldn't convert to 64-bit integer"); + return Err("Couldn't convert to 64-bit integer"); + return JS::Ok(); } GJS_ALWAYS_INLINE -static inline void -assign(JSContext *cx, - char c, - bool nullable, - JS::HandleValue value, - double *ref) -{ +static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, + JS::HandleValue value, double* ref) { if (c != 'f') - throw g_strdup_printf("Wrong type for %c, got double*", c); + return Err("Wrong type for %c, got double*", c); if (nullable) - throw g_strdup("Invalid format string combination ?f"); + return Err("Invalid format string combination ?f"); if (!JS::ToNumber(cx, value, ref)) - throw g_strdup("Couldn't convert to double"); + return Err("Couldn't convert to double"); + return JS::Ok(); } /* Special case: treat pointer-to-enum as pointer-to-int, but use enable_if to * prevent instantiation for any other types besides pointer-to-enum */ template , int> = 0> -GJS_ALWAYS_INLINE static inline void assign(JSContext* cx, char c, - bool nullable, - JS::HandleValue value, T* ref) { +GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, + bool nullable, + JS::HandleValue value, + T* ref) { /* Sadly, we cannot use std::underlying_type here; the underlying type of * an enum is implementation-defined, so it would not be clear what letter * to use in the format string. For the same reason, we can only support @@ -196,7 +209,7 @@ * value was in range for the enum, but that is not possible (yet?) */ static_assert(sizeof(T) == sizeof(int), "Short or wide enum types not supported"); - assign(cx, c, nullable, value, (int *)ref); + return assign(cx, c, nullable, value, reinterpret_cast(ref)); } template @@ -236,15 +249,15 @@ fmt_optional++; } - try { + ParseArgsResult res = assign(cx, *fchar, nullable, args[param_ix], param_ref); - } catch (char *message) { + if (res.isErr()) { /* Our error messages are going to be more useful than whatever was * thrown by the various conversion functions */ + const char* message = res.inspectErr().message(); JS_ClearPendingException(cx); gjs_throw(cx, "Error invoking %s, at argument %d (%s): %s", function_name, param_ix, param_name, message); - g_free(message); return false; } @@ -269,6 +282,8 @@ return retval; } +} // namespace detail + /* Empty-args version of the template */ GJS_JSAPI_RETURN_CONVENTION [[maybe_unused]] static bool gjs_parse_call_args( JSContext* cx, const char* function_name, const JS::CallArgs& args, @@ -381,8 +396,8 @@ fmt_required = parts.get()[0]; fmt_optional = parts.get()[1]; // may be null - return parse_call_args_helper(cx, function_name, args, fmt_required, - fmt_optional, 0, params...); + return detail::parse_call_args_helper(cx, function_name, args, fmt_required, + fmt_optional, 0, params...); } #endif // GJS_JSAPI_UTIL_ARGS_H_ diff -Nru gjs-1.76.2/gjs/jsapi-util.cpp gjs-1.78.0/gjs/jsapi-util.cpp --- gjs-1.76.2/gjs/jsapi-util.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-util.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -39,6 +39,7 @@ #include #include // for JS_InstanceOf #include // for ProtoKeyToClass +#include // for JSProto_InternalError, JSProto_SyntaxError #include "gjs/atoms.h" #include "gjs/context-private.h" @@ -330,8 +331,6 @@ out << '\n' << utf8_stack.get(); JS_ClearPendingException(cx); - // COMPAT: use JS::GetExceptionCause, mozjs 91.6 and later, on Error objects - // in order to avoid side effects JS::RootedValue v_cause(cx); if (!JS_GetPropertyById(cx, exc_obj, atoms.cause(), &v_cause)) JS_ClearPendingException(cx); @@ -556,56 +555,18 @@ gjs_gc_if_needed(context); } -/** - * 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* cx) { - return GjsContextPrivate::from_cx(cx)->global(); -} - -/** - * gjs_get_internal_global: - * - * @brief Gets the "internal global" for the context's runtime. The internal - * global object is the global object used for all "internal" JavaScript - * code (e.g. the module loader) that should not be accessible from users' - * code. - * - * @param cx a #JSContext - * - * @returns the "internal global" for the context's - * runtime. Will never return %NULL while GJS has an active context - * for the runtime. - */ -JSObject* gjs_get_internal_global(JSContext* cx) { - return GjsContextPrivate::from_cx(cx)->internal_global(); -} - const char* gjs_explain_gc_reason(JS::GCReason reason) { if (JS::InternalGCReason(reason)) return JS::ExplainGCReason(reason); static const char* reason_strings[] = { + // clang-format off "RSS above threshold", "GjsContext disposed", "Big Hammer hit", "gjs_context_gc() called", + "Memory usage is low", + // clang-format on }; static_assert(G_N_ELEMENTS(reason_strings) == Gjs::GCReason::N_REASONS, "Explanations must match the values in Gjs::GCReason"); diff -Nru gjs-1.76.2/gjs/jsapi-util-error.cpp gjs-1.78.0/gjs/jsapi-util-error.cpp --- gjs-1.76.2/gjs/jsapi-util-error.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-util-error.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -5,11 +5,12 @@ #include #include +#include +#include #include #include -#include #include #include #include @@ -17,13 +18,13 @@ #include // for DefaultHasher #include #include +#include #include // for BuildStackString +#include // for JS_NewStringCopyUTF8Z #include #include // for UniqueChars #include -#include -#include // for JS_GetClassObject -#include // for JSProtoKey, JSProto_Error, JSProto... +#include #include "gjs/atoms.h" #include "gjs/context-private.h" @@ -36,9 +37,9 @@ js::SystemAllocPolicy>; GJS_JSAPI_RETURN_CONVENTION -static bool get_last_cause_impl(JSContext* cx, JS::HandleValue v_exc, - JS::MutableHandleObject last_cause, - JS::MutableHandle seen_causes) { +static bool get_last_cause(JSContext* cx, JS::HandleValue v_exc, + JS::MutableHandleObject last_cause, + JS::MutableHandle seen_causes) { if (!v_exc.isObject()) { last_cause.set(nullptr); return true; @@ -64,100 +65,91 @@ return true; } - return get_last_cause_impl(cx, v_cause, last_cause, seen_causes); + return get_last_cause(cx, v_cause, last_cause, seen_causes); } GJS_JSAPI_RETURN_CONVENTION -static bool get_last_cause(JSContext* cx, JS::HandleValue v_exc, - JS::MutableHandleObject last_cause) { +static bool append_new_cause(JSContext* cx, JS::HandleValue thrown, + JS::HandleValue new_cause, bool* appended) { + g_assert(appended && "forgot out parameter"); + *appended = false; + JS::Rooted seen_causes(cx); - return get_last_cause_impl(cx, v_exc, last_cause, &seen_causes); + JS::RootedObject last_cause{cx}; + if (!get_last_cause(cx, thrown, &last_cause, &seen_causes)) + return false; + if (!last_cause) + return true; + + const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + if (!JS_SetPropertyById(cx, last_cause, atoms.cause(), new_cause)) + return false; + + *appended = true; + return true; } -/* - * See: - * https://bugzilla.mozilla.org/show_bug.cgi?id=166436 - * https://bugzilla.mozilla.org/show_bug.cgi?id=215173 - * - * Very surprisingly, jsapi.h lacks any way to "throw new Error()" - * - * So here is an awful hack inspired by - * http://egachine.berlios.de/embedding-sm-best-practice/embedding-sm-best-practice.html#error-handling - */ [[gnu::format(printf, 4, 0)]] static void gjs_throw_valist( - JSContext* context, JSProtoKey error_kind, const char* error_name, + JSContext* cx, JSExnType error_kind, const char* error_name, const char* format, va_list args) { - char *s; - bool result; - - s = g_strdup_vprintf(format, args); - - JS::RootedObject constructor(context); - JS::RootedValue v_constructor(context), exc_val(context); - JS::RootedObject new_exc(context); - JS::RootedValueArray<1> error_args(context); - result = false; - - if (!gjs_string_from_utf8(context, s, error_args[0])) { - JS_ReportErrorUTF8(context, "Failed to copy exception string"); - goto out; - } - - if (!JS_GetClassObject(context, error_kind, &constructor)) - goto out; - - v_constructor.setObject(*constructor); - - /* throw new Error(message) */ - if (!JS::Construct(context, v_constructor, error_args, &new_exc)) - goto out; - - if (!new_exc) - goto out; + GjsAutoChar s = g_strdup_vprintf(format, args); + auto fallback = mozilla::MakeScopeExit([cx, &s]() { + // try just reporting it to error handler? should not + // happen though pretty much + JS_ReportErrorUTF8(cx, "Failed to throw exception '%s'", s.get()); + }); + + JS::ConstUTF8CharsZ chars{s.get(), strlen(s.get())}; + JS::RootedString message{cx, JS_NewStringCopyUTF8Z(cx, chars)}; + if (!message) + return; + + JS::RootedObject saved_frame{cx}; + if (!JS::CaptureCurrentStack(cx, &saved_frame)) + return; + + JS::RootedString source_string{cx}; + JS::GetSavedFrameSource(cx, /* principals = */ nullptr, saved_frame, + &source_string); + uint32_t line_num; + JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line_num); + uint32_t column_num; + JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &column_num); + + JS::RootedValue v_exc{cx}; + if (!JS::CreateError(cx, error_kind, saved_frame, source_string, line_num, + column_num, /* report = */ nullptr, message, + /* cause = */ JS::NothingHandleValue, &v_exc)) + return; if (error_name) { - const GjsAtoms& atoms = GjsContextPrivate::atoms(context); - JS::RootedValue name_value(context); - if (!gjs_string_from_utf8(context, error_name, &name_value) || - !JS_SetPropertyById(context, new_exc, atoms.name(), name_value)) - goto out; + const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + JS::RootedValue v_name{cx}; + JS::RootedObject exc{cx, &v_exc.toObject()}; + if (!gjs_string_from_utf8(cx, error_name, &v_name) || + !JS_SetPropertyById(cx, exc, atoms.name(), v_name)) + return; } - exc_val.setObject(*new_exc); - - if (JS_IsExceptionPending(context)) { + if (JS_IsExceptionPending(cx)) { // Often it's unclear whether a given jsapi.h function will throw an // exception, so we will throw ourselves "just in case"; in those cases, // we append the new exception as the cause of the original exception. // The second exception may add more info. - const GjsAtoms& atoms = GjsContextPrivate::atoms(context); - JS::RootedValue pending(context); - JS_GetPendingException(context, &pending); - JS::RootedObject last_cause(context); - if (!get_last_cause(context, pending, &last_cause)) - goto out; - if (last_cause) { - if (!JS_SetPropertyById(context, last_cause, atoms.cause(), - exc_val)) - goto out; - } else { - gjs_debug(GJS_DEBUG_CONTEXT, "Ignoring second exception: '%s'", s); - } + JS::RootedValue pending(cx); + JS_GetPendingException(cx, &pending); + JS::AutoSaveExceptionState saved_exc{cx}; + bool appended; + if (!append_new_cause(cx, pending, v_exc, &appended)) + saved_exc.restore(); + if (!appended) + gjs_debug(GJS_DEBUG_CONTEXT, "Ignoring second exception: '%s'", + s.get()); } else { - JS_SetPendingException(context, exc_val); + JS_SetPendingException(cx, v_exc); } - result = true; - - out: - - if (!result) { - /* try just reporting it to error handler? should not - * happen though pretty much - */ - JS_ReportErrorUTF8(context, "Failed to throw exception '%s'", s); - } - g_free(s); + fallback.release(); } /* Throws an exception, like "throw new Error(message)" @@ -175,7 +167,7 @@ va_list args; va_start(args, format); - gjs_throw_valist(context, JSProto_Error, nullptr, format, args); + gjs_throw_valist(context, JSEXN_ERR, nullptr, format, args); va_end(args); } @@ -184,29 +176,10 @@ * class and 'name' property. Mainly used for throwing TypeError instead of * error. */ -void -gjs_throw_custom(JSContext *cx, - JSProtoKey kind, - const char *error_name, - const char *format, - ...) -{ +void gjs_throw_custom(JSContext *cx, JSExnType kind, const char *error_name, + const char *format, ...) { va_list args; - switch (kind) { - case JSProto_Error: - case JSProto_EvalError: - case JSProto_InternalError: - case JSProto_RangeError: - case JSProto_ReferenceError: - case JSProto_SyntaxError: - case JSProto_TypeError: - case JSProto_URIError: - break; - default: - g_return_if_reached(); - } - va_start(args, format); gjs_throw_valist(cx, kind, error_name, format, args); va_end(args); @@ -235,13 +208,10 @@ * Use this when handling a GError in an internal function, where the error code * and domain don't matter. So, for example, don't use it to throw errors * around calling from JS into C code. - * - * Frees the GError. */ -bool gjs_throw_gerror_message(JSContext* cx, GError* error) { +bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const& error) { g_return_val_if_fail(error, false); gjs_throw_literal(cx, error->message); - g_error_free(error); return false; } diff -Nru gjs-1.76.2/gjs/jsapi-util.h gjs-1.78.0/gjs/jsapi-util.h --- gjs-1.76.2/gjs/jsapi-util.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-util.h 2023-09-17 02:27:20.000000000 +0000 @@ -23,12 +23,12 @@ #include #include +#include // for JSExnType #include #include // for IgnoreGCPolicy #include #include #include // for UniqueChars -#include // for JSProtoKey #include "gjs/macros.h" #include "util/log.h" @@ -37,7 +37,6 @@ # include "gi/arg-types-inl.h" // for static_type_name #endif -class JSErrorReport; namespace JS { class CallArgs; @@ -73,6 +72,10 @@ std::conditional_t, std::remove_extent_t, T>; using Ptr = std::add_pointer_t; using ConstPtr = std::add_pointer_t>; + using RvalueRef = std::add_lvalue_reference_t; + + protected: + using BaseType = GjsAutoPointer; private: template @@ -138,6 +141,18 @@ return m_ptr; } + template + constexpr std::enable_if_t, RvalueRef> operator[]( + int index) { + return m_ptr[index]; + } + + template + constexpr std::enable_if_t, std::add_const_t> + operator[](int index) const { + return m_ptr[index]; + } + constexpr Tp operator*() const { return *m_ptr; } constexpr operator Ptr() { return m_ptr; } constexpr operator Ptr() const { return m_ptr; } @@ -146,7 +161,7 @@ constexpr Ptr get() const { return m_ptr; } constexpr Ptr* out() { return &m_ptr; } - constexpr ConstPtr* out() const { return &m_ptr; } + constexpr ConstPtr* out() const { return const_cast(&m_ptr); } constexpr Ptr release() { auto* ptr = m_ptr; @@ -220,8 +235,20 @@ struct GjsAutoErrorFuncs { static GError* error_copy(GError* error) { return g_error_copy(error); } }; -using GjsAutoError = - GjsAutoPointer; + +struct GjsAutoError : GjsAutoPointer { + using BaseType::BaseType; + using BaseType::operator=; + + constexpr BaseType::ConstPtr* operator&() // NOLINT(runtime/operator) + const { + return out(); + } + constexpr BaseType::Ptr* operator&() { // NOLINT(runtime/operator) + return out(); + } +}; using GjsAutoStrv = GjsAutoPointer; @@ -279,12 +306,16 @@ // to conform to the interface of std::unique_ptr here. GjsAutoInfo(GIBaseInfo* ptr = nullptr) // NOLINT(runtime/explicit) : GjsAutoBaseInfo(ptr) { +#ifndef G_DISABLE_CAST_CHECKS validate(); +#endif } void reset(GIBaseInfo* other = nullptr) { GjsAutoBaseInfo::reset(other); +#ifndef G_DISABLE_CAST_CHECKS validate(); +#endif } // You should not need this method, because you already know the answer. @@ -297,6 +328,7 @@ } }; +using GjsAutoArgInfo = GjsAutoInfo; using GjsAutoEnumInfo = GjsAutoInfo; using GjsAutoFieldInfo = GjsAutoInfo; using GjsAutoFunctionInfo = GjsAutoInfo; @@ -304,6 +336,7 @@ using GjsAutoObjectInfo = GjsAutoInfo; using GjsAutoPropertyInfo = GjsAutoInfo; using GjsAutoStructInfo = GjsAutoInfo; +using GjsAutoSignalInfo = GjsAutoInfo; using GjsAutoTypeInfo = GjsAutoInfo; using GjsAutoValueInfo = GjsAutoInfo; using GjsAutoVFuncInfo = GjsAutoInfo; @@ -358,6 +391,8 @@ template <> struct GjsSmartPointer : GjsAutoError { using GjsAutoError::GjsAutoError; + using GjsAutoError::operator=; + using GjsAutoError::operator&; }; template <> @@ -415,10 +450,6 @@ if (!args.computeThis(cx, &to)) \ return false; -[[nodiscard]] JSObject* gjs_get_import_global(JSContext* cx); - -[[nodiscard]] JSObject* gjs_get_internal_global(JSContext* cx); - void gjs_throw_constructor_error (JSContext *context); void gjs_throw_abstract_constructor_error(JSContext* cx, @@ -437,12 +468,12 @@ [[gnu::format(printf, 2, 3)]] void gjs_throw(JSContext* cx, const char* format, ...); [[gnu::format(printf, 4, 5)]] void gjs_throw_custom(JSContext* cx, - JSProtoKey error_kind, + JSExnType error_kind, const char* error_name, const char* format, ...); void gjs_throw_literal (JSContext *context, const char *string); -bool gjs_throw_gerror_message(JSContext* cx, GError* error); +bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const&); bool gjs_log_exception (JSContext *context); @@ -588,7 +619,8 @@ macro(LINUX_RSS_TRIGGER, 0) \ macro(GJS_CONTEXT_DISPOSE, 1) \ macro(BIG_HAMMER, 2) \ - macro(GJS_API_CALL, 3) + macro(GJS_API_CALL, 3) \ + macro(LOW_MEMORY, 4) // clang-format on namespace Gjs { diff -Nru gjs-1.76.2/gjs/jsapi-util-string.cpp gjs-1.78.0/gjs/jsapi-util-string.cpp --- gjs-1.76.2/gjs/jsapi-util-string.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/jsapi-util-string.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -209,7 +209,7 @@ const JS::Value filename_val, GjsAutoChar *filename_string) { - GError *error; + GjsAutoError error; /* gjs_string_to_filename verifies that filename_val is a string */ @@ -233,7 +233,7 @@ JS::MutableHandleValue value_p) { gsize written; - GError *error; + GjsAutoError error; error = NULL; GjsAutoChar utf8_string = g_filename_to_utf8(filename_string, n_bytes, @@ -241,9 +241,7 @@ if (error) { gjs_throw(context, "Could not convert UTF-8 string '%s' to a filename: '%s'", - filename_string, - error->message); - g_error_free(error); + filename_string, error->message); return false; } @@ -337,7 +335,7 @@ return true; size_t len; - GError *error = NULL; + GjsAutoError error; if (JS::StringHasLatin1Chars(str)) return from_latin1(cx, str, ucs4_string_p, len_p); @@ -361,7 +359,6 @@ if (*ucs4_string_p == NULL) { gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s", error->message); - g_clear_error(&error); return false; } if (len_p != NULL) @@ -393,14 +390,13 @@ } long u16_string_length; - GError *error = NULL; + GjsAutoError error; gunichar2* u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, nullptr, &u16_string_length, &error); if (!u16_string) { gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16: %s", error->message); - g_error_free(error); return false; } diff -Nru gjs-1.76.2/gjs/mem.cpp gjs-1.78.0/gjs/mem.cpp --- gjs-1.76.2/gjs/mem.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/mem.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -4,8 +4,6 @@ #include -#include // for atomic_int64_t - #include #include "gjs/mem-private.h" diff -Nru gjs-1.76.2/gjs/module.cpp gjs-1.78.0/gjs/module.cpp --- gjs-1.76.2/gjs/module.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/module.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -7,8 +7,6 @@ #include // for size_t #include -#include // for u16string - #include #include @@ -29,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -44,6 +41,7 @@ #include "gjs/atoms.h" #include "gjs/context-private.h" +#include "gjs/deprecation.h" #include "gjs/global.h" #include "gjs/jsapi-util-args.h" #include "gjs/jsapi-util.h" @@ -153,7 +151,7 @@ JS::HandleObject module, GFile *file) { - GError *error = nullptr; + GjsAutoError error; GjsAutoChar script; size_t script_len = 0; @@ -194,14 +192,9 @@ * be supported according to ES6. For compatibility with earlier GJS, * we treat it as if it were a real property, but warn about it. */ - g_warning( - "Some code accessed the property '%s' on the module '%s'. That " - "property was defined with 'let' or 'const' inside the module. " - "This was previously supported, but is not correct according to " - "the ES6 standard. Any symbols to be exported from a module must " - "be defined with 'var'. The property access will work as " - "previously for the time being, but please fix your code anyway.", - gjs_debug_id(id).c_str(), m_name.get()); + _gjs_warn_deprecated_once_per_callsite( + cx, GjsDeprecationMessageId::ModuleExportedLetOrConst, + {gjs_debug_id(id).c_str(), m_name}); JS::Rooted desc(cx, maybe_desc.value()); return JS_DefinePropertyById(cx, module, id, desc); @@ -427,8 +420,8 @@ if (!gjs_parse_call_args(cx, "importSync", args, "s", "identifier", &id)) return false; - JS::RootedObject global(cx, gjs_get_import_global(cx)); - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{cx}; + JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)}; JS::AutoSaveExceptionState exc_state(cx); @@ -445,7 +438,8 @@ } JS::RootedObject native_obj(cx); - if (!Gjs::NativeModuleRegistry::get().load(cx, id.get(), &native_obj)) { + if (!Gjs::NativeModuleDefineFuncs::get().define(cx, id.get(), + &native_obj)) { gjs_throw(cx, "Failed to load native module: %s", id.get()); return false; } @@ -505,7 +499,7 @@ * Hook SpiderMonkey calls to resolve import specifiers. * * @param importingModulePriv the private value of the #Module object initiating - * the import. + * the import, or a JS null value * @param specifier the import specifier to resolve * * @returns whether an error occurred while resolving the specifier. @@ -516,9 +510,6 @@ gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) && "gjs_module_resolve can only be called from module-enabled " "globals."); - g_assert(importingModulePriv.isObject() && - "the importing module can't be null, don't add import to the " - "bootstrap script"); JS::RootedString specifier( cx, JS::GetModuleRequestSpecifier(cx, module_request)); @@ -533,9 +524,9 @@ args[1].setString(specifier); gjs_debug(GJS_DEBUG_IMPORTER, - "Module resolve hook for module '%s' (relative to %p), global %p", + "Module resolve hook for module %s (relative to %s), global %p", gjs_debug_string(specifier).c_str(), - &importingModulePriv.toObject(), global.get()); + gjs_debug_value(importingModulePriv).c_str(), global.get()); JS::RootedValue result(cx); if (!JS::Call(cx, loader, "moduleResolveHook", args, &result)) @@ -611,14 +602,13 @@ gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise resolved"); - JS::RootedObject global(cx, gjs_get_import_global(cx)); - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{cx}; g_assert(args[0].isObject()); JS::RootedObject module(cx, &args[0].toObject()); JS::RootedValue evaluation_promise(cx); - if (!JS::ModuleInstantiate(cx, module) || + if (!JS::ModuleLink(cx, module) || !JS::ModuleEvaluate(cx, module, &evaluation_promise)) return fail_import(cx, args); @@ -638,7 +628,7 @@ "global."); JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); - JSAutoRealm ar(cx, global); + g_assert(global && "gjs_dynamic_module_resolve must be in a realm"); JS::RootedValue v_loader( cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)); @@ -659,13 +649,13 @@ if (importing_module_priv.isObject()) { gjs_debug(GJS_DEBUG_IMPORTER, - "Async module resolve hook for module '%s' (relative to %p), " + "Async module resolve hook for module %s (relative to %p), " "global %p", gjs_debug_string(specifier).c_str(), &importing_module_priv.toObject(), global.get()); } else { gjs_debug(GJS_DEBUG_IMPORTER, - "Async module resolve hook for module '%s' (unknown path), " + "Async module resolve hook for module %s (unknown path), " "global %p", gjs_debug_string(specifier).c_str(), global.get()); } diff -Nru gjs-1.76.2/gjs/native.cpp gjs-1.78.0/gjs/native.cpp --- gjs-1.76.2/gjs/native.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/native.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -18,8 +18,8 @@ #include "gjs/native.h" #include "util/log.h" -void Gjs::NativeModuleRegistry::add(const char* module_id, - GjsDefineModuleFunc func) { +void Gjs::NativeModuleDefineFuncs::add(const char* module_id, + GjsDefineModuleFunc func) { bool inserted; std::tie(std::ignore, inserted) = m_modules.insert({module_id, func}); if (!inserted) { @@ -41,32 +41,30 @@ * been registered. This is used to check to see if a name is a * builtin module without starting to try and load it. */ -bool Gjs::NativeModuleRegistry::is_registered(const char* name) const { +bool Gjs::NativeModuleDefineFuncs::is_registered(const char* name) const { return m_modules.count(name) > 0; } /** - * gjs_load: + * define: * @context: the #JSContext - * @parse_name: Name under which the module was registered with - * add(), should be in the format as returned by - * g_file_get_parse_name() + * @id: Name under which the module was registered with add() * @module_out: Return location for a #JSObject * - * Loads a builtin native-code module called @name into @module_out. + * Loads a builtin native-code module called @name into @module_out by calling + * the function to define it. * * Returns: true on success, false if an exception was thrown. */ -bool Gjs::NativeModuleRegistry::load(JSContext* context, const char* parse_name, - JS::MutableHandleObject module_out) { - gjs_debug(GJS_DEBUG_NATIVE, "Defining native module '%s'", parse_name); +bool Gjs::NativeModuleDefineFuncs::define( + JSContext* context, const char* id, + JS::MutableHandleObject module_out) const { + gjs_debug(GJS_DEBUG_NATIVE, "Defining native module '%s'", id); - const auto& iter = m_modules.find(parse_name); + const auto& iter = m_modules.find(id); if (iter == m_modules.end()) { - gjs_throw(context, - "No native module '%s' has registered itself", - parse_name); + gjs_throw(context, "No native module '%s' has registered itself", id); return false; } diff -Nru gjs-1.76.2/gjs/native.h gjs-1.78.0/gjs/native.h --- gjs-1.76.2/gjs/native.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/native.h 2023-09-17 02:27:20.000000000 +0000 @@ -15,29 +15,29 @@ #include "gjs/macros.h" namespace Gjs { -class NativeModuleRegistry { - NativeModuleRegistry() {} +class NativeModuleDefineFuncs { + NativeModuleDefineFuncs() {} typedef bool (*GjsDefineModuleFunc)(JSContext* context, JS::MutableHandleObject module_out); std::unordered_map m_modules; public: - static NativeModuleRegistry& get() { - static NativeModuleRegistry the_singleton; + static NativeModuleDefineFuncs& get() { + static NativeModuleDefineFuncs the_singleton; return the_singleton; } /* called on context init */ void add(const char* module_id, GjsDefineModuleFunc func); - /* called by importer.c to to check for already loaded modules */ + // called by importer.cpp to to check for already loaded modules [[nodiscard]] bool is_registered(const char* name) const; - /* called by importer.cpp to load a statically linked native module */ + // called by importer.cpp to load a built-in native module GJS_JSAPI_RETURN_CONVENTION - bool load(JSContext* cx, const char* name, - JS::MutableHandleObject module_out); + bool define(JSContext* cx, const char* name, + JS::MutableHandleObject module_out) const; }; }; // namespace Gjs diff -Nru gjs-1.76.2/gjs/profiler.cpp gjs-1.78.0/gjs/profiler.cpp --- gjs-1.76.2/gjs/profiler.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/profiler.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -89,6 +89,10 @@ GSource* periodic_flush; SysprofCaptureWriter* target_capture; + + // Cache previous values of counters so that we don't overrun the output + // with counters that don't change very often + uint64_t last_counter_values[GJS_N_COUNTERS]; #endif /* ENABLE_PROFILER */ /* The filename to write to */ @@ -435,15 +439,24 @@ unsigned ids[GJS_N_COUNTERS]; SysprofCaptureCounterValue values[GJS_N_COUNTERS]; + size_t new_counts = 0; -# define FETCH_COUNTERS(name, ix) \ - ids[ix] = self->counter_base + ix; \ - values[ix].v64 = GJS_GET_COUNTER(name); +# define FETCH_COUNTERS(name, ix) \ + { \ + uint64_t count = GJS_GET_COUNTER(name); \ + if (count != self->last_counter_values[ix]) { \ + ids[new_counts] = self->counter_base + ix; \ + values[new_counts].v64 = count; \ + new_counts++; \ + } \ + self->last_counter_values[ix] = count; \ + } GJS_FOR_EACH_COUNTER(FETCH_COUNTERS); # undef FETCH_COUNTERS - if (!sysprof_capture_writer_set_counters(self->capture, now, -1, self->pid, - ids, values, GJS_N_COUNTERS)) + if (new_counts > 0 && + !sysprof_capture_writer_set_counters(self->capture, now, -1, self->pid, + ids, values, new_counts)) gjs_profiler_stop(self); } diff -Nru gjs-1.76.2/gjs/promise.cpp gjs-1.78.0/gjs/promise.cpp --- gjs-1.76.2/gjs/promise.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/promise.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -6,8 +6,6 @@ #include // for size_t -#include - #include #include diff -Nru gjs-1.76.2/gjs/stack.cpp gjs-1.78.0/gjs/stack.cpp --- gjs-1.76.2/gjs/stack.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/stack.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -6,12 +6,18 @@ #include // for stderr +#include +#include + #include #include +#include #include +#include // for UniqueChars #include +#include "gjs/context-private.h" #include "gjs/context.h" #include "gjs/jsapi-util.h" @@ -35,3 +41,32 @@ gjs_context_print_stack_stderr(context); } } + +std::string +gjs_dumpstack_string() { + std::string out; + std::ostringstream all_traces; + + GjsSmartPointer contexts = gjs_context_get_all(); + js::Sprinter printer; + GList *iter; + + for (iter = contexts; iter; iter = iter->next) { + GjsAutoUnref context(GJS_CONTEXT(iter->data)); + if (!printer.init()) { + all_traces << "No stack trace for context " << context.get() + << ": out of memory\n\n"; + break; + } + auto* cx = + static_cast(gjs_context_get_native_context(context)); + js::DumpBacktrace(cx, printer); + JS::UniqueChars trace = printer.release(); + all_traces << "== Stack trace for context " << context.get() << " ==\n" + << trace.get() << "\n"; + } + out = all_traces.str(); + out.resize(MAX(out.size() - 2, 0)); + + return out; +} diff -Nru gjs-1.76.2/gjs/text-encoding.cpp gjs-1.78.0/gjs/text-encoding.cpp --- gjs-1.76.2/gjs/text-encoding.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs/text-encoding.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -15,6 +15,7 @@ #include // for distance #include // for unique_ptr #include // for u16string +#include // for tuple #include #include @@ -23,7 +24,7 @@ #include #include #include -#include // for JS_ReportOutOfMemory +#include // for JS_ReportOutOfMemory, JSEXN_TYPEERR #include // for JS_ClearPendingException, JS_... #include // for AutoCheckCannotGC #include @@ -36,10 +37,9 @@ #include #include // for JS_NewPlainObject, JS_InstanceOf #include // for ProtoKeyToClass -#include // for JSProto_TypeError, JSProto_InternalError +#include // for JSProto_InternalError #include #include -#include #include #include "gjs/jsapi-util-args.h" @@ -53,11 +53,10 @@ g_free(contents); } -static std::nullptr_t gjs_throw_type_error_from_gerror(JSContext* cx, - GError* error) { +static std::nullptr_t gjs_throw_type_error_from_gerror( + JSContext* cx, GjsAutoError const& error) { g_return_val_if_fail(error, nullptr); - gjs_throw_custom(cx, JSProto_TypeError, nullptr, "%s", error->message); - g_error_free(error); + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "%s", error->message); return nullptr; } @@ -77,7 +76,7 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( JSContext* cx, const uint8_t* bytes, size_t bytes_len, const char* from_codeset) { - GError* error = nullptr; + GjsAutoError error; GjsAutoUnref converter( g_charset_converter_new(UTF16_CODESET, from_codeset, &error)); @@ -122,13 +121,15 @@ std::u16string output_str = u""; do { + GjsAutoError local_error; + // Create a buffer to convert into. std::unique_ptr buffer = std::make_unique(buffer_size); size_t bytes_written = 0, bytes_read = 0; g_converter_convert(G_CONVERTER(converter.get()), input, input_len, buffer.get(), buffer_size, G_CONVERTER_INPUT_AT_END, - &bytes_read, &bytes_written, &error); + &bytes_read, &bytes_written, &local_error); // If bytes were read, adjust input. if (bytes_read > 0) { @@ -142,7 +143,7 @@ char16_t* utf16_buffer = reinterpret_cast(buffer.get()); // std::u16string uses exactly 2 bytes for every character. output_str.append(utf16_buffer, bytes_written / 2); - } else if (error) { + } else if (local_error) { // A PARTIAL_INPUT error can only occur if the user does not provide // the full sequence for a multi-byte character, we skip over the // next character and insert a unicode fallback. @@ -150,8 +151,10 @@ // An INVALID_DATA error occurs when there is no way to decode a // given byte into UTF-16 or the given byte does not exist in the // source encoding. - if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA) || - g_error_matches(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT)) { + if (g_error_matches(local_error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA) || + g_error_matches(local_error, G_IO_ERROR, + G_IO_ERROR_PARTIAL_INPUT)) { // If we're already at the end of the string, don't insert a // fallback. if (input_len > 0) { @@ -162,9 +165,6 @@ // Append the unicode fallback character to the output output_str.append(u"\ufffd", 1); } - - // Clear the error. - g_clear_error(&error); } else if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NO_SPACE)) { // If the buffer was full increase the buffer @@ -180,9 +180,6 @@ } else { buffer_size += bytes_len; } - - // Clear the error. - g_clear_error(&error); } } @@ -215,7 +212,7 @@ } size_t bytes_written, bytes_read; - GError* error = nullptr; + GjsAutoError error; GjsAutoChar bytes = g_convert(reinterpret_cast(input), input_len, @@ -326,7 +323,7 @@ // Clear the existing exception. JS_ClearPendingException(cx); gjs_throw_custom( - cx, JSProto_TypeError, nullptr, + cx, JSEXN_TYPEERR, nullptr, "The provided encoded data was not valid UTF-8"); } @@ -412,7 +409,7 @@ if (array_buffer) mozilla::Unused << utf8.release(); } else { - GError* error = nullptr; + GjsAutoError error; GjsAutoChar encoded = nullptr; size_t bytes_written; @@ -475,7 +472,7 @@ JS::HandleObject uint8array, JS::MutableHandleValue rval) { if (!JS_IsUint8Array(uint8array)) { - gjs_throw_custom(cx, JSProto_TypeError, nullptr, + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Argument to encodeInto() must be a Uint8Array"); return false; } @@ -488,7 +485,7 @@ return false; } - mozilla::Maybe> results; + mozilla::Maybe> results; { JS::AutoCheckCannotGC nogc(cx); @@ -507,7 +504,7 @@ } size_t read, written; - mozilla::Tie(read, written) = *results; + std::tie(read, written) = *results; g_assert(written <= len); diff -Nru gjs-1.76.2/gjs.doap gjs-1.78.0/gjs.doap --- gjs-1.76.2/gjs.doap 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/gjs.doap 2023-09-17 02:27:20.000000000 +0000 @@ -20,7 +20,7 @@ - + C++ @@ -45,7 +45,7 @@ Giovanni Campagna - gcampagna + diff -Nru gjs-1.76.2/installed-tests/extra/lsan.supp gjs-1.78.0/installed-tests/extra/lsan.supp --- gjs-1.76.2/installed-tests/extra/lsan.supp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/extra/lsan.supp 2023-09-17 02:27:20.000000000 +0000 @@ -13,3 +13,7 @@ # GIO Module instances are created once and they're expected to be "leaked" leak:g_io_module_new + +# Gtk test may leak because of a Gdk/X11 issue: +# https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6037 +leak:gdk_x11_selection_input_stream_new_async diff -Nru gjs-1.76.2/installed-tests/js/meson.build gjs-1.78.0/installed-tests/js/meson.build --- gjs-1.76.2/installed-tests/js/meson.build 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/meson.build 2023-09-17 02:27:20.000000000 +0000 @@ -164,6 +164,7 @@ tests_dependencies = [ gschemas_compiled, + gjs_private_typelib, gjstest_tools_typelib, gimarshallingtests_typelib, regress_typelib, @@ -203,9 +204,13 @@ # during build should be run using dbus-run-session dbus_tests = ['GDBus'] -if have_gtk4 and not get_option('skip_gtk_tests') - # FIXME: find out why GTK4 tries to acquire a message bus - dbus_tests += 'Gtk4' +if not get_option('skip_gtk_tests') + have_gtk4 = dependency('gtk4', required: false).found() + + if have_gtk4 + # FIXME: find out why GTK4 tries to acquire a message bus + dbus_tests += 'Gtk4' + endif endif bus_config = files('../../test/test-bus.conf') @@ -215,7 +220,8 @@ if not get_option('skip_dbus_tests') test(test, dbus_run_session, args: ['--config-file', bus_config, '--', minijasmine, test_file], - env: tests_environment, protocol: 'tap', suite: 'dbus') + env: tests_environment, protocol: 'tap', suite: 'dbus', + depends: tests_dependencies) endif dbus_test_description_subst = { diff -Nru gjs-1.76.2/installed-tests/js/minijasmine-executor.js gjs-1.78.0/installed-tests/js/minijasmine-executor.js --- gjs-1.76.2/installed-tests/js/minijasmine-executor.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/minijasmine-executor.js 2023-09-17 02:27:20.000000000 +0000 @@ -5,14 +5,19 @@ import * as system from 'system'; import GLib from 'gi://GLib'; -import {environment, retval, errorsOutput, mainloop, mainloopLock} from './minijasmine.js'; - -/* jasmineEnv.execute() queues up all the tests and runs them - * asynchronously. This should start after the main loop starts, otherwise - * we will hit the main loop only after several tests have already run. For - * consistency we should guarantee that there is a main loop running during - * all tests. - */ +import { + environment, + retval, + errorsOutput, + mainloop, + mainloopLock, +} from './minijasmine.js'; + +// environment.execute() queues up all the tests and runs them +// asynchronously. This should start after the main loop starts, otherwise +// we will hit the main loop only after several tests have already run. For +// consistency we should guarantee that there is a main loop running during +// all tests. GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { try { environment.execute(); @@ -27,8 +32,9 @@ return GLib.SOURCE_REMOVE; }); -// Keep running the mainloop while mainloopLock -// is not null and resolves true +// Keep running the main loop while mainloopLock is not null and resolves true. +// This happens when testing the main loop itself, in testAsyncMainloop.js. We +// don't want to exit minijasmine when the inner loop exits. do { // Run the mainloop diff -Nru gjs-1.76.2/installed-tests/js/minijasmine.js gjs-1.78.0/installed-tests/js/minijasmine.js --- gjs-1.76.2/installed-tests/js/minijasmine.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/minijasmine.js 2023-09-17 02:27:20.000000000 +0000 @@ -122,10 +122,11 @@ export let mainloopLock = null; /** - * Stops the mainloop but prevents the minijasmine-executor from - * exiting. + * Stops the main loop but prevents the minijasmine-executor from + * exiting. This is used for testing the main loop itself. * - * @returns a callback which returns control to minijasmine-executor + * @returns a callback which returns control of the main loop to + * minijasmine-executor */ export function acquireMainloop() { let resolve; diff -Nru gjs-1.76.2/installed-tests/js/testConsole.js gjs-1.78.0/installed-tests/js/testConsole.js --- gjs-1.76.2/installed-tests/js/testConsole.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testConsole.js 2023-09-17 02:27:20.000000000 +0000 @@ -121,6 +121,25 @@ writer_func.calls.reset(); }); + it('logs an empty object correctly', function () { + const emptyObject = {}; + console.log(emptyObject); + expectLog('{}'); + }); + + it('logs an object with custom constructor name', function () { + function CustomObject() {} + const customInstance = new CustomObject(); + console.log(customInstance); + expectLog('CustomObject {}'); + }); + + it('logs an object with undefined constructor', function () { + const objectWithUndefinedConstructor = Object.create(null); + console.log(objectWithUndefinedConstructor); + expectLog('{}'); + }); + it('logs a warning', function () { console.warn('a warning'); diff -Nru gjs-1.76.2/installed-tests/js/testEncoding.js gjs-1.78.0/installed-tests/js/testEncoding.js --- gjs-1.76.2/installed-tests/js/testEncoding.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testEncoding.js 2023-09-17 02:27:20.000000000 +0000 @@ -363,10 +363,7 @@ expect(() => { decoder.decode(new Uint8Array([161, 200, 200])); - }).toThrowError( - TypeError, - 'Invalid byte sequence in conversion input' - ); + }).toThrowError(TypeError); }); it('can decode ASCII', function () { diff -Nru gjs-1.76.2/installed-tests/js/testGIMarshalling.js gjs-1.78.0/installed-tests/js/testGIMarshalling.js --- gjs-1.76.2/installed-tests/js/testGIMarshalling.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGIMarshalling.js 2023-09-17 02:27:20.000000000 +0000 @@ -341,15 +341,17 @@ expect(newArray).toEqual([]); }); - xit('marshals an inout empty array', function () { - const [, newArray] = GIMarshallingTests.init_function([]); + it('marshals an inout empty array', function () { + const [ret, newArray] = GIMarshallingTests.init_function([]); + expect(ret).toBeTrue(); expect(newArray).toEqual([]); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88'); + }); - xit('marshals an inout array', function () { - const [, newArray] = GIMarshallingTests.init_function(['--foo', '--bar']); + it('marshals an inout array', function () { + const [ret, newArray] = GIMarshallingTests.init_function(['--foo', '--bar']); + expect(ret).toBeTrue(); expect(newArray).toEqual(['--foo']); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88'); + }); }); describe('Fixed-size C array', function () { @@ -510,6 +512,57 @@ expect(() => GIMarshallingTests.array_in_guint8_len([-1, 0, 1, 2])).not.toThrow(); }); + it('can be an in-out argument', function () { + const array = GIMarshallingTests.array_inout([-1, 0, 1, 2]); + expect(array).toEqual([-2, -1, 0, 1, 2]); + }); + + it('can be an in-out argument with in length', function () { + if (!GIMarshallingTests.array_inout_length_in) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); + const array = GIMarshallingTests.array_inout_length_in([-1, 0, 1, 2]); + expect(array).toEqual([-2, -1, 1, 2]); + }); + + xit('can be an out argument with in-out length', function () { + const array = GIMarshallingTests.array_out_length_inout(5); + expect(array).toEqual([-2, -4, -6, 8, -10, -12]); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); + + it('cannot be an out argument with in-out length', function () { + // TODO(3v1n0): remove this test when fixing + // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 + if (!GIMarshallingTests.array_out_length_inout) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); + expect(() => GIMarshallingTests.array_out_length_inout(5)).toThrow(); + }); + + xit('can be an in-out argument with out length', function () { + const array = GIMarshallingTests.array_inout_length_out([-1, 0, 1, 2]); + expect(array).toEqual([-2, -1, 0, 1, 2]); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); + + it('cannot be an in-out argument with out length', function () { + // TODO(3v1n0): remove this test when fixing + // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 + if (!GIMarshallingTests.array_inout_length_out) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); + expect(() => GIMarshallingTests.array_inout_length_out([-1, 0, 1, 2])).toThrow(); + }); + + xit('can be an out argument with in length', function () { + const array = GIMarshallingTests.array_out_length_in([-1, 0, 1, 2]); + expect(array).toEqual([-2, 0, -2, -4]); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); + + it('cannot be an out argument with in length', function () { + // TODO(3v1n0): remove this test when fixing + // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 + if (!GIMarshallingTests.array_out_length_in) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); + expect(() => GIMarshallingTests.array_out_length_in([-1, 0, 1, 2])).toThrow(); + }); + it('can be an out argument along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_out_etc(9, 5); expect(sum).toEqual(14); @@ -697,6 +750,56 @@ testSimpleMarshalling('gstrv', ['0', '1', '2'], ['-1', '0', '1', '2']); }); +describe('Array of GStrv', function () { + ['length', 'fixed', 'zero_terminated'].forEach(arrayKind => + ['none', 'container', 'full'].forEach(transfer => { + const testFunction = returnMode => { + const commonName = 'array_of_gstrv_transfer'; + const funcName = [arrayKind, commonName, transfer, returnMode].join('_'); + const func = GIMarshallingTests[funcName]; + if (!func) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); + return func; + }; + + ['out', 'return'].forEach(returnMode => + it(`${arrayKind} ${returnMode} transfer ${transfer}`, function () { + const func = testFunction(returnMode); + expect(func()).toEqual([ + ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], + ]); + })); + + it(`${arrayKind} in transfer ${transfer}`, function () { + const func = testFunction('in'); + if (transfer === 'container') + pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); + + expect(() => func([ + ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], + ])).not.toThrow(); + }); + + it(`${arrayKind} inout transfer ${transfer}`, function () { + const func = testFunction('inout'); + + if (transfer === 'container') + pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); + + const expectedReturn = [ + ['-1', '0', '1', '2'], ['-1', '3', '4', '5'], ['-1', '6', '7', '8'], + ]; + + if (arrayKind !== 'fixed') + expectedReturn.push(['-1', '9', '10', '11']); + + expect(func([ + ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], + ])).toEqual(expectedReturn); + }); + })); +}); + ['GList', 'GSList'].forEach(listKind => { const list = listKind.toLowerCase(); @@ -898,6 +1001,13 @@ .toEqual([42, '42', true]); }); + it('array can be passed as an out argument and unpacked when zero-terminated', function () { + if (!GIMarshallingTests.return_gvalue_zero_terminated_array) + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/397'); + expect(GIMarshallingTests.return_gvalue_zero_terminated_array()) + .toEqual([42, '42', true]); + }); + xit('array can roundtrip with GValues intact', function () { expect(GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', true)) .toEqual([42, '42', true]); @@ -1893,7 +2003,10 @@ }); it('marshals a GError** at the end of the signature as an exception', function () { - expect(() => GIMarshallingTests.gerror_array_in([-1, 0, 1, 2])).toThrow(); + expect(() => GIMarshallingTests.gerror_array_in([-1, 0, 1, 2])).toThrowMatching(e => + e.matches(GLib.quark_from_static_string(GIMarshallingTests.CONSTANT_GERROR_DOMAIN), + GIMarshallingTests.CONSTANT_GERROR_CODE) && + e.message === GIMarshallingTests.CONSTANT_GERROR_MESSAGE); }); it('marshals a GError** elsewhere in the signature as an out parameter', function () { @@ -1976,13 +2089,20 @@ }); function testPropertyGetSet(type, value1, value2, skip = false) { - it(`gets and sets a ${type} property`, function () { - if (skip) - pending(skip); - obj[`some_${type}`] = value1; - expect(obj[`some_${type}`]).toEqual(value1); - obj[`some_${type}`] = value2; - expect(obj[`some_${type}`]).toEqual(value2); + const snakeCase = `some_${type}`; + const paramCase = snakeCase.replaceAll('_', '-'); + const camelCase = snakeCase.replace(/(_\w)/g, + match => match.toUpperCase().replace('_', '')); + + [snakeCase, paramCase, camelCase].forEach(propertyName => { + it(`gets and sets a ${type} property as ${propertyName}`, function () { + if (skip) + pending(skip); + obj[propertyName] = value1; + expect(obj[propertyName]).toEqual(value1); + obj[propertyName] = value2; + expect(obj[propertyName]).toEqual(value2); + }); }); } @@ -2077,7 +2197,7 @@ }); }); -xdescribe('GObject signals', function () { +describe('GObject signals', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.SignalsObject(); @@ -2088,15 +2208,13 @@ if (skip) pending(skip); - function signalCallback(o, arg) { - expect(value).toEqual(arg); - } - + const signalCallback = jasmine.createSpy('signalCallback'); const signalName = `some_${type}`; - const funcName = `emit_${type}`.replace(/-/g, '_'); + const funcName = `emit_${type}`.replaceAll('-', '_'); const signalId = obj.connect(signalName, signalCallback); obj[funcName](); obj.disconnect(signalId); + expect(signalCallback).toHaveBeenCalledOnceWith(obj, value); }); } @@ -2106,4 +2224,13 @@ new GIMarshallingTests.BoxedStruct({long_: 43}), new GIMarshallingTests.BoxedStruct({long_: 44}), ]); + + testSignalEmission('hash-table-utf8-int', { + '-1': 1, + '0': 0, + '1': -1, + '2': -2, + }, !GIMarshallingTests.SignalsObject.prototype.emit_hash_table_utf8_int + ? 'https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/409' + : false); }); diff -Nru gjs-1.76.2/installed-tests/js/testGio.js gjs-1.78.0/installed-tests/js/testGio.js --- gjs-1.76.2/installed-tests/js/testGio.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGio.js 2023-09-17 02:27:20.000000000 +0000 @@ -121,6 +121,18 @@ })).toThrowError(/schema/); }); + it('can construct with a settings schema object', function () { + const source = Gio.SettingsSchemaSource.get_default(); + const settingsSchema = source.lookup('org.gnome.GjsTest', false); + expect(() => new Gio.Settings({settingsSchema})).not.toThrow(); + }); + + it('throws proper error message when settings schema is specified with a wrong type', function () { + expect(() => new Gio.Settings({ + settings_schema: 'string.path', + }).toThrowError('is not of type Gio.SettingsSchema')); + }); + describe('with existing schema', function () { const KINDS = ['boolean', 'double', 'enum', 'flags', 'int', 'int64', 'string', 'strv', 'uint', 'uint64', 'value']; @@ -219,6 +231,12 @@ }); }); +describe('Gio.content_type_set_mime_dirs', function () { + it('can be called with NULL argument', function () { + expect(() => Gio.content_type_set_mime_dirs(null)).not.toThrow(); + }); +}); + describe('Gio.add_action_entries override', function () { it('registers each entry as an action', function () { const app = new Gio.Application(); diff -Nru gjs-1.76.2/installed-tests/js/testGLib.js gjs-1.78.0/installed-tests/js/testGLib.js --- gjs-1.76.2/installed-tests/js/testGLib.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGLib.js 2023-09-17 02:27:20.000000000 +0000 @@ -92,6 +92,25 @@ }); }); +describe('GVariant strv', function () { + let v; + beforeEach(function () { + v = new GLib.Variant('as', ['a', 'b', 'c', 'foo']); + }); + + it('unpacked matches constructed', function () { + expect(v.deepUnpack()).toEqual(['a', 'b', 'c', 'foo']); + }); + + it('getter matches constructed', function () { + expect(v.get_strv()).toEqual(['a', 'b', 'c', 'foo']); + }); + + it('getter (dup) matches constructed', function () { + expect(v.dup_strv()).toEqual(['a', 'b', 'c', 'foo']); + }); +}); + describe('GVariantDict lookup', function () { let variantDict; beforeEach(function () { @@ -114,6 +133,17 @@ }); }); +describe('GLib spawn processes', function () { + it('sync with null envp', function () { + const [ret, stdout, stderr, exit_status] = GLib.spawn_sync( + null, ['true'], null, GLib.SpawnFlags.SEARCH_PATH, null); + expect(ret).toBe(true); + expect(stdout).toEqual(new Uint8Array()); + expect(stderr).toEqual(new Uint8Array()); + expect(exit_status).toBe(0); + }).pend('https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3523'); +}); + describe('GLib string function overrides', function () { let numExpectedWarnings; diff -Nru gjs-1.76.2/installed-tests/js/testGObjectDestructionAccess.js gjs-1.78.0/installed-tests/js/testGObjectDestructionAccess.js --- gjs-1.76.2/installed-tests/js/testGObjectDestructionAccess.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGObjectDestructionAccess.js 2023-09-17 02:27:20.000000000 +0000 @@ -381,9 +381,7 @@ file = null; GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*during garbage collection*'); - GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*dispose*'); + '*during garbage collection*offending callback was dispose()*'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'calls dispose vfunc on explicit disposal only'); @@ -697,7 +695,7 @@ 'can be finalized while queued in toggle queue'); }); - it('can be toggled up-down from various threads while getting a GWeakRef from main', function () { + xit('can be toggled up-down from various threads while getting a GWeakRef from main', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; GjsTestTools.save_weak(file); @@ -738,5 +736,5 @@ GjsTestTools.clear_saved(); System.gc(); expect(GjsTestTools.get_weak()).toBeNull(); - }); + }).pend('Flaky, see https://gitlab.gnome.org/GNOME/gjs/-/issues/NNN'); }); diff -Nru gjs-1.76.2/installed-tests/js/testGtk3.js gjs-1.78.0/installed-tests/js/testGtk3.js --- gjs-1.76.2/installed-tests/js/testGtk3.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGtk3.js 2023-09-17 02:27:20.000000000 +0000 @@ -177,9 +177,7 @@ it('avoid crashing when GTK vfuncs are called in garbage collection', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*during garbage collection*'); - GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*destroy*'); + '*during garbage collection*offending callback was destroy()*'); const BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label { vfunc_destroy() {} @@ -206,9 +204,7 @@ expect(spy).toHaveBeenCalledTimes(1); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*during garbage collection*'); - GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - '*destroy*'); + '*during garbage collection*offending callback was destroy()*'); label = null; System.gc(); GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0, diff -Nru gjs-1.76.2/installed-tests/js/testGtk4.js gjs-1.78.0/installed-tests/js/testGtk4.js --- gjs-1.76.2/installed-tests/js/testGtk4.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testGtk4.js 2023-09-17 02:27:20.000000000 +0000 @@ -5,7 +5,7 @@ imports.gi.versions.Gtk = '4.0'; const ByteArray = imports.byteArray; -const {Gdk, Gio, GObject, Gtk} = imports.gi; +const {Gdk, Gio, GObject, Gtk, GLib} = imports.gi; // This is ugly here, but usually it would be in a resource function createTemplate(className) { @@ -80,6 +80,26 @@ } }); +const MyComplexGtkSubclassFromString = GObject.registerClass({ + Template: createTemplate('Gjs_MyComplexGtkSubclassFromString'), + Children: ['label-child', 'label-child2'], + InternalChildren: ['internal-label-child'], +}, class MyComplexGtkSubclassFromString extends Gtk.Grid { + testChildrenExist() { + expect(this.label_child).toEqual(jasmine.anything()); + expect(this.label_child2).toEqual(jasmine.anything()); + expect(this._internal_label_child).toEqual(jasmine.anything()); + } + + templateCallback(widget) { + this.callbackEmittedBy = widget; + } + + boundCallback(widget) { + widget.callbackBoundTo = this; + } +}); + const [templateFile, stream] = Gio.File.new_tmp(null); const baseStream = stream.get_output_stream(); const out = new Gio.DataOutputStream({baseStream}); @@ -174,9 +194,19 @@ validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); + validateTemplate('UI template from string', MyComplexGtkSubclassFromString); validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); validateTemplate('Class inheriting from template class', SubclassSubclass, true); + it('rejects unsupported template URIs', function () { + expect(() => { + return GObject.registerClass({ + Template: 'https://gnome.org', + }, class GtkTemplateInvalid extends Gtk.Widget { + }); + }).toThrowError(TypeError, /Invalid template URI/); + }); + it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); @@ -224,4 +254,19 @@ custom.activate_action('custom.action', null); expect(custom.action).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3796'); + + it('Gdk.NoSelection section returns valid start/end values', function () { + if (!Gtk.NoSelection.prototype.get_section) + pending('Gtk 4.12 is required'); + + let result; + try { + result = new Gtk.NoSelection().get_section(0); + } catch (err) { + if (err.message.includes('not introspectable')) + pending('This version of GTK has the annotation bug'); + throw err; + } + expect(result).toEqual([0, GLib.MAXUINT32]); + }); }); diff -Nru gjs-1.76.2/installed-tests/js/testPrint.js gjs-1.78.0/installed-tests/js/testPrint.js --- gjs-1.76.2/installed-tests/js/testPrint.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testPrint.js 2023-09-17 02:27:20.000000000 +0000 @@ -194,4 +194,12 @@ expect(prettyPrint({symbol: Symbol.hasInstance})) .toEqual('{ symbol: Symbol.hasInstance }'); }); + + it('undefined', function () { + expect(prettyPrint(undefined)).toEqual('undefined'); + }); + + it('null', function () { + expect(prettyPrint(null)).toEqual('null'); + }); }); diff -Nru gjs-1.76.2/installed-tests/js/testRegress.js gjs-1.78.0/installed-tests/js/testRegress.js --- gjs-1.76.2/installed-tests/js/testRegress.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/js/testRegress.js 2023-09-17 02:27:20.000000000 +0000 @@ -1218,6 +1218,15 @@ o.emit_sig_with_obj(); }); + it('signal with object with gets correct arguments from JS', function (done) { + o.connect('sig-with-obj', (self, objectParam) => { + expect(objectParam.int).toEqual(33); + done(); + }); + const testObj = new Regress.TestObj({int: 33}); + o.emit('sig-with-obj', testObj); + }); + // See testCairo.js for a test of // Regress.TestObj::sig-with-foreign-struct. @@ -1239,6 +1248,26 @@ o.emit_sig_with_uint64(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); + xit('signal with array parameter is properly handled', function (done) { + o.connect('sig-with-array-prop', (signalObj, signalArray, shouldBeUndefined) => { + expect(signalObj).toBe(o); + expect(shouldBeUndefined).not.toBeDefined(); + expect(signalArray).toEqual([0, 1, 2, 3, 4, 5]); + done(); + }); + o.emit('sig-with-array-prop', [0, 1, 2, 3, 4, 5]); + }).pend('Not yet implemented'); + + xit('signal with hash parameter is properly handled', function (done) { + o.connect('sig-with-hash-prop', (signalObj, signalArray, shouldBeUndefined) => { + expect(signalObj).toBe(o); + expect(shouldBeUndefined).not.toBeDefined(); + expect(signalArray).toEqual([0, 1, 2, 3, 4, 5]); + done(); + }); + o.emit('sig-with-hash-prop', {'0': 1}); + }).pend('Not yet implemented'); + it('signal with array len parameter is not passed correct array and no length arg', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray, shouldBeUndefined) => { expect(shouldBeUndefined).not.toBeDefined(); @@ -1248,6 +1277,29 @@ o.emit_sig_with_array_len_prop(); }); + it('signal with GStrv parameter is properly handled', function (done) { + o.connect('sig-with-strv', (signalObj, signalArray, shouldBeUndefined) => { + expect(signalObj).toBe(o); + expect(shouldBeUndefined).not.toBeDefined(); + expect(signalArray).toEqual(['a', 'bb', 'ccc']); + done(); + }); + o.emit('sig-with-strv', ['a', 'bb', 'ccc']); + }); + + xit('signal with int array ret parameter is properly handled', function (done) { + o.connect('sig-with-intarray-ret', (signalObj, signalInt, shouldBeUndefined) => { + expect(signalObj).toBe(o); + expect(shouldBeUndefined).not.toBeDefined(); + expect(signalInt).toEqual(5); + const ret = []; + for (let i = 0; i < signalInt; ++i) + ret.push(i); + done(); + }); + expect(o.emit('sig-with-intarray-ret', 5)).toBe([0, 1, 2, 3, 4]); + }).pend('Not yet implemented'); + xit('can pass parameter to signal with array len parameter via emit', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray) => { expect(signalArray).toEqual([0, 1, 2, 3, 4]); @@ -1287,6 +1339,25 @@ }); o.emit_sig_with_null_error(); }); + + it('GError signal with no GError set from js', function (done) { + o.connect('sig-with-gerror', (obj, e) => { + expect(e).toBeNull(); + done(); + }); + o.emit('sig-with-gerror', null); + }); + + it('GError signal with no GError set from js', function (done) { + o.connect('sig-with-gerror', (obj, e) => { + expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); + expect(e.domain).toEqual(Gio.io_error_quark()); + expect(e.code).toEqual(Gio.IOErrorEnum.EXISTS); + done(); + }); + o.emit('sig-with-gerror', new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.EXISTS, 'We support this!')); + }); }); it('can call an instance method', function () { diff -Nru gjs-1.76.2/installed-tests/meson.build gjs-1.78.0/installed-tests/meson.build --- gjs-1.76.2/installed-tests/meson.build 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/meson.build 2023-09-17 02:27:20.000000000 +0000 @@ -10,6 +10,10 @@ # Simple shell script tests # simple_tests = [] +tests_dependencies = [ + gjs_console, + gjs_private_typelib, +] # The test scripts need to be ported from shell scripts # for clang-cl builds, which do not use BASH-style shells @@ -25,7 +29,7 @@ test_file = files('scripts' / 'test@0@.sh'.format(test)) test(test, test_file, env: tests_environment, protocol: 'tap', - suite: 'Scripts') + suite: 'Scripts', depends: tests_dependencies) test_description_subst = { 'name': 'test@0@.sh'.format(test), @@ -80,7 +84,7 @@ test('@0@ command'.format(test), debugger_test_driver, args: test_file, env: tests_environment, protocol: 'tap', - suite: 'Debugger') + suite: 'Debugger', depends: tests_dependencies) test_description_subst = { 'name': '@0@.debugger'.format(test), diff -Nru gjs-1.76.2/installed-tests/scripts/testCommandLine.sh gjs-1.78.0/installed-tests/scripts/testCommandLine.sh --- gjs-1.76.2/installed-tests/scripts/testCommandLine.sh 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/installed-tests/scripts/testCommandLine.sh 2023-09-17 02:27:20.000000000 +0000 @@ -327,7 +327,8 @@ # https://gitlab.gnome.org/GNOME/gjs/-/issues/19 echo "# VALGRIND = $VALGRIND" if test -z $VALGRIND; then - ASAN_OPTIONS=detect_leaks=0 output=$($gjs -m signalexit.js) + output=$(env LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 \ + $gjs -m signalexit.js) test $? -eq 15 report "exit with correct code from a signal callback" test -n "$output" -a -z "${output##*click 1*}" diff -Nru gjs-1.76.2/meson.build gjs-1.78.0/meson.build --- gjs-1.76.2/meson.build 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/meson.build 2023-09-17 02:27:20.000000000 +0000 @@ -2,10 +2,10 @@ # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan -project('gjs', 'cpp', 'c', version: '1.76.2', license: ['MIT', 'LGPL2+'], +project('gjs', 'cpp', 'c', version: '1.78.0', license: ['MIT', 'LGPL2+'], meson_version: '>= 0.54.0', - default_options: ['cpp_std=c++17', 'cpp_rtti=false', 'c_std=c99', - 'warning_level=2', 'b_pch=true' ]) + default_options: ['cpp_std=c++17', 'cpp_rtti=false', 'cpp_eh=none', + 'c_std=c99', 'warning_level=2', 'b_pch=true' ]) # cpp_rtti: SpiderMonkey can be compiled with or without runtime type # information, and the default is without. We must match that option because we @@ -39,7 +39,7 @@ add_project_arguments(cxx.get_supported_arguments([ '-utf-8', # Use UTF-8 mode '/Zc:externConstexpr', # Required for 'extern constexpr' on MSVC - '/Zc:preprocessor', # Required to consume the mozjs-102 headers on MSVC + '/Zc:preprocessor', # Required to consume the mozjs-115 headers on MSVC # Ignore spurious compiler warnings for things that GLib and SpiderMonkey # header files commonly do @@ -128,7 +128,7 @@ ffi = dependency('libffi', fallback: ['libffi', 'ffi_dep']) gi = dependency('gobject-introspection-1.0', version: '>= 1.66.0', fallback: ['gobject-introspection', 'girepo_dep']) -spidermonkey = dependency('mozjs-102') +spidermonkey = dependency('mozjs-115') # We might need to look for the headers and lib's for Cairo # manually on MSVC/clang-cl builds... @@ -442,7 +442,6 @@ 'gjs/promise.cpp', 'gjs/promise.h', 'gjs/stack.cpp', 'modules/console.cpp', 'modules/console.h', - 'modules/modules.cpp', 'modules/modules.h', 'modules/print.cpp', 'modules/print.h', 'modules/system.cpp', 'modules/system.h', ] @@ -653,6 +652,7 @@ tests_environment.set('TSAN_OPTIONS', 'history_size=5,force_seq_cst_atomics=1,suppressions=@0@'.format( meson.current_source_dir() / 'installed-tests' / 'extra' / 'tsan.supp')) +tests_environment.set('G_SLICE', 'always-malloc') tests_environment.set('NO_AT_BRIDGE', '1') tests_environment.set('GSETTINGS_SCHEMA_DIR', js_tests_builddir) tests_environment.set('GSETTINGS_BACKEND', 'memory') @@ -679,10 +679,6 @@ ### Tests and test setups ###################################################### -if not get_option('skip_gtk_tests') - have_gtk4 = dependency('gtk4', required: false).found() -endif - subdir('installed-tests') # Note: The test program in test/ needs to be ported @@ -813,3 +809,10 @@ 'Dtrace debugging': get_option('dtrace'), 'Systemtap debugging': get_option('systemtap'), }, section: 'Optional features', bool_yn: true) + +### Maintainer scripts ######################################################### + +run_target('maintainer-upload-release', + command: ['build/maintainer-upload-release.sh', + meson.project_name(), + meson.project_version()]) diff -Nru gjs-1.76.2/modules/console.cpp gjs-1.78.0/modules/console.cpp --- gjs-1.76.2/modules/console.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/console.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -34,6 +34,7 @@ #include #include #include +#include // for CurrentGlobalOrNull #include #include #include @@ -224,7 +225,7 @@ { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); volatile bool eof, exit_warning; // accessed after setjmp() - JS::RootedObject global(context, gjs_get_import_global(context)); + JS::RootedObject global{context, JS::CurrentGlobalOrNull(context)}; char* temp_buf; volatile int lineno; // accessed after setjmp() volatile int startline; // accessed after setjmp() diff -Nru gjs-1.76.2/modules/core/overrides/Gio.js gjs-1.78.0/modules/core/overrides/Gio.js --- gjs-1.76.2/modules/core/overrides/Gio.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/core/overrides/Gio.js 2023-09-17 02:27:20.000000000 +0000 @@ -743,6 +743,8 @@ throw new Error('One of property \'schema-id\' or ' + '\'settings-schema\' are required for Gio.Settings'); } + if (settingsSchemaProp && !(props[settingsSchemaProp] instanceof Gio.SettingsSchema)) + throw new Error(`Value of property '${settingsSchemaProp}' is not of type Gio.SettingsSchema`); const source = Gio.SettingsSchemaSource.get_default(); const settingsSchema = settingsSchemaProp diff -Nru gjs-1.76.2/modules/core/overrides/Gtk.js gjs-1.78.0/modules/core/overrides/Gtk.js --- gjs-1.76.2/modules/core/overrides/Gtk.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/core/overrides/Gtk.js 2023-09-17 02:27:20.000000000 +0000 @@ -3,7 +3,7 @@ // SPDX-FileCopyrightText: 2013 Giovanni Campagna const Legacy = imports._legacy; -const {Gio, GjsPrivate, GObject} = imports.gi; +const {Gio, GjsPrivate, GLib, GObject} = imports.gi; const {_registerType} = imports._common; let Gtk; @@ -96,12 +96,25 @@ if (template) { if (typeof template === 'string') { - if (template.startsWith('resource:///')) { - Gtk.Widget.set_template_from_resource.call(klass, - template.slice(11)); - } else if (template.startsWith('file:///')) { - let file = Gio.File.new_for_uri(template); - let [, contents] = file.load_contents(null); + try { + const uri = GLib.Uri.parse(template, GLib.UriFlags.NONE); + const scheme = uri.get_scheme(); + + if (scheme === 'resource') { + Gtk.Widget.set_template_from_resource.call(klass, + uri.get_path()); + } else if (scheme === 'file') { + let file = Gio.File.new_for_uri(template); + let [, contents] = file.load_contents(null); + Gtk.Widget.set_template.call(klass, contents); + } else { + throw new TypeError(`Invalid template URI: ${template}`); + } + } catch (err) { + if (!(err instanceof GLib.UriError)) + throw err; + + let contents = new TextEncoder().encode(template); Gtk.Widget.set_template.call(klass, contents); } } else { diff -Nru gjs-1.76.2/modules/esm/console.js gjs-1.78.0/modules/esm/console.js --- gjs-1.76.2/modules/esm/console.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/esm/console.js 2023-09-17 02:27:20.000000000 +0000 @@ -40,8 +40,9 @@ * @returns {string} */ function formatOptimally(item) { + const GLib = imports.gi.GLib; // Handle optimal error formatting. - if (item instanceof Error) { + if (item instanceof Error || item instanceof GLib.Error) { return `${item.toString()}${item.stack ? '\n' : ''}${item.stack ?.split('\n') // Pad each stacktrace line. @@ -52,6 +53,10 @@ // TODO: Enhance 'optimal' formatting. // There is a current work on a better object formatter for GJS in // https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/587 + if (typeof item === 'object' && item !== null) { + if (item.constructor?.name !== 'Object') + return `${item.constructor?.name} ${JSON.stringify(item, null, 4)}`; + } return JSON.stringify(item, null, 4); } diff -Nru gjs-1.76.2/modules/internal/loader.js gjs-1.78.0/modules/internal/loader.js --- gjs-1.76.2/modules/internal/loader.js 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/internal/loader.js 2023-09-17 02:27:20.000000000 +0000 @@ -329,37 +329,32 @@ * Resolves a module import with optional handling for relative imports. * Overrides InternalModuleLoader.moduleResolveHook * - * @param {ModulePrivate} importingModulePriv - * the private object of the module initiating the import + * @param {ModulePrivate | null} importingModulePriv - the private object of + * the module initiating the import, null if the import is not coming from + * a file that can resolve relative imports * @param {string} specifier the module specifier to resolve for an import * @returns {import("./internalLoader").Module} */ moduleResolveHook(importingModulePriv, specifier) { - const module = this.resolveModule(specifier, importingModulePriv.uri); + const module = this.resolveModule(specifier, importingModulePriv?.uri); if (module) return module; return this.resolveBareSpecifier(specifier); } - moduleResolveAsyncHook(importingModulePriv, specifier) { - // importingModulePriv should never be missing. If it is then a JSScript - // is missing a private object - if (!importingModulePriv || !importingModulePriv.uri) - throw new ImportError('Cannot resolve relative imports from an unknown file.'); - - return this.resolveModuleAsync(specifier, importingModulePriv.uri); - } - /** * Resolves a module import with optional handling for relative imports asynchronously. * - * @param {string} specifier the specifier (e.g. relative path, root package) to resolve - * @param {string | null} importingModuleURI the URI of the module - * triggering this resolve - * @returns {import("../types").Module} + * @param {ModulePrivate | null} importingModulePriv - the private object of + * the module initiating the import, null if the import is not coming from + * a file that can resolve relative imports + * @param {string} specifier - the specifier (e.g. relative path, root + * package) to resolve + * @returns {Promise} */ - async resolveModuleAsync(specifier, importingModuleURI) { + async moduleResolveAsyncHook(importingModulePriv, specifier) { + const importingModuleURI = importingModulePriv?.uri; const registry = getRegistry(this.global); // Check if the module has already been loaded diff -Nru gjs-1.76.2/modules/modules.cpp gjs-1.78.0/modules/modules.cpp --- gjs-1.76.2/modules/modules.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/modules.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ -// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -// SPDX-FileCopyrightText: 2013 Red Hat, Inc. - -#include // for ENABLE_CAIRO - -#include "gjs/native.h" -#include "modules/console.h" -#include "modules/modules.h" -#include "modules/print.h" -#include "modules/system.h" - -#ifdef ENABLE_CAIRO -# include "modules/cairo-module.h" -#endif - -void gjs_register_static_modules(void) { - Gjs::NativeModuleRegistry& registry = Gjs::NativeModuleRegistry::get(); -#ifdef ENABLE_CAIRO - registry.add("cairoNative", gjs_js_define_cairo_stuff); -#endif - registry.add("system", gjs_js_define_system_stuff); - registry.add("console", gjs_define_console_stuff); - registry.add("_print", gjs_define_print_stuff); -} diff -Nru gjs-1.76.2/modules/modules.h gjs-1.78.0/modules/modules.h --- gjs-1.76.2/modules/modules.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/modules/modules.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ -// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -// SPDX-FileCopyrightText: 2013 Red Hat, Inc. - -#ifndef MODULES_MODULES_H_ -#define MODULES_MODULES_H_ - -void gjs_register_static_modules (void); - -#endif // MODULES_MODULES_H_ diff -Nru gjs-1.76.2/NEWS gjs-1.78.0/NEWS --- gjs-1.76.2/NEWS 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/NEWS 2023-09-17 02:27:20.000000000 +0000 @@ -1,3 +1,111 @@ +Version 1.78.0 +-------------- + +- Closed bugs and merge requests: + * Improved Console.log Output [!886, Sriyansh Shivam] + * `gjs:dbus / Gtk4` unit test fails: Function Gtk.SectionModel.get_section() + cannot be called [#575, !889, Matt Turner] + +Version 1.77.90 +--------------- + +- Building GJS with -fno-exceptions is now the default. To retain the previous + behaviour, invoke Meson with -Dcpp_eh=default. + +- Closed bugs and merge requests: + * testEverything fails make check [#95, !858, Marco Trevisan] + * Using a Gio.Appinfo().launch with context may crash gjs [#553, !858, Marco + Trevisan] + * Fixed-size and Zero-terminated arrays are leaked when used as in or inout + arguments with transfer none [#561, !858, Marco Trevisan] + * Crash due to bad memory usage when calling a function taking an inout array + with length parameter and transfer full [#562, !858, Marco Trevisan] + * Various maintenance [!875, !888, Philip Chimento, Marco Trevisan, Andy + Holmes] + * README.MSVC.md: Update for SpiderMonkey-115.x [!877, Chun-wei Fan] + * GJS returns pointers instead of numbers for function with output parameters + [#570, !878, Philip Chimento, Marco Trevisan] + * Profiler spuriously records GJS.boxed_instance and GJS.boxed_prototype + [#551, !879, Philip Chimento] + * installed-tests/js/meson: Add tests dependencies to dbus tests [!880, Marco + Trevisan] + * eslint: Make multi-line imports to always include a trailing comma [!881, + Marco Trevisan] + * Make console.error format GError correctly [#572, !883, Sriyansh Shivam] + * Gtk: Throw an error for an invalid Template string [!884, Andy Holmes] + * Gtk: Attempt to load Template from a string, if it appears valid [!885, Andy + Holmes] + * global: Really enable non-mutating Array methods [!887, Philip Chimento] + +Version 1.77.2 +-------------- + +- New JavaScript features! This version of GJS is based on SpiderMonkey 115, an + upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 102. + Here are the highlights of the new JavaScript features. + For more information, look them up on MDN or devdocs.io. + + * New APIs + + Arrays and typed arrays have gained `findLast()` and `findLastIndex()` + methods, which act like `find()` and `findIndex()` respectively, but start + searching at the end of the array. + + Arrays and typed arrays have gained the `with()` method, which returns a + copy of the array with one element replaced. + + Arrays and typed arrays have gained `toReversed()`, `toSorted()`, and + `toSpliced()` methods, which act like `reverse()`, `sort()`, and + `splice()` respectively, but return a copy of the array instead of + modifying it in-place. + + The `Array.fromAsync()` static method acts like `Array.from()` but with + async iterables, and returns a Promise that fulfills to the new Array. + +- It is now possible to build GJS with -fno-exceptions, by invoking Meson with + -Dcpp_eh=none. + +- Closed bugs and merge requests: + * Port to mozjs115 [#556, !855, !871, !874, Xi Ruoyao, Philip Chimento] + * Various maintenance [!856, Philip Chimento] + * arg: Preserve transfer when freeing out arrays [!857, Marco Trevisan] + * Some values leak fixes and cleanups [!860, Marco Trevisan] + * Does not parse hash tables in signals [#488, !861, Marco Trevisan] + * docs: fix minor URL mistakes and behavioural omissions [!865, Andy Holmes] + * gjs: Listen to GMemoryMonitor::low-memory-warning to trigger GC [!870, Marco + Trevisan] + * GSettings override in Gio.js may fail on construction [#418, !873, Onur + Şahin] + * Gio: Fix constructing Settings with a SettingsSchema object [!876, James + Westman, Philip Chimento] + +Version 1.77.1 +-------------- + +- Includes all fixes from 1.76.1 and 1.76.2. + +- Many documentation improvements and cleanups. + +- New API for C programs embedding GJS: gjs_context_run_in_realm(). + This allows using the SpiderMonkey API, for advanced use cases, while having + entered the main realm where GJS code runs. Most programs will not need to use + this. + +- Closed bugs and merge requests: + * Cleanups: Use more autopointers [!763, Marco Trevisan] + * bug(build, tests): broken dependency cycle associated with the `have_gtk4` + variable [#532, !830, Dominik Opyd] + * Better handling of callbacks during GC [!832, Sebastian Keller] + * doc: Add Gio and GLib runAsync overrides [!833, Sonny Piers] + * installed-tests/meson: Add tests dependencies on gjs console and GjsPrivate + [!835, Marco Trevisan] + * gi/arg: Cleanup handling of C arrays and GValue arrays [!836, Marco + Trevisan] + * Various maintenance [!838, !848, Philip Chimento] + * doc: Fix http-client.js example [!840, Sonny Piers] + * use `meson setup` instead of ambiguous `meson` [!842, Angelo Verlain] + * docs: document `GObject.gtypeNameBasedOnJSPath` [!844, Andy Holmes] + * docs: fix formatting for `Signals.md` [!845, Andy Holmes] + * Provide API to get GTypes defined in a module [#536, !846, Philip Chimento] + * doc: Update inroduction [!847, Sonny Piers] + * gi/args.cpp: Fix build with Visual Studio [!854, Chun-wei Fan] + Version 1.76.2 -------------- @@ -29,6 +137,65 @@ * "flat" arrays of GObject's are leaked [#542, !837, Marco Trevisan] * gjs can't print null [#545, !841, Angelo Verlain] +Version 1.74.3 +-------------- + +- Various fixes ported from the development branch. + +- Closed bugs and merge requests: + * Possible errors in cairo enums [#516, !811, !852, Vítor Vasconcellos] + * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, + !852, tuberry] + * Handle transfer-none string return value from vfunc implemented in JS [#519, + !821, !823, !852, Marco Trevisan, Daniel van Vugt] + * GJS freezes, program stops responding, error states Gtk4 EventController + GestureClick returns incorrect state- Gdk.ModifierType on mouse button press + in X11 [#507, !829, !852, Sundeep Mediratta] + * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, !852, + Daniel van Vugt] + * Memory leak with GError [#36, !837, !852, Marco Trevisan] + * GVariant return values leaked [#499, !837, !852, Marco Trevisan] + * GBytes's are leaked when passed as-is to a function [#539, !837, !852, Marco + Trevisan] + * Transformed GValues are leaking temporary instances [#540, !837, !852, Marco + Trevisan] + * GHash value infos are leaked [#541, !837, !852, Marco Trevisan] + * "flat" arrays of GObject's are leaked [#542, !837, !852, Marco Trevisan] + * Gjs console leaks invalid option errors [#544, !837, !852, Marco Trevisan] + +Version 1.72.4 +-------------- + +- Various fixes ported from the development branch. + +- Closed bugs and merge requests: + * log_set_writer_func is not safe to use [#481, !766, !851, Evan Welsh] + * Gnome-Shell 42 - crash after login (general protection fault) [#479, !740, + !851, Xi Ruoyao] + * Static methods on classes from GObject introspection are now present on JS + classes that inherit from those classes. [!851, Marco Trevisan] + * Enabling window-list extension causes gnome-shell to crash when running + "dconf update" as root [#510, !813, !851, Philip Chimento] + * Possible errors in cairo enums [#516, !811, !851, Vítor Vasconcellos] + * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, + !851, tuberry] + * Handle transfer-none string return value from vfunc implemented in JS [#519, + !821, !823, !851, Marco Trevisan, Daniel van Vugt] + * GJS freezes, program stops responding, error states Gtk4 EventController + GestureClick returns incorrect state- Gdk.ModifierType on mouse button press + in X11 [#507, !829, !851, Sundeep Mediratta] + * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, !851, + Daniel van Vugt] + * Memory leak with GError [#36, !837, !851, Marco Trevisan] + * GVariant return values leaked [#499, !837, !851, Marco Trevisan] + * GBytes's are leaked when passed as-is to a function [#539, !837, !851, Marco + Trevisan] + * Transformed GValues are leaking temporary instances [#540, !837, !851, Marco + Trevisan] + * GHash value infos are leaked [#541, !837, !851, Marco Trevisan] + * "flat" arrays of GObject's are leaked [#542, !837, !851, Marco Trevisan] + * Gjs console leaks invalid option errors [#544, !837, !851, Marco Trevisan] + Version 1.76.0 -------------- diff -Nru gjs-1.76.2/README.MSVC.md gjs-1.78.0/README.MSVC.md --- gjs-1.76.2/README.MSVC.md 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/README.MSVC.md 2023-09-17 02:27:20.000000000 +0000 @@ -7,27 +7,27 @@ clang-cl, as we will still use items from the Windows SDK. Recent official binary installers of CLang (which contains clang-cl) -from the LLVM website are known to work to build SpiderMonkey 102 and +from the LLVM website are known to work to build SpiderMonkey 115 and GJS. You will need the following items to build GJS using Visual Studio or clang-cl (they can be built with Visual Studio 2015 or later, unless otherwise noted): -- SpiderMonkey 102.x (mozjs-102). This must be built with clang-cl as +- SpiderMonkey 115.x (mozjs-115). This must be built with clang-cl as the Visual Studio compiler is no longer supported for building this. Please see the below section carefully on this... -- GObject-Introspection (G-I) 1.61.2 or later -- GLib 2.58.x or later, (which includes GIO, GObject, and the +- GObject-Introspection (G-I) 1.66.x or later +- GLib 2.66.x or later, (which includes GIO, GObject, and the associated tools) - Cairo including Cairo-GObject support (Optional) -- GTK+-3.20.x or later (Optional) +- GTK+-4.x or later (Optional) - and anything that the above items depend on. Note again that SpiderMonkey must be built using Visual Studio with clang-cl, and the rest should preferably be built with Visual Studio or clang-cl as well. The Visual Studio version used for building the other dependencies should preferably be the same across the board, or, -if using Visual Studio 2015 or later, Visual Studio 2015 through 2019. +if using Visual Studio 2015 or later, Visual Studio 2015 through 2022. Please also be aware that the Rust MSVC toolchains that correspond to the platform you are building for must also be present to build @@ -44,8 +44,8 @@ the GJS version that is being built, as GJS depends on ESR (Extended Service Release, a.k.a Long-term support) releases of SpiderMonkey. -You may also be able to obtain the SpiderMonkey 102.x sources via the -FireFox (ESR) or Thunderbird 102.x sources, in $(srcroot)/js. +You may also be able to obtain the SpiderMonkey 115.x sources via the +FireFox (ESR) or Thunderbird 115.x sources, in $(srcroot)/js. Please do note that the build must be done carefully, in addition to the official instructions that are posted on the Mozilla website: @@ -53,7 +53,7 @@ https://firefox-source-docs.mozilla.org/js/build.html You will need to create a .mozconfig file that will describe your build -options for the build in the root directory of the Firefox/ThunderBird 102.x +options for the build in the root directory of the Firefox/ThunderBird 115.x sources. A sample content of the .mozconfig file can be added as follows: ``` @@ -65,7 +65,7 @@ ac_add_options --enable-optimize ac_add_options --disable-debug ac_add_options --disable-jemalloc -ac_add_options --prefix=c:/software.b/mozjs102.bin +ac_add_options --prefix=c:/software.b/mozjs115.bin ``` An explanation of the lines above: @@ -77,16 +77,13 @@ * `ac_add_options --enable-optimize`: Use for release builds of SpiderMonkey. Use `--disable-optimize` instead if building with `--enable-debug` * `ac_add_options --enable-debug`: Include debugging functions, for debug builds. Use `--disable-debug` instead if building with `--enable-optimize` * `ac_add_options --disable-jemalloc`: This is absolutely needed, otherwise GJS will not build and run correctly -* `ac_add_options --prefix=c:/software.b/mozjs102.bin`: Some installation path, change as needed +* `ac_add_options --prefix=c:/software.b/mozjs115.bin`: Some installation path, change as needed If your GJS build crashes upon launch, use Dependency Walker to ensure that -mozjs-102.dll does not depend on mozglue.dll! If it does, or if GJS fails to +mozjs-115.dll does not depend on mozglue.dll! If it does, or if GJS fails to link with missing arena_malloc() and friends symbols, you have built SpiderMoney incorrectly and will need to rebuild SpiderMonkey (with the build options as -noted above) and retry the build. Because SpiderMonkey needs to be built -without jemalloc, enclose the entire `DllMain()` implementation in -`$(srcroot)/js/src/jsapi.cpp` with `#if 0` ... `#endif`, otherwise -SpiderMonkey will fail to link. +noted above) and retry the build. Please also check that `--enable-optimize` is *not* used with `--enable-debug`. You should explicitly enable one and disable the other, as `--enable-debug` @@ -120,28 +117,17 @@ you will need to search for them in $(buildroot), where the PDB file names match the filenames for the DLLs/EXEs in $(buildroot)/dist/bin, and you will need to look for the following .lib files: --mozjs-102.lib +-mozjs-115.lib -js_static.lib (optional) -Due to some bugs that are not yet resolved in upstream SpiderMonkey 102.x, -you may need to do the following after running `./mach build install` and -before attempting to build GJS itself: - -* Copy `$(builddir)/dist/include/js/ProfilingCategoryList.h` to - `$(PREFIX)\include\mozjs-102\js`. - -* Change `$(PREFIX)\include\mozjs-102\mozilla\EnumSet.h` and change line - 329 from `static constexpr size_t kMaxBits = EnumSet().MaxBits();` - to `size_t kMaxBits = EnumSet().MaxBits();`. - You may want to put the .lib's and DLLs/EXEs into $(PREFIX)\lib and $(PREFIX)\bin respectively, and put the headers into -$(PREFIX)\include\mozjs-102 for convenience. +$(PREFIX)\include\mozjs-115 for convenience. -You will need to place the generated mozjs-102.pc pkg-config file into +You will need to place the generated mozjs-115.pc pkg-config file into $(PREFIX)\lib\pkgconfig and ensure that pkg-config can find it by setting PKG_CONFIG_PATH. Ensure that the 'includedir' and 'libdir' -in there is correct so that the mozjs-102.pc can be used correctly in +in there is correct so that the mozjs-115.pc can be used correctly in Visual Studio/clang-cl builds, and replace the `-isystem` with `-I` if building GJS with Visual Studio. You will also need to ensure that the existing GObject-Introspection installation (if used) is on the same diff -Nru gjs-1.76.2/test/extra/Dockerfile gjs-1.78.0/test/extra/Dockerfile --- gjs-1.76.2/test/extra/Dockerfile 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/extra/Dockerfile 2023-09-17 02:27:20.000000000 +0000 @@ -3,7 +3,7 @@ # === Build Spidermonkey stage === -FROM registry.fedoraproject.org/fedora:37 AS mozjs-build +FROM registry.fedoraproject.org/fedora:38 AS mozjs-build ARG MOZJS_BRANCH=mozjs102 ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} ARG BUILD_OPTS= @@ -40,7 +40,7 @@ # === Actual Docker image === -FROM registry.fedoraproject.org/fedora:37 +FROM registry.fedoraproject.org/fedora:38 ARG LOCALES=tr_TR ENV SHELL=/bin/bash diff -Nru gjs-1.76.2/test/extra/Dockerfile.debug gjs-1.78.0/test/extra/Dockerfile.debug --- gjs-1.76.2/test/extra/Dockerfile.debug 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/extra/Dockerfile.debug 2023-09-17 02:27:20.000000000 +0000 @@ -3,7 +3,7 @@ # === Build stage === -FROM registry.fedoraproject.org/fedora:37 AS build +FROM registry.fedoraproject.org/fedora:38 AS build ARG MOZJS_BRANCH=mozjs102 ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} ARG BUILD_OPTS= @@ -30,9 +30,9 @@ WORKDIR /root RUN mkdir -p include-what-you-use/_build -ADD https://include-what-you-use.org/downloads/include-what-you-use-0.19.src.tar.gz /root/include-what-you-use/ +ADD https://include-what-you-use.org/downloads/include-what-you-use-0.20.src.tar.gz /root/include-what-you-use/ WORKDIR /root/include-what-you-use -RUN tar xzf include-what-you-use-0.19.src.tar.gz --strip-components=1 +RUN tar xzf include-what-you-use-0.20.src.tar.gz --strip-components=1 WORKDIR /root/include-what-you-use/_build @@ -55,9 +55,19 @@ RUN DESTDIR=/root/mozjs-install make install RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs +WORKDIR /root + +# Install gnome-introspection from main, so that we can test against it +RUN dnf -y builddep gobject-introspection +RUN git clone https://gitlab.gnome.org/GNOME/gobject-introspection.git + +WORKDIR /root/gobject-introspection +RUN meson setup _build . --prefix=/opt/GNOME --buildtype debugoptimized +RUN DESTDIR=/root/g-i-install ninja -C _build install + # === Actual Docker image === -FROM registry.fedoraproject.org/fedora:37 +FROM registry.fedoraproject.org/fedora:38 ARG LOCALES=tr_TR ENV SHELL=/bin/bash @@ -116,6 +126,7 @@ COPY --from=build /root/mozjs-install/usr /usr COPY --from=build /root/iwyu-install/usr /usr +COPY --from=build /root/g-i-install/opt /opt RUN ln -s /usr/bin/iwyu_tool.py /usr/bin/iwyu_tool # Enable sudo for wheel users diff -Nru gjs-1.76.2/test/gjs-test-call-args.cpp gjs-1.78.0/test/gjs-test-call-args.cpp --- gjs-1.76.2/test/gjs-test-call-args.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-call-args.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -11,6 +11,7 @@ #include #include #include +#include // for CurrentGlobalOrNull #include #include #include @@ -299,7 +300,7 @@ { gjs_unit_test_fixture_setup(fx, unused); - JS::RootedObject global(fx->cx, gjs_get_import_global(fx->cx)); + JS::RootedObject global{fx->cx, JS::CurrentGlobalOrNull(fx->cx)}; bool success = JS_DefineFunctions(fx->cx, global, native_test_funcs); g_assert_true(success); } diff -Nru gjs-1.76.2/test/gjs-test-coverage.cpp gjs-1.78.0/test/gjs-test-coverage.cpp --- gjs-1.76.2/test/gjs-test-coverage.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-coverage.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -36,7 +36,7 @@ replace_file(GFile *file, const char *contents) { - GError *error = NULL; + GjsAutoError error; g_file_replace_contents(file, contents, strlen(contents), NULL /* etag */, FALSE /* make backup */, G_FILE_CREATE_NONE, NULL /* etag out */, NULL /* cancellable */, &error); diff -Nru gjs-1.76.2/test/gjs-test-jsapi-utils.cpp gjs-1.78.0/test/gjs-test-jsapi-utils.cpp --- gjs-1.76.2/test/gjs-test-jsapi-utils.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-jsapi-utils.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -146,6 +146,22 @@ g_assert_cmpint(autoptr[0].val, ==, 5); g_assert_cmpint(autoptr[1].val, ==, 5); g_assert_cmpint(autoptr[2].val, ==, 5); + + autoptr[1].val = 4; + + TestStruct const& const_struct_const_1 = autoptr[1]; + g_assert_cmpint(const_struct_const_1.val, ==, 4); + // const_struct_const_1.val = 3; // This will would not compile + + TestStruct& test_struct_1 = autoptr[1]; + test_struct_1.val = 3; + g_assert_cmpint(test_struct_1.val, ==, 3); + + int* int_ptrs = new int[3]{5, 6, 7}; + GjsAutoCppPointer int_autoptr(int_ptrs); + g_assert_cmpint(int_autoptr[0], ==, 5); + g_assert_cmpint(int_autoptr[1], ==, 6); + g_assert_cmpint(int_autoptr[2], ==, 7); } g_assert_cmpuint(deleted, ==, 3); @@ -547,6 +563,27 @@ gjs_test_object_get_type()); } +static void test_gjs_error_init() { + GjsAutoError error = + g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_EXIST, "Message"); + + g_assert_nonnull(error); + g_assert_cmpint(error->domain, ==, G_FILE_ERROR); + g_assert_cmpint(error->code, ==, G_FILE_ERROR_EXIST); + g_assert_cmpstr(error->message, ==, "Message"); + + error = g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_FAILED, "Other"); + g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED); + g_assert_cmpstr(error->message, ==, "Other"); +} + +static void test_gjs_error_out() { + GjsAutoError error( + g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_EXIST, "Message")); + g_clear_error(&error); + g_assert_null(error); +} + #define ADD_AUTOPTRTEST(path, func) \ g_test_add(path, Fixture, nullptr, setup, func, teardown); @@ -651,4 +688,8 @@ g_test_add_func("/gjs/jsapi-utils/gjs-autotypeclass/init", test_gjs_autotypeclass_init); + + g_test_add_func("/gjs/jsapi-utils/gjs-autoerror/init", test_gjs_error_init); + g_test_add_func("/gjs/jsapi-utils/gjs-autoerror/as-out-value", + test_gjs_error_out); } diff -Nru gjs-1.76.2/test/gjs-tests.cpp gjs-1.78.0/test/gjs-tests.cpp --- gjs-1.76.2/test/gjs-tests.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-tests.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -19,10 +19,14 @@ #include #include +#include +#include #include #include #include +#include #include +#include #include #include #include // for UniqueChars @@ -41,6 +45,10 @@ #include "test/gjs-test-utils.h" #include "util/misc.h" +namespace mozilla { +union Utf8Unit; +} + // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, @@ -117,7 +125,7 @@ { GjsContext *context; int estatus; - GError *error = NULL; + GjsAutoError error; context = gjs_context_new (); if (!gjs_context_eval (context, "1+1", -1, "", &estatus, &error)) @@ -127,7 +135,7 @@ static void gjstest_test_func_gjs_context_eval_dynamic_import() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = NULL; + GjsAutoError error; int status; bool ok = gjs_context_eval(gjs, R"js( @@ -144,7 +152,7 @@ static void gjstest_test_func_gjs_context_eval_dynamic_import_relative() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = NULL; + GjsAutoError error; int status; bool ok = g_file_set_contents("num.js", "export default 77;", -1, &error); @@ -172,7 +180,7 @@ static void gjstest_test_func_gjs_context_eval_dynamic_import_bad() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = NULL; + GjsAutoError error; int status; g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, @@ -196,13 +204,11 @@ g_assert_cmpuint(status, ==, 10); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_non_zero_terminated(void) { GjsAutoUnref gjs = gjs_context_new(); - GError* error = NULL; + GjsAutoError error; int status; // This string is invalid JS if it is treated as zero-terminated @@ -217,7 +223,7 @@ gjstest_test_func_gjs_context_exit(void) { GjsContext *context = gjs_context_new(); - GError *error = NULL; + GjsAutoError error; int status; bool ok = gjs_context_eval(context, "imports.system.exit(0);", -1, @@ -226,7 +232,7 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(status, ==, 0); - g_clear_error(&error); + error.reset(); ok = gjs_context_eval(context, "imports.system.exit(42);", -1, "", &status, &error); @@ -234,14 +240,13 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(status, ==, 42); - g_clear_error(&error); g_object_unref(context); } static void gjstest_test_func_gjs_context_eval_module_file() { GjsAutoUnref gjs = gjs_context_new(); uint8_t exit_status; - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_eval_module_file( gjs, "resource:///org/gnome/gjs/mock/test/modules/default.js", @@ -256,7 +261,7 @@ static void gjstest_test_func_gjs_context_eval_module_file_throw() { GjsAutoUnref gjs = gjs_context_new(); uint8_t exit_status; - GError* error = nullptr; + GjsAutoError error; g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); @@ -269,13 +274,11 @@ g_assert_cmpuint(exit_status, ==, 1); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_file_exit() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; uint8_t exit_status; bool ok = gjs_context_eval_module_file( @@ -286,7 +289,7 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(exit_status, ==, 0); - g_clear_error(&error); + error.reset(); ok = gjs_context_eval_module_file( gjs, "resource:///org/gnome/gjs/mock/test/modules/exit.js", @@ -295,19 +298,17 @@ g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(exit_status, ==, 42); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_file_fail_instantiate() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; uint8_t exit_status; g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "*foo*"); // evaluating this module without registering 'foo' first should make it - // fail ModuleInstantiate + // fail ModuleLink bool ok = gjs_context_eval_module_file( gjs, "resource:///org/gnome/gjs/mock/test/modules/import.js", &exit_status, &error); @@ -317,13 +318,11 @@ g_assert_cmpuint(exit_status, ==, 1); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_warning() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "*foo*"); @@ -335,14 +334,12 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_no_warning() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_eval_module_file( gjs, "resource:///org/gnome/gjs/mock/test/modules/default.js", nullptr, @@ -350,12 +347,11 @@ g_assert_true(ok); g_assert_no_error(error); - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_throw() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); @@ -367,13 +363,11 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_no_throw() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_eval_file( gjs, "resource:///org/gnome/gjs/mock/test/modules/nothrows.js", nullptr, @@ -381,13 +375,11 @@ g_assert_true(ok); g_assert_no_error(error); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_register_module_eval_module() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_register_module( gjs, "foo", "resource:///org/gnome/gjs/mock/test/modules/default.js", @@ -406,7 +398,7 @@ static void gjstest_test_func_gjs_context_register_module_eval_module_file() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_register_module( gjs, "foo", "resource:///org/gnome/gjs/mock/test/modules/default.js", @@ -425,21 +417,83 @@ g_assert_cmpuint(exit_status, ==, 0); } +static void gjstest_test_func_gjs_context_register_module_eval_jsapi( + GjsUnitTestFixture* fx, const void*) { + GjsAutoError error; + + bool ok = gjs_context_register_module( + fx->gjs_context, "foo", + "resource:///org/gnome/gjs/mock/test/modules/default.js", &error); + g_assert_true(ok); + g_assert_no_error(error); + + JS::CompileOptions options{fx->cx}; + options.setFileAndLine("import.js", 1); + static const char* code = R"js( + let error; + const loop = new imports.gi.GLib.MainLoop(null, false); + import('foo') + .then(module => { + if (module.default !== 77) + throw new Error('wrong number'); + }) + .catch(e => (error = e)) + .finally(() => loop.quit()); + loop.run(); + if (error) + throw error; + )js"; + JS::SourceText source; + ok = source.init(fx->cx, code, strlen(code), JS::SourceOwnership::Borrowed); + g_assert_true(ok); + + JS::RootedValue unused{fx->cx}; + ok = JS::Evaluate(fx->cx, options, source, &unused); + gjs_log_exception(fx->cx); // will fail test if exception pending + g_assert_true(ok); +} + +static void gjstest_test_func_gjs_context_register_module_eval_jsapi_rel( + GjsUnitTestFixture* fx, const void*) { + JS::CompileOptions options{fx->cx}; + options.setFileAndLine("import.js", 1); + static const char* code = R"js( + let error; + const loop = new imports.gi.GLib.MainLoop(null, false); + import('./foo.js') + .catch(e => (error = e)) + .finally(() => loop.quit()); + loop.run(); + if (error) + throw error; + )js"; + JS::SourceText source; + bool ok = + source.init(fx->cx, code, strlen(code), JS::SourceOwnership::Borrowed); + g_assert_true(ok); + + JS::RootedValue unused{fx->cx}; + ok = JS::Evaluate(fx->cx, options, source, &unused); + g_assert_false(ok); + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, + "JS ERROR: ImportError*relative*"); + gjs_log_exception(fx->cx); + g_test_assert_expected_messages(); +} + static void gjstest_test_func_gjs_context_register_module_non_existent() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_register_module(gjs, "foo", "nonexist.js", &error); g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_unregistered() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; uint8_t exit_status; bool ok = gjs_context_eval_module(gjs, "foo", &exit_status, &error); @@ -447,25 +501,21 @@ g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); g_assert_cmpuint(exit_status, ==, 1); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_throw() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_eval_module(gjs, "foo", nullptr, &error); g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; bool ok = gjs_context_register_module( gjs, "lies", "resource:///org/gnome/gjs/mock/test/modules/nothrows.js", @@ -478,8 +528,79 @@ g_assert_true(ok); g_assert_no_error(error); +} + +static void gjstest_test_func_gjs_context_module_eval_jsapi_throws( + GjsUnitTestFixture* fx, const void*) { + GjsAutoError error; + + bool ok = gjs_context_register_module( + fx->gjs_context, "foo", + "resource:///org/gnome/gjs/mock/test/modules/throws.js", &error); + g_assert_true(ok); + g_assert_no_error(error); + + JS::CompileOptions options{fx->cx}; + options.setFileAndLine("import.js", 1); + static const char* code = R"js( + let error; + const loop = new imports.gi.GLib.MainLoop(null, false); + import('foo') + .catch(e => (error = e)) + .finally(() => loop.quit()); + loop.run(); + error; + )js"; + JS::SourceText source; + ok = source.init(fx->cx, code, strlen(code), JS::SourceOwnership::Borrowed); + g_assert_true(ok); + + JS::RootedValue thrown{fx->cx}; + ok = JS::Evaluate(fx->cx, options, source, &thrown); + gjs_log_exception(fx->cx); // will fail test if exception pending + + g_assert_true(ok); + + g_assert_true(thrown.isObject()); + JS::RootedObject thrown_obj{fx->cx, &thrown.toObject()}; + JS::RootedValue message{fx->cx}; + ok = JS_GetProperty(fx->cx, thrown_obj, "message", &message); + g_assert_true(ok); + g_assert_true(message.isString()); + bool match = false; + ok = JS_StringEqualsAscii(fx->cx, message.toString(), "bad module", &match); + g_assert_true(ok); + g_assert_true(match); +} + +static void gjstest_test_func_gjs_context_run_in_realm() { + GjsAutoUnref gjs = gjs_context_new(); - g_clear_error(&error); + auto* cx = static_cast(gjs_context_get_native_context(gjs)); + g_assert_null(JS::GetCurrentRealmOrNull(cx)); + + struct RunInRealmData { + int sentinel; + bool has_run; + } data{42, false}; + + gjs_context_run_in_realm( + gjs, + [](GjsContext* gjs, void* ptr) { + g_assert_true(GJS_IS_CONTEXT(gjs)); + auto* data = static_cast(ptr); + g_assert_cmpint(data->sentinel, ==, 42); + + auto* cx = + static_cast(gjs_context_get_native_context(gjs)); + g_assert_nonnull(JS::GetCurrentRealmOrNull(cx)); + + data->has_run = true; + }, + &data); + + g_assert_null(JS::GetCurrentRealmOrNull(cx)); + g_assert_true(data.has_run); } #define JS_CLASS "\ @@ -491,7 +612,7 @@ gjstest_test_func_gjs_gobject_js_defined_type(void) { GjsContext *context = gjs_context_new(); - GError *error = NULL; + GjsAutoError error; int status; bool ok = gjs_context_eval(context, JS_CLASS, -1, "", &status, &error); g_assert_no_error(error); @@ -509,7 +630,7 @@ static void gjstest_test_func_gjs_gobject_without_introspection(void) { GjsAutoUnref context = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; int status; /* Ensure class */ @@ -537,7 +658,7 @@ static void gjstest_test_func_gjs_context_eval_exit_code_omitted_throw() { GjsAutoUnref context = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*wrong code*"); @@ -549,13 +670,11 @@ g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); g_test_assert_expected_messages(); - - g_clear_error(&error); } static void gjstest_test_func_gjs_context_eval_exit_code_omitted_no_throw() { GjsAutoUnref context = gjs_context_new(); - GError* error = nullptr; + GjsAutoError error; const char good_js[] = "let num = 77;"; @@ -564,8 +683,6 @@ g_assert_true(ok); g_assert_no_error(error); - - g_clear_error(&error); } static void gjstest_test_func_gjs_jsapi_util_string_js_string_utf8( @@ -822,7 +939,7 @@ gjs_profiler_start(profiler); for (size_t ix = 0; ix < 100; ix++) { - GError *error = nullptr; + GjsAutoError error; int estatus; #define TESTJS "[1,5,7,1,2,3,67,8].sort()" @@ -1016,8 +1133,7 @@ int status; const char* argv[1] = {"test"}; - bool ok = - gjs_context_define_string_array(gjs, "ARGV", 1, argv, error.out()); + bool ok = gjs_context_define_string_array(gjs, "ARGV", 1, argv, &error); g_assert_no_error(error); g_assert_true(ok); @@ -1025,7 +1141,7 @@ ok = gjs_context_eval(gjs, R"js( imports.system.exit(ARGV[0] === "test" ? 0 : 1) )js", - -1, "
", &status, error.out()); + -1, "
", &status, &error); g_assert_cmpint(status, ==, 0); g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); @@ -1091,6 +1207,14 @@ g_test_add_func( "/gjs/context/register-module/eval-module-file", gjstest_test_func_gjs_context_register_module_eval_module_file); + g_test_add("/gjs/context/register-module/eval-jsapi", GjsUnitTestFixture, + nullptr, gjs_unit_test_fixture_setup, + gjstest_test_func_gjs_context_register_module_eval_jsapi, + gjs_unit_test_fixture_teardown); + g_test_add("/gjs/context/register-module/eval-jsapi-relative", + GjsUnitTestFixture, nullptr, gjs_unit_test_fixture_setup, + gjstest_test_func_gjs_context_register_module_eval_jsapi_rel, + gjs_unit_test_fixture_teardown); g_test_add_func("/gjs/context/register-module/non-existent", gjstest_test_func_gjs_context_register_module_non_existent); g_test_add_func("/gjs/context/eval-module/unregistered", @@ -1127,6 +1251,12 @@ g_test_add_func( "/gjs/context/eval-module/exit-code-omitted-no-throw", gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw); + g_test_add("/gjs/context/eval-module/jsapi-throw", GjsUnitTestFixture, + nullptr, gjs_unit_test_fixture_setup, + gjstest_test_func_gjs_context_module_eval_jsapi_throws, + gjs_unit_test_fixture_teardown); + g_test_add_func("/gjs/context/run-in-realm", + gjstest_test_func_gjs_context_run_in_realm); #define ADD_JSAPI_UTIL_TEST(path, func) \ g_test_add("/gjs/jsapi/util/" path, GjsUnitTestFixture, NULL, \ diff -Nru gjs-1.76.2/test/gjs-test-toggle-queue.cpp gjs-1.78.0/test/gjs-test-toggle-queue.cpp --- gjs-1.76.2/test/gjs-test-toggle-queue.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-toggle-queue.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -82,7 +82,7 @@ const char* gi_initializer = "imports.gi;"; g_assert_true(gjs_context_eval(fx->gjs_context, gi_initializer, -1, - "", &code, error.out())); + "", &code, &error)); g_assert_no_error(error); } @@ -411,7 +411,7 @@ GjsAutoError error; reffed = instance->ptr(); - gjs_test_tools_ref_other_thread(reffed, error.out()); + gjs_test_tools_ref_other_thread(reffed, &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -436,7 +436,7 @@ GjsAutoError error; reffed = instance->ptr(); - gjs_test_tools_ref_other_thread(reffed, error.out()); + gjs_test_tools_ref_other_thread(reffed, &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, @@ -460,13 +460,13 @@ auto* instance = new_test_gobject(fx); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, ::ToggleQueue::Direction::UP); - gjs_test_tools_unref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_unref_other_thread(instance->ptr(), &error); g_assert_no_error(error); g_assert_true(ToggleQueue::queue().empty()); @@ -486,7 +486,7 @@ auto* instance_test = reinterpret_cast(instance); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); GjsAutoUnref reffed(instance->ptr()); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -505,13 +505,13 @@ auto* instance_test = reinterpret_cast(instance); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, ::ToggleQueue::Direction::UP); - gjs_test_tools_unref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_unref_other_thread(instance->ptr(), &error); g_assert_no_error(error); g_assert_true(ToggleQueue::queue().empty()); @@ -527,7 +527,7 @@ auto* instance_test = reinterpret_cast(instance); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, @@ -538,7 +538,7 @@ ToggleQueue::get_default()->handle_all_toggles(toggles_handler); g_assert_true(s_toggle_history.empty()); - gjs_test_tools_unref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_unref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, @@ -555,13 +555,13 @@ auto* instance = new_test_gobject(fx); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, ::ToggleQueue::Direction::UP); - gjs_test_tools_unref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_unref_other_thread(instance->ptr(), &error); g_assert_no_error(error); g_assert_true(ToggleQueue::queue().empty()); @@ -581,7 +581,7 @@ auto* instance_test = reinterpret_cast(instance); GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); GjsAutoUnref reffed(instance->ptr()); // Simulating the case where late threads are causing this... @@ -607,11 +607,11 @@ // This is something similar to what is happening on #297 GjsAutoError error; - gjs_test_tools_ref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); ToggleQueue::get_default()->enqueue(instance, ::ToggleQueue::Direction::UP, ToggleQueue().handler()); - gjs_test_tools_unref_other_thread(instance->ptr(), error.out()); + gjs_test_tools_unref_other_thread(instance->ptr(), &error); g_assert_no_error(error); ToggleQueue::get_default()->enqueue( instance, ::ToggleQueue::Direction::DOWN, ToggleQueue().handler()); diff -Nru gjs-1.76.2/test/gjs-test-utils.cpp gjs-1.78.0/test/gjs-test-utils.cpp --- gjs-1.76.2/test/gjs-test-utils.cpp 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-utils.cpp 2023-09-17 02:27:20.000000000 +0000 @@ -8,10 +8,11 @@ #include #include +#include // for JS_GetContextPrivate #include -#include #include +#include "gjs/context-private.h" #include "gjs/context.h" #include "gjs/jsapi-util.h" #include "test/gjs-test-common.h" @@ -21,8 +22,8 @@ fx->gjs_context = gjs_context_new(); fx->cx = (JSContext *) gjs_context_get_native_context(fx->gjs_context); - JS::RootedObject global(fx->cx, gjs_get_import_global(fx->cx)); - fx->realm = JS::EnterRealm(fx->cx, global); + auto* gjs = static_cast(JS_GetContextPrivate(fx->cx)); + fx->realm = JS::EnterRealm(fx->cx, gjs->global()); } void diff -Nru gjs-1.76.2/test/gjs-test-utils.h gjs-1.78.0/test/gjs-test-utils.h --- gjs-1.76.2/test/gjs-test-utils.h 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/gjs-test-utils.h 2023-09-17 02:27:20.000000000 +0000 @@ -8,12 +8,15 @@ #include -#include // for g_assert_... #include // for uintptr_t + +#include // for pair #include // for numeric_limits #include #include // for is_same -#include +#include // IWYU pragma: keep + +#include // for g_assert_... #include "gjs/context.h" diff -Nru gjs-1.76.2/test/test-ci.sh gjs-1.78.0/test/test-ci.sh --- gjs-1.76.2/test/test-ci.sh 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/test/test-ci.sh 2023-09-17 02:27:20.000000000 +0000 @@ -10,7 +10,8 @@ #SpiderMonkey and libgjs export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib64:/usr/local/lib + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib64:/usr/local/lib: + export GI_TYPELIB_PATH=$GI_TYPELIB_PATH:/usr/lib64/girepository-1.0 #Macros export ACLOCAL_PATH=$ACLOCAL_PATH:/usr/local/share/aclocal @@ -18,6 +19,17 @@ export SHELL=/bin/bash PATH=$PATH:~/.local/bin + if [ "$USE_UNSTABLE_GNOME_PREFIX" = "true" ]; then + prefix=/opt/GNOME + libdir=$prefix/lib64 + export PATH=$prefix/bin:$PATH + export LD_LIBRARY_PATH=$libdir:$LD_LIBRARY_PATH + export PKG_CONFIG_PATH=$libdir/pkgconfig:$PKG_CONFIG_PATH + export GI_TYPELIB_PATH=$libdir/girepository-1.0:$GI_TYPELIB_PATH + export XDG_DATA_DIRS=$prefix/share:$XDG_DATA_DIRS + export ACLOCAL_PATH=$prefix/share/aclocal:$ACLOCAL_PATH + fi + export DISPLAY="${DISPLAY:-:0}" } @@ -139,7 +151,7 @@ DEFAULT_CONFIG_OPTS="-Dcairo=enabled -Dreadline=enabled -Dprofiler=enabled \ -Ddtrace=false -Dsystemtap=false -Dverbose_logs=false --werror" - meson _build $DEFAULT_CONFIG_OPTS $CONFIG_OPTS + meson setup _build $DEFAULT_CONFIG_OPTS $CONFIG_OPTS ninja -C _build if test "$TEST" != "skip"; then diff -Nru gjs-1.76.2/tools/heapgraph.py gjs-1.78.0/tools/heapgraph.py --- gjs-1.76.2/tools/heapgraph.py 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/tools/heapgraph.py 2023-09-17 02:27:20.000000000 +0000 @@ -99,6 +99,7 @@ 'GIRepositoryNamespace', 'GjsFileImporter', 'GjsGlobal', + 'GjsInternalGlobal', 'GjsModule'], help='Don\'t show nodes with labels containing LABEL') @@ -116,12 +117,12 @@ WeakMapEntry = namedtuple('WeakMapEntry', 'weakMap key keyDelegate value') -addr_regex = re.compile('[A-F0-9]+$|0x[a-f0-9]+$') -node_regex = re.compile ('((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') -edge_regex = re.compile ('> ((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') +addr_regex = re.compile(r'[A-F0-9]+$|0x[a-f0-9]+$') +node_regex = re.compile(r'((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') +edge_regex = re.compile(r'> ((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') wme_regex = re.compile(r'WeakMapEntry map=((?:0x)?[a-zA-Z0-9]+|\(nil\)) key=((?:0x)?[a-zA-Z0-9]+|\(nil\)) keyDelegate=((?:0x)?[a-zA-Z0-9]+|\(nil\)) value=((?:0x)?[a-zA-Z0-9]+)') -func_regex = re.compile('Function(?: ([^/]+)(?:/([<|\w]+))?)?') +func_regex = re.compile(r'Function(?: ([^/]+)(?:/([<|\w]+))?)?') priv_regex = re.compile(r'([^ ]+) (0x[a-fA-F0-9]+$)') atom_regex = re.compile(r'^string (.*)\r?$') @@ -215,6 +216,11 @@ node_color = node.group(2) node_label = node.group(3) + # Don't hide atoms matching hide_nodes, as they may be labels + if atom_regex.match(node_label) is not None: + addNode(node_addr, node_label) + continue + # Use this opportunity to map hide_nodes to addresses for hide_node in args.hide_nodes: if hide_node in node_label: @@ -667,6 +673,7 @@ # Node and Root Filtering if args.show_global: args.hide_nodes.remove('GjsGlobal') + args.hide_nodes.remove('GjsInternalGlobal') if args.show_imports: args.hide_nodes.remove('GjsFileImporter') args.hide_nodes.remove('GjsModule') diff -Nru gjs-1.76.2/tools/package-lock.json gjs-1.78.0/tools/package-lock.json --- gjs-1.76.2/tools/package-lock.json 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/tools/package-lock.json 2023-09-17 02:27:20.000000000 +0000 @@ -1045,9 +1045,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -1193,9 +1193,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1974,9 +1974,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -2083,9 +2083,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrappy": { diff -Nru gjs-1.76.2/tools/process_iwyu.py gjs-1.78.0/tools/process_iwyu.py --- gjs-1.76.2/tools/process_iwyu.py 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/tools/process_iwyu.py 2023-09-17 02:27:20.000000000 +0000 @@ -75,13 +75,8 @@ ('gi/private.cpp', '#include ', 'for max'), ('gjs/importer.cpp', '#include ', 'for max'), ('gjs/importer.cpp', '#include ', 'for max, copy'), # also! - ('modules/cairo-context.cpp', '#include ', 'for max'), - - # False positive when using EnumType operators - # https://github.com/include-what-you-use/include-what-you-use/issues/927 - ('modules/cairo-context.cpp', '#include ', 'for enable_if_t'), - ('modules/cairo-region.cpp', '#include ', 'for enable_if_t'), - ('modules/cairo-surface.cpp', '#include ', 'for enable_if_t'), + ('gjs/module.cpp', '#include ', 'for copy'), + ('util/log.cpp', '#include ', 'for fill_n'), # False positive when constructing JS::GCHashMap ('gi/boxed.h', '#include ', 'for move'), diff -Nru gjs-1.76.2/tools/run_coverage.sh gjs-1.78.0/tools/run_coverage.sh --- gjs-1.76.2/tools/run_coverage.sh 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/tools/run_coverage.sh 2023-09-17 02:27:20.000000000 +0000 @@ -11,7 +11,7 @@ IGNORE="*/gjs/test/* *-resources.c *minijasmine.cpp */gjs/subprojects/*" rm -rf "$BUILDDIR" -meson "$BUILDDIR" -Db_coverage=true +meson setup "$BUILDDIR" -Db_coverage=true VERSION=$(meson introspect "$BUILDDIR" --projectinfo | python -c 'import json, sys; print(json.load(sys.stdin)["version"])') diff -Nru gjs-1.76.2/tools/run_iwyu.sh gjs-1.78.0/tools/run_iwyu.sh --- gjs-1.76.2/tools/run_iwyu.sh 2023-06-14 22:04:12.000000000 +0000 +++ gjs-1.78.0/tools/run_iwyu.sh 2023-09-17 02:27:20.000000000 +0000 @@ -47,7 +47,7 @@ IWYU_TOOL_ARGS="-I../gjs" IWYU_ARGS="-Wno-pragma-once-outside-header" IWYU_RAW="include-what-you-use -xc++ -std=c++17 -Xiwyu --keep=config.h $IWYU_ARGS" -IWYU_RAW_INC="-I. -I.. $(pkg-config --cflags gobject-introspection-1.0 mozjs-102)" +IWYU_RAW_INC="-I. -I.. $(pkg-config --cflags gobject-introspection-1.0 mozjs-115)" PRIVATE_MAPPING="-Xiwyu --mapping_file=$SRCDIR/tools/gjs-private-iwyu.imp -Xiwyu --keep=config.h" PUBLIC_MAPPING="-Xiwyu --mapping_file=$SRCDIR/tools/gjs-public-iwyu.imp" POSTPROCESS="python3 $SRCDIR/tools/process_iwyu.py"