diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8869ed6b..bf2ecdea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 @@ -14,7 +14,7 @@ stages: .gjs-alpine: variables: - FDO_DISTRIBUTION_TAG: '2022-07-23.0' + FDO_DISTRIBUTION_TAG: '2022-11-02.0' FDO_UPSTREAM_REPO: GNOME/gjs build-alpine-image: @@ -23,7 +23,7 @@ build-alpine-image: - .gjs-alpine stage: prepare variables: - FDO_DISTRIBUTION_PACKAGES: cppcheck git python3 yarn bash grep + FDO_DISTRIBUTION_PACKAGES: cppcheck git python3 npm bash grep FDO_DISTRIBUTION_EXEC: | python3 -m ensurepip && rm -r /usr/lib/python*/ensurepip && @@ -31,7 +31,7 @@ build-alpine-image: 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 @@ build-alpine-image: 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-alpine-image: build_recommended: <<: *build stage: source_check - image: registry.gitlab.gnome.org/gnome/gjs:job-2190518_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 @@ sanitizer_gcc: 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 @@ sanitizer_thread_gcc: 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,13 +114,15 @@ sanitizer_thread_gcc: # 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++ - CONFIG_OPTS: -Ddtrace=true -Dsystemtap=true -Dverbose_logs=true + USE_UNSTABLE_GNOME_PREFIX: 'true' + CONFIG_OPTS: >- + -Ddtrace=true -Dsystemtap=true -Dverbose_logs=true -Db_pch=false ENABLE_GTK: "yes" except: - schedules @@ -125,11 +130,20 @@ build_maximal: - 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 @@ -141,7 +155,7 @@ build_minimal: 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 @@ -201,6 +215,12 @@ cppcheck: - tags variables: - $CI_COMMIT_MESSAGE =~ /\[skip cppcheck\]/ + only: + changes: + - "**/*.c" + - "**/*.cpp" + - "**/*.h" + - "**/*.hh" cpplint: when: on_success @@ -217,6 +237,11 @@ cpplint: - tags variables: - $CI_COMMIT_MESSAGE =~ /\[skip cpplint\]/ + only: + changes: + - "**/*.cpp" + - "**/*.h" + - "**/*.hh" eslint: when: on_success @@ -233,6 +258,12 @@ eslint: - tags variables: - $CI_COMMIT_MESSAGE =~ /\[skip eslint\]/ + only: + changes: + - "**/*.js" + - .eslintignore + - .eslintrc.yml + - '**/.eslintrc.yml' pch_check: when: on_success @@ -249,11 +280,18 @@ pch_check: - tags variables: - $CI_COMMIT_MESSAGE =~ /\[skip pch_check\]/ + only: + changes: + - "**/*.c" + - "**/*.cpp" + - "**/*.h" + - "**/*.hh" + - test/check-pch.sh 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 @@ -274,7 +312,11 @@ codespell: stage: source_check script: - codespell --version - - codespell -S "*.png,*.po,*.jpg,*.wrap,.git,LICENSES" -f --builtin "code,usage,clear" --skip="./installed-tests/js/jasmine.js,./README.md,./build/flatpak/*.json" --ignore-words-list="afterall,befores,files',filetest,gir,inout,stdio,uint,upto,xdescribe" + - | + codespell -S "*.png,*.po,*.jpg,*.wrap,.git,LICENSES" -f \ + --builtin "code,usage,clear" \ + --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 @@ -304,7 +346,7 @@ coverage: 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 @@ -316,10 +358,13 @@ sanitizer_clang: 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 @@ -329,7 +374,7 @@ sanitizer_clang: 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 @@ -345,8 +390,9 @@ installed_tests: 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 @@ -357,7 +403,7 @@ valgrind: 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 @@ -367,7 +413,7 @@ zeal_2: 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 @@ -377,7 +423,7 @@ zeal_4: 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 @@ -388,7 +434,7 @@ zeal_11: # 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: @@ -419,28 +465,28 @@ zeal_11: BUILDAH_FORMAT: docker BUILDAH_ISOLATION: chroot -fedora.mozjs91: +fedora.mozjs102: <<: *create_docker_image variables: <<: *docker_variables DOCKERFILE: test/extra/Dockerfile -fedora.mozjs91-debug: +fedora.mozjs102-debug: <<: *create_docker_image variables: <<: *docker_variables DOCKERFILE: test/extra/Dockerfile.debug -fedora.mozjs102: +fedora.mozjs115: <<: *create_docker_image variables: <<: *docker_variables DOCKERFILE: test/extra/Dockerfile - ARGS: --build-arg MOZJS_BRANCH=mozjs102 --build-arg MOZJS_BUILDDEPS=mozjs91 + ARGS: --build-arg MOZJS_BRANCH=mozjs115 --build-arg MOZJS_BUILDDEPS=mozjs102 -fedora.mozjs102-debug: +fedora.mozjs115-debug: <<: *create_docker_image variables: <<: *docker_variables DOCKERFILE: test/extra/Dockerfile.debug - ARGS: --build-arg MOZJS_BRANCH=mozjs102 --build-arg MOZJS_BUILDDEPS=mozjs91 + ARGS: --build-arg MOZJS_BRANCH=mozjs115 --build-arg MOZJS_BUILDDEPS=mozjs102 diff --git a/NEWS b/NEWS index 1e168c72..2a7485c0 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,280 @@ +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 CjsPrivate + [!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 +-------------- + +- Various fixes ported from the development branch. + +- Closed bugs and merge requests: + * GJS freezes, program stops responding, error states Gtk4 EventController + GestureClick returns incorrect state- Gdk.ModifierType on mouse button press + in X11 [#507, !829, !850, Sundeep Mediratta] + * Caller allocated boxed types or structs are not fully released [#543, !837, + !849, Marco Trevisan] + * Gjs console leaks invalid option errors [#544, !837, !849, Marco Trevisan] + +Version 1.76.1 +-------------- + +- Various fixes ported from the development branch. + +- Closed bugs and merge requests: + * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, Daniel + van Vugt] + * Memory leak with GError [#36, !837, Marco Trevisan] + * GVariant return values leaked [#499, !837, Marco Trevisan] + * GBytes's are leaked when passed as-is to a function [#539, !837, Marco + Trevisan] + * Transformed GValues are leaking temporary instances [#540, !837, Marco + Trevisan] + * GHash value infos are leaked [#541, !837, Marco Trevisan] + * "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 +-------------- + +- No changes from release candidate 1.75.90. + +Version 1.75.90 +--------------- + +- Closed bugs and merge requests: + * NEWS: fix a typo causing codespell to fail [!824, Marco Trevisan] + * doc: Add more apps written in GJS [!822, Sonny Piers] + * Gio: Use proper default priority on async generators [!827, Marco Trevisan] + * gjs 1.75.2 GObjectValue build test failing on ARM [#529, !825, Marco + Trevisan] + * testGObjectValue: Enable creating object with a string property [!826, Marco + Trevisan] + * Handle transfer-none string return value from vfunc implemented in JS [#519, + 823, Marco Trevisan, Daniel van Vugt] + * Various maintenance, performance improvements [!828, Philip Chimento] + +Version 1.75.2 +-------------- + +- There are new `Gio.Application.prototype.runAsync()` and + `GLib.MainLoop.prototype.runAsync()` methods which do the same thing as + `run()` but return a Promise which resolves when the main loop ends, instead + of blocking while the main loop runs. Use one of these methods (by awaiting + it) if you use async operations with Promises in your application. Previously, + it was easy to get into a state where Promises never resolved if you didn't + run the main loop inside a callback. [Evan Welsh] + +- There are new `Gio.InputStream.prototype.createSyncIterator()` and + `Gio.InputStream.prototype.createAsyncIterator()` methods which allow easy + iteration of input streams in consecutive chunks of bytes, either with a + for-of loop or a for-await-of loop. [Sonny Piers] + +- DBus proxy wrapper classes now have a static `newAsync()` method, which + returns a Promise that resolves to an instance of the proxy wrapper class on + which `initAsync()` has completed. [Marco Trevisan] + +- DBus property getters can now return GLib.Variant instances directly, if they + have the correct type, instead of returning JS values and having them be + packed into GLib.Variants. [Andy Holmes] + +- Dramatic performance improvements in the legacy `imports.signals` module, + which has also gained a `connectAfter()` method that works like the same-named + method in GObject signals. (However, the signals module remains legacy, and is + mostly there for historical reasons with GNOME Shell. Don't use it in new + code.) [Marco Trevisan] + +- For years we have had a typo in `Cairo.LineCap.SQUARE`, incorrectly naming it + `SQUASH`. This is fixed and the typoed name is retained as an alias. [Vítor + Vasconcellos] + +- Also in Cairo, the value of `Cairo.Format.RGB16_565` was wrong. This was fixed + with a breaking change, because anyone using it was probably already not + getting the results they expected. [Vítor Vasconcellos] + +- Continuing the Cairo improvements, SVG surfaces have gained + `Cairo.SVGSurface.prototype.finish()` and `Cairo.SVGSurface.prototype.flush()` + because previously SVG surfaces were only written to disk when the SVGSurface + object was garbage collected, making it uncertain to rely on them. [tuberry] + +- The debugger now handles Symbol values and Symbol property keys of objects. + Previously, these were not displayed correctly. [Philip Chimento] + +- Various type-safety refactors [Marco Trevisan] + +- Many bug fixes and performance improvements. + +- Closed bugs and merge requests: + * Promises in application.run do not fulfill until loop exit [#468, !732, Evan + Welsh] + * console: Various cleanups to tracing functions and increase structured + logging metadata [!756, Marco Trevisan] + * Legacy signals code optimizations [!757, Marco Trevisan] + * meson: Depend on g-i 1.71 and enable newly supported tests [!761, Marco + Trevisan] + * Gio: Add support for initializing a DBus Proxy via a promise [#494, !794, + Marco Trevisan, Philip Chimento] + * Make GInputStream iterable and async iterable [!573, !797, Sonny Piers] + * Gio: allow D-Bus implementations to return pre-packed variants [!796, Andy + Holmes] + * Update ESLint tooling [!798, Sonny Piers] + * Various maintenance [!804, !814, !820, Philip Chimento] + * Add legacy signals connectAfter method [!805, Marco Trevisan] + * arg-cache: Add support passing caller-allocated C-arrays [!806, Marco + Trevisan] + * Crash when passing an introspected function as a callback argument [#518, + !809, Philip Chimento] + * CI: Upgrade CI images to F37 [!810, Philip Chimento] + * Possible errors in cairo enums [#516, !811, Vítor Vasconcellos] + * ci: Only run source check jobs if relevant files have been changed [!812, + Marco Trevisan] + * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, + tuberry] + * signals: Fix bugs when multiple handlers are connected and disconnect is + called [!818, Evan Welsh] + * Handle Symbol values in pretty-printer and debugger [!819, Philip Chimento] + +Version 1.74.2 +-------------- + +- Various fixes ported from the development branch. + +- Closed bugs and merge requests: + * build error with clang [#514, !807, Philip Chimento] + * can't compile current version with mozjs 102 [#503, !808, Philip Chimento] + * Enabling window-list extension causes gnome-shell to crash when running + "dconf update" as root [#510, !813, Philip Chimento] + * log: Fix an off-by-one buffer overflow [!817, Valentin David] + +Version 1.75.1 +-------------- + +- Static methods on classes from GObject introspection are now present on JS + classes that inherit from those classes. [Marco Trevisan] + Version 1.74.1 -------------- @@ -563,7 +840,7 @@ Version 1.68.1 * Accessing GLib.ByteArray throws [#386, !590, Philip Chimento] * Missing hyphen and camelCase getters for CONSTRUCT_ONLY GObject properties defined in JavaScript [#391, !591, Philip Chimento] - * gnome-shell crashes on deferencing a destroyed wrapper object [#395, !593, + * gnome-shell crashes on dereferencing a destroyed wrapper object [#395, !593, !617, Marco Trevisan] * GNOME crash "JS object wrapper for GObject 0x563bf88f5f50 (GSettings) is being released..." [#294, !593, !617, Marco Trevisan] @@ -978,7 +1255,7 @@ Version 1.65.3 * CI fixes for new test images [!433, Philip Chimento] * Various maintenance [!428, Philip Chimento] * Fix dead link [!436, prnsml] - * overrides/Gtk: Make GTK4 widgets iteratable [!437, Florian Müllner] + * overrides/Gtk: Make GTK4 widgets iterable [!437, Florian Müllner] * arg.cpp: Fix building on Visual Studio [!439, Chun-wei Fan] * Separate closures and vfuncs [!438, Philip Chimento] * Improvements to IWYU script [!435, Philip Chimento] @@ -2048,7 +2325,7 @@ Version 1.53.2 Philip Chimento] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] - * gjs: JS_GetContextPrivate(): gjs-console killed by SIGSEGV [#148, !121, + * gjs: JS_GetContextPrivate(): cjs-console killed by SIGSEGV [#148, !121, Philip Chimento] * Use compacting GC on RSS size growth [#151, !133, Carlos Garnacho] * Segfault on enumeration of GjSFileImporter properties when a searchpath @@ -2641,7 +2918,7 @@ Version 1.49.2 - Closed bugs: * Make gjs build on Windows/Visual Studio [#775868, Chun-wei Fan] - * Bring back fancy error reporter in gjs-console [#781882, Philip Chimento] + * Bring back fancy error reporter in cjs-console [#781882, Philip Chimento] * Add Meson running from source support to package.js [#781882, Patrick Griffis] * package: Fix initSubmodule() when running from source in Meson [#782065, @@ -2998,7 +3275,7 @@ Version 1.47.0 'A_FIELD': 'A value', }); -- Backwards-incompatible change: we have changed the way gjs-console interprets +- Backwards-incompatible change: we have changed the way cjs-console interprets command-line arguments. Previously there was a heuristic to try to decide whether "--help" given on the command line was meant for GJS itself or for a script being launched. This did not work in some cases, for example: @@ -3180,7 +3457,7 @@ Changes: - Support for non-default constructor (i.e. constructors like GdkPixbuf.Pixbuf.new_from_file(file)) - Add a Lang.bind function which binds the meaning of 'this' - - Add an interactive console gjs-console + - Add an interactive console cjs-console - Allow code in directory modules (i.e. the code should reside in __init__.js files) - Fix handling of enum/flags return values @@ -3193,7 +3470,7 @@ Changes: - GHashTable return values/out parameters - Support GHashTable 'in' parameters - Convert JSON-style object to a GHashTable - - Add support for UNIX shebang (i.e. #!/usr/bin/gjs-console) + - Add support for UNIX shebang (i.e. #!/usr/bin/cjs-console) - Support new introspection short/ushort type tags - Support GI_TYPE_TAG_FILENAME - Improve support for machine-dependent integer types and arrays of diff --git a/build/maintainer-upload-release.sh b/build/maintainer-upload-release.sh new file mode 100755 index 00000000..60a61094 --- /dev/null +++ b/build/maintainer-upload-release.sh @@ -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 --git a/build/symlink-gjs.py b/build/symlink-gjs.py index 24b92bf7..af54428e 100644 --- a/build/symlink-gjs.py +++ b/build/symlink-gjs.py @@ -23,8 +23,8 @@ if os.name == 'nt': # Using symlinks on Windows often require administrative privileges, - # which is not what we want. Instead, copy gjs-console.exe. - shutil.copyfile('gjs-console.exe', os.path.join(installed_bin_dir, 'gjs.exe')) + # which is not what we want. Instead, copy cjs-console.exe. + shutil.copyfile('cjs-console.exe', os.path.join(installed_bin_dir, 'cjs.exe')) else: try: temp_link = tempfile.mktemp(dir=installed_bin_dir) diff --git a/cjs/console.cpp b/cjs/console.cpp index 3f262d27..c5064657 100644 --- a/cjs/console.cpp +++ b/cjs/console.cpp @@ -127,7 +127,7 @@ static void check_script_args_for_stray_gjs_args(int argc, char * const *argv) { - GError *error = NULL; + GjsAutoError error; GjsAutoStrv new_coverage_prefixes; GjsAutoChar new_coverage_output_path; GjsAutoStrv new_include_paths; @@ -157,7 +157,6 @@ check_script_args_for_stray_gjs_args(int argc, g_option_context_add_main_entries(script_options, script_check_entries, NULL); if (!g_option_context_parse_strv(script_options, argv_copy.out(), &error)) { g_warning("Scanning script arguments failed: %s", error->message); - g_error_free(error); return; } @@ -190,7 +189,7 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc, 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 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc, 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,19 +207,17 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc, 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; } int main(int argc, char** argv) { - GError* error = NULL; + GjsAutoError error; const char *filename; const char *program_name; gsize len; @@ -316,7 +312,6 @@ int main(int argc, char** argv) { } 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)) { diff --git a/cjs/context-private.h b/cjs/context-private.h index 3b6886f3..7e31d0f6 100644 --- a/cjs/context-private.h +++ b/cjs/context-private.h @@ -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 "cjs/context.h" @@ -82,6 +83,7 @@ class GjsContextPrivate : public JS::JobQueue { 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 @@ class GjsContextPrivate : public JS::JobQueue { 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 @@ class GjsContextPrivate : public JS::JobQueue { [[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 @@ class GjsContextPrivate : public JS::JobQueue { 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 @@ class GjsContextPrivate : public JS::JobQueue { 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 @@ class GjsContextPrivate : public JS::JobQueue { 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 --git a/cjs/context.cpp b/cjs/context.cpp index 375e995c..79e2c922 100644 --- a/cjs/context.cpp +++ b/cjs/context.cpp @@ -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 @@ -123,10 +125,6 @@ struct _GjsContext { GObject parent; }; -struct _GjsContextClass { - GObjectClass parent; -}; - G_DEFINE_TYPE_WITH_PRIVATE(GjsContext, gjs_context, G_TYPE_OBJECT); Gjs::NativeModuleRegistry& registry = Gjs::NativeModuleRegistry::get(); @@ -471,6 +469,9 @@ void GjsContextPrivate::dispose(void) { delete m_gtype_table; delete m_atoms; + m_job_queue.clear(); + m_object_init_list.clear(); + /* Tear down JS */ JS_DestroyContext(m_cx); m_cx = nullptr; @@ -601,7 +602,7 @@ static void load_context_module(JSContext* cx, const char* uri, 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); } @@ -644,6 +645,7 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) 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, @@ -685,9 +687,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) 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)) { @@ -714,7 +715,7 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) m_global = global; { - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{this}; std::vector paths; if (m_search_path) @@ -756,7 +757,7 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) } { - JSAutoRealm ar(cx, global); + Gjs::AutoMainRealm ar{this}; load_context_module( cx, "resource:///org/gnome/gjs/modules/esm/_bootstrap/default.js", "ESM bootstrap"); @@ -765,6 +766,19 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) [[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(); } @@ -1006,6 +1020,12 @@ bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const { 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) { @@ -1311,7 +1331,7 @@ bool GjsContextPrivate::auto_profile_enter() { (_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); @@ -1408,7 +1428,7 @@ bool GjsContextPrivate::eval(const char* script, size_t script_len, 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); @@ -1474,7 +1494,7 @@ bool GjsContextPrivate::eval_module(const char* identifier, 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)); @@ -1488,7 +1508,7 @@ bool GjsContextPrivate::eval_module(const char* identifier, 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); @@ -1551,7 +1571,7 @@ bool GjsContextPrivate::eval_module(const char* identifier, 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; @@ -1704,7 +1724,7 @@ gjs_context_define_string_array(GjsContext *js_context, 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) { @@ -1758,6 +1778,44 @@ gjs_context_make_current (GjsContext *context) 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 @@ -1785,3 +1843,25 @@ gjs_get_js_version(void) { 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 --git a/cjs/context.h b/cjs/context.h index 5d0bd246..bbb97abf 100644 --- a/cjs/context.h +++ b/cjs/context.h @@ -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 @@ void gjs_context_make_current (GjsContext *js_context); 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 --git a/cjs/coverage.cpp b/cjs/coverage.cpp index 57b34fd3..c5201018 100644 --- a/cjs/coverage.cpp +++ b/cjs/coverage.cpp @@ -273,15 +273,14 @@ void 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 @@ bootstrap_coverage(GjsCoverage *coverage) { 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 @@ gjs_coverage_constructed(GObject *object) 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 --git a/cjs/debugger.cpp b/cjs/debugger.cpp index b4383739..75aff4fd 100644 --- a/cjs/debugger.cpp +++ b/cjs/debugger.cpp @@ -107,24 +107,24 @@ static JSFunctionSpec debugger_funcs[] = { }; // 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 --git a/cjs/deprecation.cpp b/cjs/deprecation.cpp index 3c8c550a..283c80e2 100644 --- a/cjs/deprecation.cpp +++ b/cjs/deprecation.cpp @@ -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 @@ const char* messages[] = { "(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 @@ static JS::UniqueChars get_callsite(JSContext* cx) { 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 --git a/cjs/deprecation.h b/cjs/deprecation.h index a5d8b344..1f68b95f 100644 --- a/cjs/deprecation.h +++ b/cjs/deprecation.h @@ -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 --git a/cjs/engine.cpp b/cjs/engine.cpp index 0b9bb646..9991b545 100644 --- a/cjs/engine.cpp +++ b/cjs/engine.cpp @@ -58,7 +58,7 @@ static void on_promise_unhandled_rejection( 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 --git a/cjs/gjs_pch.hh b/cjs/gjs_pch.hh index 368b2334..f74e6591 100644 --- a/cjs/gjs_pch.hh +++ b/cjs/gjs_pch.hh @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -117,11 +118,12 @@ #include #include #include +#include +#include +#include #include -#include #include #include -#include #ifdef HAVE_READLINE_READLINE_H #include #include diff --git a/cjs/global.cpp b/cjs/global.cpp index 19526b74..877108e2 100644 --- a/cjs/global.cpp +++ b/cjs/global.cpp @@ -450,10 +450,10 @@ bool gjs_global_registry_set(JSContext* cx, JS::HandleObject registry, /** * 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 --git a/cjs/importer.cpp b/cjs/importer.cpp index 91594fae..d186d877 100644 --- a/cjs/importer.cpp +++ b/cjs/importer.cpp @@ -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 @@ -283,7 +282,7 @@ gjs_import_native_module(JSContext *cx, gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", parse_name); 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)); if (id.isVoid()) @@ -310,7 +309,7 @@ import_module_init(JSContext *context, 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 +324,6 @@ import_module_init(JSContext *context, return false; } - g_error_free(error); return true; } g_assert(script); @@ -607,7 +605,7 @@ static bool do_import(JSContext* context, JS::HandleObject obj, /* 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 +699,7 @@ static bool importer_new_enumerate(JSContext* context, JS::HandleObject object, 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 --git a/cjs/internal.cpp b/cjs/internal.cpp index 649860f2..f529fbd2 100644 --- a/cjs/internal.cpp +++ b/cjs/internal.cpp @@ -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 "cjs/context-private.h" #include "cjs/engine.h" @@ -89,8 +89,7 @@ bool gjs_load_internal_module(JSContext* cx, const char* identifier) { 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 @@ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, 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 @@ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, /** * 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_internal_module(JSContext* cx, unsigned argc, 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 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { 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 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { 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 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { 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 @@ bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc, 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 @@ static void load_async_callback(GObject* file, GAsyncResult* res, void* data) { 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 --git a/cjs/jsapi-dynamic-class.cpp b/cjs/jsapi-dynamic-class.cpp index 61e64b3c..fa51f35b 100644 --- a/cjs/jsapi-dynamic-class.cpp +++ b/cjs/jsapi-dynamic-class.cpp @@ -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 "cjs/atoms.h" #include "cjs/context-private.h" @@ -121,7 +121,7 @@ gjs_typecheck_instance(JSContext *context, 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 --git a/cjs/jsapi-util-args.h b/cjs/jsapi-util-args.h index efe4f645..723b5d0a 100644 --- a/cjs/jsapi-util-args.h +++ b/cjs/jsapi-util-args.h @@ -18,13 +18,18 @@ #include #include #include +#include #include #include #include // for UniqueChars +#include // for GenericErrorResult +#include // IWYU pragma: keep #include "cjs/jsapi-util.h" #include "cjs/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 @@ GJS_ALWAYS_INLINE static inline void assign(JSContext* cx, char c, * 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 @@ GJS_JSAPI_RETURN_CONVENTION static bool parse_call_args_helper( 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 @@ GJS_JSAPI_RETURN_CONVENTION static bool parse_call_args_helper( 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 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_parse_call_args( 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 --git a/cjs/jsapi-util-error.cpp b/cjs/jsapi-util-error.cpp index 255fe238..a55e0739 100644 --- a/cjs/jsapi-util-error.cpp +++ b/cjs/jsapi-util-error.cpp @@ -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 "cjs/atoms.h" #include "cjs/context-private.h" @@ -36,9 +37,9 @@ using CauseSet = JS::GCHashSet, 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 @@ static bool get_last_cause_impl(JSContext* cx, JS::HandleValue v_exc, 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 @@ gjs_throw(JSContext *context, 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 @@ gjs_throw(JSContext *context, * 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 @@ gjs_throw_literal(JSContext *context, * 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 --git a/cjs/jsapi-util-string.cpp b/cjs/jsapi-util-string.cpp index 9c5fd1d7..9f1a0d1d 100644 --- a/cjs/jsapi-util-string.cpp +++ b/cjs/jsapi-util-string.cpp @@ -209,7 +209,7 @@ gjs_string_to_filename(JSContext *context, 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 @@ gjs_string_from_filename(JSContext *context, 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 @@ gjs_string_from_filename(JSContext *context, 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 @@ gjs_string_to_ucs4(JSContext *cx, 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 @@ gjs_string_to_ucs4(JSContext *cx, 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 @@ gjs_string_from_ucs4(JSContext *cx, } 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 --git a/cjs/jsapi-util.cpp b/cjs/jsapi-util.cpp index 26fa4c5a..8119e19c 100644 --- a/cjs/jsapi-util.cpp +++ b/cjs/jsapi-util.cpp @@ -39,6 +39,7 @@ #include #include // for JS_InstanceOf #include // for ProtoKeyToClass +#include // for JSProto_InternalError, JSProto_SyntaxError #include "cjs/atoms.h" #include "cjs/context-private.h" @@ -330,8 +331,6 @@ static std::string format_exception_with_cause( 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_maybe_gc (JSContext *context) 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 --git a/cjs/jsapi-util.h b/cjs/jsapi-util.h index 4a9153d5..6e1a15ad 100644 --- a/cjs/jsapi-util.h +++ b/cjs/jsapi-util.h @@ -23,12 +23,12 @@ #include #include +#include // for JSExnType #include #include // for IgnoreGCPolicy #include #include #include // for UniqueChars -#include // for JSProtoKey #include "cjs/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 @@ struct GjsAutoPointer { 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 @@ struct GjsAutoPointer { 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 @@ struct GjsAutoPointer { 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 @@ using GjsAutoChar16 = GjsAutoPointer; 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 @@ struct GjsAutoInfo : GjsAutoBaseInfo { // 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 @@ struct GjsAutoInfo : GjsAutoBaseInfo { } }; +using GjsAutoArgInfo = GjsAutoInfo; using GjsAutoEnumInfo = GjsAutoInfo; using GjsAutoFieldInfo = GjsAutoInfo; using GjsAutoFunctionInfo = GjsAutoInfo; @@ -304,6 +336,7 @@ using GjsAutoInterfaceInfo = GjsAutoInfo; 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 @@ struct GjsSmartPointer : GjsAutoBaseInfo { template <> struct GjsSmartPointer : GjsAutoError { using GjsAutoError::GjsAutoError; + using GjsAutoError::operator=; + using GjsAutoError::operator&; }; template <> @@ -415,10 +450,6 @@ struct GCPolicy : public IgnoreGCPolicy {}; 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 @@ JSObject* gjs_define_string_array(JSContext* cx, JS::HandleObject obj, [[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 @@ bool gjs_object_require_converted_property(JSContext *context, 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 --git a/cjs/mem.cpp b/cjs/mem.cpp index afb69a58..0a5c2c42 100644 --- a/cjs/mem.cpp +++ b/cjs/mem.cpp @@ -4,8 +4,6 @@ #include -#include // for atomic_int64_t - #include #include "cjs/mem-private.h" diff --git a/cjs/module.cpp b/cjs/module.cpp index b7962bcc..82972bff 100644 --- a/cjs/module.cpp +++ b/cjs/module.cpp @@ -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 "cjs/atoms.h" #include "cjs/context-private.h" +#include "cjs/deprecation.h" #include "cjs/global.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" @@ -153,7 +151,7 @@ class GjsScriptModule { JS::HandleObject module, GFile *file) { - GError *error = nullptr; + GjsAutoError error; GjsAutoChar script; size_t script_len = 0; @@ -194,14 +192,9 @@ class GjsScriptModule { * 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 @@ static bool import_native_module_sync(JSContext* cx, unsigned argc, 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); @@ -505,7 +498,7 @@ bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref, * 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 +509,6 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, 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 +523,9 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, 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)) @@ -545,6 +535,23 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, return &result.toObject(); } +// Note: exception is never pending after this function finishes, even if it +// returns null. The return value is intended to be passed to +// JS::FinishDynamicModuleImport(). +static JSObject* reject_new_promise_with_pending_exception(JSContext* cx) { + JS::ExceptionStack stack{cx}; + if (!JS::StealPendingExceptionStack(cx, &stack)) { + gjs_log_exception(cx); + return nullptr; + } + JS::RootedObject rejected{cx, JS::NewPromiseObject(cx, nullptr)}; + if (!rejected || !JS::RejectPromise(cx, rejected, stack.exception())) { + gjs_log_exception(cx); + return nullptr; + } + return rejected; +} + // Call JS::FinishDynamicModuleImport() with the values stashed in the function. // Can fail in JS::FinishDynamicModuleImport(), but will assert if anything // fails in fetching the stashed values, since that would be a serious GJS bug. @@ -586,9 +593,12 @@ static bool finish_import(JSContext* cx, JS::HandleObject evaluation_promise, // case we must not call JS::FinishDynamicModuleImport(). GJS_JSAPI_RETURN_CONVENTION static bool fail_import(JSContext* cx, const JS::CallArgs& args) { - if (JS_IsExceptionPending(cx)) - return finish_import(cx, nullptr, args); - return false; + if (!JS_IsExceptionPending(cx)) + return false; + + JS::RootedObject rejected_promise{ + cx, reject_new_promise_with_pending_exception(cx)}; + return finish_import(cx, rejected_promise, args); } GJS_JSAPI_RETURN_CONVENTION @@ -597,12 +607,16 @@ static bool import_rejected(JSContext* cx, unsigned argc, JS::Value* vp) { gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise rejected"); - // Throw the value that the promise is rejected with, so that - // FinishDynamicModuleImport will reject the internal_promise with it. - JS_SetPendingException(cx, args.get(0), - JS::ExceptionStackBehavior::DoNotCapture); + // Reject a new promise with the rejection value of the async import + // promise, so that FinishDynamicModuleImport will reject the + // internal_promise with it. + JS::RootedObject rejected{cx, JS::NewPromiseObject(cx, nullptr)}; + if (!rejected || !JS::RejectPromise(cx, rejected, args.get(0))) { + gjs_log_exception(cx); + return finish_import(cx, nullptr, args); + } - return finish_import(cx, nullptr, args); + return finish_import(cx, rejected, args); } GJS_JSAPI_RETURN_CONVENTION @@ -611,14 +625,13 @@ static bool import_resolved(JSContext* cx, unsigned argc, JS::Value* vp) { 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 +651,7 @@ bool gjs_dynamic_module_resolve(JSContext* cx, "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 +672,13 @@ bool gjs_dynamic_module_resolve(JSContext* cx, 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()); } @@ -675,9 +688,16 @@ bool gjs_dynamic_module_resolve(JSContext* cx, args[1].setString(specifier); JS::RootedValue result(cx); - if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result)) - return JS::FinishDynamicModuleImport(cx, nullptr, importing_module_priv, + if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result)) { + if (!JS_IsExceptionPending(cx)) + return false; + + JS::RootedObject rejected_promise{ + cx, reject_new_promise_with_pending_exception(cx)}; + return JS::FinishDynamicModuleImport(cx, rejected_promise, + importing_module_priv, module_request, internal_promise); + } // Release in finish_import GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx); diff --git a/cjs/promise.cpp b/cjs/promise.cpp index ad4f4dc3..62a79f74 100644 --- a/cjs/promise.cpp +++ b/cjs/promise.cpp @@ -6,8 +6,6 @@ #include // for size_t -#include - #include #include diff --git a/cjs/stack.cpp b/cjs/stack.cpp index e3d2ab6e..30ca4a4c 100644 --- a/cjs/stack.cpp +++ b/cjs/stack.cpp @@ -4,7 +4,10 @@ #include -#include // for stderr +#include // for stderr, open_memstream + +#include +#include #include #include @@ -12,6 +15,7 @@ #include #include +#include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" @@ -35,3 +39,49 @@ gjs_dumpstack(void) gjs_context_print_stack_stderr(context); } } + +#ifdef HAVE_OPEN_MEMSTREAM +static std::string +stack_trace_string(GjsContext *context) { + JSContext *cx = static_cast(gjs_context_get_native_context(context)); + std::ostringstream out; + FILE *stream; + GjsAutoChar buf; + size_t len; + + stream = open_memstream(buf.out(), &len); + if (!stream) { + out << "No stack trace for context " << context << ": " + "open_memstream() failed\n\n"; + return out.str(); + } + js::DumpBacktrace(cx, stream); + fclose(stream); + out << "== Stack trace for context " << context << " ==\n" + << buf.get() << "\n"; + return out.str(); +} +#endif + +std::string +gjs_dumpstack_string() { + std::string out; + std::ostringstream all_traces; + +#ifdef HAVE_OPEN_MEMSTREAM + GjsSmartPointer contexts = gjs_context_get_all(); + GList *iter; + + for (iter = contexts; iter; iter = iter->next) { + GjsAutoUnref context(GJS_CONTEXT(iter->data)); + all_traces << stack_trace_string(context); + } + out = all_traces.str(); + out.resize(MAX(out.size() - 2, 0)); +#else + out = "No stack trace: no open_memstream() support. " + "See https://bugzilla.mozilla.org/show_bug.cgi?id=1826290"; +#endif + + return out; +} diff --git a/cjs/text-encoding.cpp b/cjs/text-encoding.cpp index c85f54bd..13b5a915 100644 --- a/cjs/text-encoding.cpp +++ b/cjs/text-encoding.cpp @@ -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 "cjs/jsapi-util-args.h" @@ -53,11 +53,10 @@ static void gfree_arraybuffer_contents(void* contents, void*) { 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 @@ GJS_JSAPI_RETURN_CONVENTION 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 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( 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 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( 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 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( // 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 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( // 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 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( } else { buffer_size += bytes_len; } - - // Clear the error. - g_clear_error(&error); } } @@ -215,7 +212,7 @@ static JSString* gjs_decode_from_uint8array_slow(JSContext* cx, } size_t bytes_written, bytes_read; - GError* error = nullptr; + GjsAutoError error; GjsAutoChar bytes = g_convert(reinterpret_cast(input), input_len, @@ -326,7 +323,7 @@ JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array, // 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 @@ JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str, if (array_buffer) mozilla::Unused << utf8.release(); } else { - GError* error = nullptr; + GjsAutoError error; GjsAutoChar encoded = nullptr; size_t bytes_written; @@ -475,7 +472,7 @@ static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str, 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 @@ static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str, return false; } - mozilla::Maybe> results; + mozilla::Maybe> results; { JS::AutoCheckCannotGC nogc(cx); @@ -507,7 +504,7 @@ static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str, } size_t read, written; - mozilla::Tie(read, written) = *results; + std::tie(read, written) = *results; g_assert(written <= len); diff --git a/debian/control b/debian/control index b9e95478..d271084d 100644 --- a/debian/control +++ b/debian/control @@ -15,8 +15,9 @@ Build-Depends: libffi-dev (>= 3.3), libgirepository1.0-dev (>= 1.71), libglib2.0-dev (>= 2.58.0), - libmozjs-102-dev, + libmozjs-115-dev, libreadline-dev, + libxml2-utils, meson (>= 0.54.0), pkg-config (>= 0.28), xauth , @@ -65,7 +66,7 @@ Depends: libcairo2-dev, libcjs0 (= ${binary:Version}), libgirepository1.0-dev (>= 1.64), - libmozjs-102-dev, + libmozjs-115-dev, ${misc:Depends}, Description: Mozilla-based javascript bindings for Cinnamon platform Makes it possible for applications to use all of Cinnamon platform diff --git a/debian/libcjs0.symbols b/debian/libcjs0.symbols index 53847561..1ac0f55c 100644 --- a/debian/libcjs0.symbols +++ b/debian/libcjs0.symbols @@ -22,6 +22,7 @@ libcjs.so.0 libcjs0 #MINVER# gjs_context_new_with_search_path@Base 2.0.0 gjs_context_print_stack_stderr@Base 2.0.0 gjs_context_register_module@Base 5.4.0 + gjs_context_run_in_realm@Base 6.0.0 gjs_context_set_argv@Base 5.4.0 gjs_context_setup_debugger_console@Base 4.6.0 gjs_coverage_enable@Base 4.6.0 diff --git a/doc/Hacking.md b/doc/Hacking.md index d58f9362..592eab42 100644 --- a/doc/Hacking.md +++ b/doc/Hacking.md @@ -37,7 +37,7 @@ You can also skip this step if you are not writing any C++ code.) ## 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 @@ example, Fedora 36 or Ubuntu 22.04 and later versions), then you don't 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 @@ with the debugging features enabled. 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,17 +94,17 @@ make sure to use the same build prefix for SpiderMonkey with `--prefix`. 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 ```sh -LD_LIBRARY_PATH=_build GI_TYPELIB_PATH=_build GJS_USE_UNINSTALLED_FILES=1 ./_build/gjs-console script.js +LD_LIBRARY_PATH=_build GI_TYPELIB_PATH=_build GJS_USE_UNINSTALLED_FILES=1 ./_build/cjs-console script.js ``` To install GJS into the path you chose with `-Dprefix`, (or into @@ -152,7 +152,7 @@ more likely to show up. 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 @@ the compilers normally fail to detect. 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 @@ This will build GJS into a separate build directory with code coverage 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 --git a/doc/Mapping.md b/doc/Mapping.md index 5f2d24a0..6824d3aa 100644 --- a/doc/Mapping.md +++ b/doc/Mapping.md @@ -183,7 +183,7 @@ var MyLabel = GObject.registerClass({ ## 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 @@ if (labelInstance instance of Gtk.Label) 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 --git a/doc/Overrides.md b/doc/Overrides.md index 07c18314..445a6e06 100644 --- a/doc/Overrides.md +++ b/doc/Overrides.md @@ -200,6 +200,21 @@ try { } ``` +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 @@ for (const bytes of inputStream.createSyncIterator(4)) { [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 @@ Note that this method will unpack source values (e.g. `uint32`) to native values (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 @@ Note that the GType name for user-defined subclasses will be prefixed with `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 @@ Disconnects all handlers on an instance that match `data`. [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 --git a/doc/README.md b/doc/README.md index 887045ff..aca22562 100644 --- a/doc/README.md +++ b/doc/README.md @@ -25,30 +25,31 @@ libraries. General documentation about built-in modules and APIs is under the 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 @@ GNOME Apps * [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 @@ Others * [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 --git a/doc/Signals.md b/doc/Signals.md index dacbf180..70f038b7 100644 --- a/doc/Signals.md +++ b/doc/Signals.md @@ -1,4 +1,4 @@ -## Signals +# Signals The `Signals` module provides a GObject-like signal framework for native JavaScript classes and objects. diff --git a/examples/http-client.js b/examples/http-client.js index dd0a8878..b9535350 100644 --- a/examples/http-client.js +++ b/examples/http-client.js @@ -23,7 +23,7 @@ function splice_callback(outputStream, result) { let data; try { - outputStream.splice_finish(outputStream, result); + outputStream.splice_finish(result); data = outputStream.steal_as_bytes(); } catch (err) { logError(err); diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp index 5f000f37..3de89bee 100644 --- a/gi/arg-cache.cpp +++ b/gi/arg-cache.cpp @@ -121,6 +121,14 @@ static bool report_invalid_null(JSContext* cx, const char* arg_name) { 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 @@ struct Nullable { } }; -// 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 && @@ -216,30 +216,31 @@ struct BaseInfo { }; // boxed / union / GObject -struct RegisteredType { +struct GTypedType { + explicit GTypedType(GType gtype) : m_gtype(gtype) {} + constexpr GType gtype() const { return m_gtype; } + + protected: + GType m_gtype; +}; + +struct RegisteredType : GTypedType { RegisteredType(GType gtype, GIInfoType info_type) - : m_gtype(gtype), m_info_type(info_type) {} + : GTypedType(gtype), m_info_type(info_type) {} explicit RegisteredType(GIBaseInfo* info) - : m_gtype(g_registered_type_info_get_g_type(info)), + : GTypedType(g_registered_type_info_get_g_type(info)), m_info_type(g_base_info_get_type(info)) { g_assert(m_gtype != G_TYPE_NONE && "Use RegisteredInterface for this type"); } - constexpr GType gtype() const { return m_gtype; } - - GType m_gtype; GIInfoType m_info_type : 5; }; -struct RegisteredInterface : BaseInfo { +struct RegisteredInterface : BaseInfo, GTypedType { explicit RegisteredInterface(GIBaseInfo* info) : BaseInfo(info, GjsAutoTakeOwnership{}), - m_gtype(g_registered_type_info_get_g_type(m_info)) {} - - constexpr GType gtype() const { return m_gtype; } - - GType m_gtype; + GTypedType(g_registered_type_info_get_g_type(m_info)) {} }; struct Callback : Nullable, BaseInfo { @@ -574,6 +575,8 @@ struct GBytesIn : BoxedIn { using BoxedIn::BoxedIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument* out_arg) override; }; struct GBytesInTransferNone : GBytesIn { @@ -713,6 +716,12 @@ struct CallerAllocatesOut : GenericOut, CallerAllocates { } }; +struct BoxedCallerAllocatesOut : CallerAllocatesOut, GTypedType { + using GTypedType::GTypedType; + bool release(JSContext*, GjsFunctionCallState*, GIArgument*, + GIArgument*) override; +}; + GJS_JSAPI_RETURN_CONVENTION bool NotIntrospectable::in(JSContext* cx, GjsFunctionCallState* state, GIArgument*, JS::HandleValue) { @@ -1200,6 +1209,7 @@ bool GBytesIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::RootedObject object(cx, &value.toObject()); if (JS_IsUint8Array(object)) { + state->ignore_release.insert(arg); gjs_arg_set(arg, gjs_byte_array_get_bytes(object)); return true; } @@ -1210,6 +1220,15 @@ bool GBytesIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, cx, object, arg, GI_DIRECTION_IN, GI_TRANSFER_EVERYTHING, G_TYPE_BYTES); } +GJS_JSAPI_RETURN_CONVENTION +bool GBytesIn::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument* in_arg, GIArgument* out_arg) { + if (state->ignore_release.erase(in_arg)) + return BoxedIn::release(cx, state, in_arg, out_arg); + + return BoxedInTransferNone::release(cx, state, in_arg, out_arg); +} + GJS_JSAPI_RETURN_CONVENTION bool InterfaceIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { @@ -1330,7 +1349,8 @@ bool ExplicitArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, GIArgument* length_arg = &(state->out_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, arg, length); + return gjs_value_from_explicit_array(cx, value, &m_type_info, m_transfer, + arg, length); } GJS_JSAPI_RETURN_CONVENTION @@ -1417,6 +1437,13 @@ bool CallerAllocatesOut::release(JSContext*, GjsFunctionCallState*, return true; } +GJS_JSAPI_RETURN_CONVENTION +bool BoxedCallerAllocatesOut::release(JSContext*, GjsFunctionCallState*, + GIArgument* in_arg, GIArgument*) { + g_boxed_free(m_gtype, gjs_arg_steal(in_arg)); + return true; +} + GJS_JSAPI_RETURN_CONVENTION bool CallbackIn::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { @@ -1518,16 +1545,16 @@ constexpr size_t argument_maximum_size() { #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 && @@ -1563,10 +1590,6 @@ std::unique_ptr Argument::make(uint8_t index, const char* name, 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"); @@ -1597,38 +1620,35 @@ bool ArgsCache::initialize(JSContext* cx, GICallableInfo* callable) { 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( @@ -1640,25 +1660,19 @@ T* ArgsCache::set_argument_auto(Tuple&& tuple, Args&&... args) { } 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; @@ -1666,13 +1680,6 @@ GType ArgsCache::instance_type() const { 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) @@ -1681,7 +1688,7 @@ GITypeInfo* ArgsCache::return_type() const { 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); } @@ -2100,21 +2107,6 @@ void ArgsCache::build_instance(GICallableInfo* callable) { 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; -} - void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, GIArgInfo* arg, GICallableInfo* callable, bool* inc_counter_out) { @@ -2160,20 +2152,15 @@ void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, 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 { + size = gjs_type_get_element_size(type_tag, &type_info); } if (!size) { @@ -2182,6 +2169,18 @@ void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, return; } + if (type_tag == GI_TYPE_TAG_INTERFACE) { + GjsAutoBaseInfo interface_info = + g_type_info_get_interface(&type_info); + GType gtype = g_registered_type_info_get_g_type(interface_info); + if (g_type_is_a(gtype, G_TYPE_BOXED)) { + auto* gjs_arg = set_argument_auto( + common_args, gtype); + gjs_arg->m_allocates_size = size; + return; + } + } + auto* gjs_arg = set_argument_auto(common_args); gjs_arg->m_allocates_size = size; diff --git a/gi/arg-cache.h b/gi/arg-cache.h index cf79dcc7..14c8946f 100644 --- a/gi/arg-cache.h +++ b/gi/arg-cache.h @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -20,6 +19,7 @@ #include "gi/arg.h" #include "cjs/enum-utils.h" +#include "cjs/jsapi-util.h" #include "cjs/macros.h" class GjsFunctionCallState; @@ -51,7 +51,6 @@ enum class Kind { } // namespace Arg struct Argument { - using UniquePtr = std::unique_ptr; virtual ~Argument() = default; GJS_JSAPI_RETURN_CONVENTION @@ -115,11 +114,13 @@ struct Argument { 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 @@ struct ArgsCache { 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 @@ struct ArgsCache { 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 @@ struct ArgsCache { 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 @@ struct ArgsCache { 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 @@ struct ArgsCache { } 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 --git a/gi/arg-inl.h b/gi/arg-inl.h index 9d78569d..36db51b7 100644 --- a/gi/arg-inl.h +++ b/gi/arg-inl.h @@ -199,7 +199,7 @@ GJS_JSAPI_RETURN_CONVENTION inline bool gjs_arg_set_from_js_value( if constexpr (Gjs::type_has_js_getter()) return Gjs::js_value_to_c(cx, value, &gjs_arg_member(arg)); - Gjs::JsValueHolder::Relaxed val; + Gjs::JsValueHolder::Relaxed val{}; if (!Gjs::js_value_to_c_checked(cx, value, &val, out_of_range)) return false; diff --git a/gi/arg.cpp b/gi/arg.cpp index 87eae990..3fe5c015 100644 --- a/gi/arg.cpp +++ b/gi/arg.cpp @@ -9,6 +9,7 @@ #include // for strcmp, strlen, memcpy #include +#include #include #include @@ -58,41 +59,25 @@ #include "util/misc.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( - JSContext*, GITransfer, GITypeInfo*, GITypeTag, GjsArgumentFlags, - GIArgument*); + JSContext*, GITransfer, GITypeInfo*, GITypeTag, GjsArgumentType, + GjsArgumentFlags, GIArgument*); static void throw_invalid_argument(JSContext* cx, JS::HandleValue value, GITypeInfo* arginfo, const char* arg_name, GjsArgumentType arg_type); bool _gjs_flags_value_is_valid(JSContext* context, GType gtype, int64_t value) { - GFlagsValue *v; - guint32 tmpval; + /* Do proper value check for flags with GType's */ + if (gtype != G_TYPE_NONE) { + GjsAutoTypeClass gflags_class(gtype); + uint32_t tmpval = static_cast(value); - /* FIXME: Do proper value check for flags with GType's */ - if (gtype == G_TYPE_NONE) - return true; - - GjsAutoTypeClass klass(gtype); - - /* check all bits are defined for flags.. not necessarily desired */ - tmpval = (guint32)value; - if (tmpval != value) { /* Not a guint32 */ - gjs_throw(context, - "0x%" G_GINT64_MODIFIER "x is not a valid value for flags %s", - value, g_type_name(G_TYPE_FROM_CLASS(klass))); - return false; - } - - while (tmpval) { - v = g_flags_get_first_value(klass.as(), tmpval); - if (!v) { + /* check all bits are valid bits for the flag and is a 32 bit flag*/ + if ((tmpval &= gflags_class->mask) != value) { /* Not a guint32 with invalid mask values*/ gjs_throw(context, - "0x%x is not a valid value for flags %s", - (guint32)value, g_type_name(G_TYPE_FROM_CLASS(klass))); + "0x%" G_GINT64_MODIFIER "x is not a valid value for flags %s", + value, g_type_name(gtype)); return false; } - - tmpval &= ~v->value; } return true; @@ -192,8 +177,7 @@ static bool _gjs_enum_value_is_valid(JSContext* context, GIEnumInfo* enum_info, break; case GI_INFO_TYPE_VALUE: // Special case for GValues - gtype = G_TYPE_VALUE; - break; + return true; default: gtype = G_TYPE_NONE; } @@ -201,7 +185,7 @@ static bool _gjs_enum_value_is_valid(JSContext* context, GIEnumInfo* enum_info, 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; } @@ -243,6 +227,9 @@ static bool _gjs_enum_value_is_valid(JSContext* context, GIEnumInfo* enum_info, case GI_INFO_TYPE_UNION: return g_type_info_is_pointer(type_info); + case GI_INFO_TYPE_OBJECT: + return true; + default: return false; } @@ -262,23 +249,18 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( // 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; } @@ -510,8 +492,8 @@ static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, g_assert(props && "Property bag cannot be null"); - GITypeInfo* key_param_info = g_type_info_get_param_type(type_info, 0); - GITypeInfo* val_param_info = g_type_info_get_param_type(type_info, 1); + GjsAutoBaseInfo key_param_info = g_type_info_get_param_type(type_info, 0); + GjsAutoBaseInfo val_param_info = g_type_info_get_param_type(type_info, 1); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release (key_param_info, g_type_info_get_tag(key_param_info)) || @@ -766,22 +748,13 @@ gjs_array_to_ptrarray(JSContext *context, } 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); @@ -799,57 +772,14 @@ static bool gjs_array_to_flat_struct_array(JSContext* cx, 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: @@ -868,32 +798,12 @@ gjs_array_from_flat_gvalue_array(JSContext *context, } } -[[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); @@ -943,13 +853,20 @@ static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, 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]]; @@ -973,62 +890,161 @@ static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, } } -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; + } + + g_return_val_if_reached(0); +} + +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 (element_transfer != GI_TRANSFER_EVERYTHING) + return true; + + if constexpr (!zero_terminated) { + if (length == 0) + return true; + } else { + if (!arg_array) + 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]; + + if constexpr (zero_terminated) { + if (*element_start == 0 && + memcmp(element_start, element_start + 1, element_size - 1) == 0) + break; + } + + gjs_arg_set(&elem, is_pointer + ? *reinterpret_cast(element_start) + : 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 (!zero_terminated) { + if (i == length - 1) + break; + } } - return element_size; + 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); } @@ -1725,7 +1741,8 @@ bool gjs_value_to_g_argument(JSContext* context, JS::HandleValue value, 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); @@ -1890,7 +1907,7 @@ bool gjs_value_to_callback_out_arg(JSContext* context, JS::HandleValue value, template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list( JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* type_info, - T* list) { + GITransfer transfer, T* list) { static_assert(std::is_same_v || std::is_same_v); GArgument arg; JS::RootedValueVector elems(cx); @@ -1906,7 +1923,9 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list( return false; } - if (!gjs_value_from_g_argument(cx, elems[i], param_info, &arg, true)) + if (!gjs_value_from_g_argument(cx, elems[i], param_info, + GJS_ARGUMENT_LIST_ELEMENT, transfer, + &arg)) return false; } @@ -1938,7 +1957,8 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list( gjs_arg_set(&elem, l->data); if (!gjs_g_arg_release_internal(cx, transfer, param_info, type_tag, - flags, &elem)) { + GJS_ARGUMENT_LIST_ELEMENT, flags, + &elem)) { return false; } } @@ -1949,11 +1969,14 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list( template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) - GITypeInfo* param_info, GIArgument* arg, void* array, size_t length) { + GITypeInfo* param_info, GIArgument* arg, void* array, size_t length, + GITransfer transfer = GI_TRANSFER_EVERYTHING) { for (size_t i = 0; i < length; i++) { gjs_arg_set(arg, *(static_cast(array) + i)); - if (!gjs_value_from_g_argument(cx, elems[i], param_info, arg, true)) + if (!gjs_value_from_g_argument(cx, elems[i], param_info, + GJS_ARGUMENT_ARRAY_ELEMENT, + transfer, arg)) return false; } @@ -1961,23 +1984,15 @@ GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_carray( } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_from_carray_internal (JSContext *context, - JS::MutableHandleValue value_p, - GIArrayType array_type, - GITypeInfo *param_info, - guint length, - gpointer array) -{ +static bool gjs_array_from_carray_internal( + JSContext* context, JS::MutableHandleValue value_p, GIArrayType array_type, + GITypeInfo* param_info, GITransfer transfer, guint length, void* array) { GArgument arg; GITypeTag element_type; guint i; 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); @@ -2068,7 +2083,8 @@ gjs_array_from_carray_internal (JSContext *context, 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; @@ -2081,8 +2097,9 @@ gjs_array_from_carray_internal (JSContext *context, gjs_arg_set(&arg, static_cast(array) + (struct_size * i)); - if (!gjs_value_from_g_argument(context, elems[i], param_info, - &arg, true)) + if (!gjs_value_from_g_argument( + context, elems[i], param_info, + GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) return false; } @@ -2099,7 +2116,7 @@ gjs_array_from_carray_internal (JSContext *context, case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + &arg, array, length, transfer)) return false; break; case GI_TYPE_TAG_VOID: @@ -2118,12 +2135,10 @@ gjs_array_from_carray_internal (JSContext *context, } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_from_fixed_size_array (JSContext *context, - JS::MutableHandleValue value_p, - GITypeInfo *type_info, - gpointer array) -{ +static bool gjs_array_from_fixed_size_array(JSContext* context, + JS::MutableHandleValue value_p, + GITypeInfo* type_info, + GITransfer transfer, void* array) { gint length; length = g_type_info_get_array_fixed_size(type_info); @@ -2134,31 +2149,26 @@ gjs_array_from_fixed_size_array (JSContext *context, return gjs_array_from_carray_internal(context, value_p, g_type_info_get_array_type(type_info), - param_info, length, array); + param_info, transfer, length, array); } -bool -gjs_value_from_explicit_array(JSContext *context, - JS::MutableHandleValue value_p, - GITypeInfo *type_info, - GIArgument *arg, - int length) -{ +bool gjs_value_from_explicit_array(JSContext* context, + JS::MutableHandleValue value_p, + GITypeInfo* type_info, GITransfer transfer, + GIArgument* arg, int length) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); return gjs_array_from_carray_internal( context, value_p, g_type_info_get_array_type(type_info), param_info, - length, gjs_arg_get(arg)); + transfer, length, gjs_arg_get(arg)); } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_from_boxed_array (JSContext *context, - JS::MutableHandleValue value_p, - GIArrayType array_type, - GITypeInfo *param_info, - GArgument *arg) -{ +static bool gjs_array_from_boxed_array(JSContext* context, + JS::MutableHandleValue value_p, + GIArrayType array_type, + GITypeInfo* param_info, + GITransfer transfer, GArgument* arg) { GArray *array; GPtrArray *ptr_array; gpointer data = NULL; @@ -2188,12 +2198,12 @@ gjs_array_from_boxed_array (JSContext *context, } return gjs_array_from_carray_internal(context, value_p, array_type, - param_info, length, data); + param_info, transfer, length, data); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, - GITypeInfo* param_info, + GITypeInfo* param_info, GITransfer transfer, const GValue* gvalue) { void* data = nullptr; size_t length = 0; @@ -2220,24 +2230,40 @@ bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, } return gjs_array_from_carray_internal(cx, value_p, array_type, param_info, - length, data); + transfer, length, data); } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) - GITypeInfo* param_info, GIArgument* arg, void* c_array) { + GITypeInfo* param_info, GIArgument* arg, void* c_array, + 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); return false; } - if (!gjs_value_from_g_argument(cx, elems[i], param_info, arg, true)) + if (!gjs_value_from_g_argument(cx, elems[i], param_info, + GJS_ARGUMENT_ARRAY_ELEMENT, transfer, + arg)) return false; } @@ -2245,12 +2271,9 @@ GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_from_zero_terminated_c_array (JSContext *context, - JS::MutableHandleValue value_p, - GITypeInfo *param_info, - gpointer c_array) -{ +static bool gjs_array_from_zero_terminated_c_array( + JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* param_info, + GITransfer transfer, void* c_array) { GArgument arg; GITypeTag element_type; @@ -2322,17 +2345,31 @@ gjs_array_from_zero_terminated_c_array (JSContext *context, 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: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + context, elems, param_info, &arg, c_array, transfer)) return false; break; /* Boolean zero-terminated array makes no sense, because FALSE is also @@ -2355,14 +2392,10 @@ gjs_array_from_zero_terminated_c_array (JSContext *context, 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, - 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; @@ -2387,10 +2420,10 @@ gjs_object_from_g_hash (JSContext *context, while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) { g_type_info_argument_from_hash_pointer(key_param_info, key_pointer, &keyarg); - if (!gjs_value_from_g_argument(context, &keyjs, - key_param_info, &keyarg, - true)) - return false; + if (!gjs_value_from_g_argument(context, &keyjs, key_param_info, + GJS_ARGUMENT_HASH_ELEMENT, + transfer, &keyarg)) + return false; keystr = JS::ToString(context, keyjs); if (!keystr) @@ -2402,9 +2435,9 @@ gjs_object_from_g_hash (JSContext *context, g_type_info_argument_from_hash_pointer(val_param_info, val_pointer, &valarg); - if (!gjs_value_from_g_argument(context, &valjs, - val_param_info, &valarg, - true)) + if (!gjs_value_from_g_argument(context, &valjs, val_param_info, + GJS_ARGUMENT_HASH_ELEMENT, + transfer, &valarg)) return false; if (!JS_DefineProperty(context, obj, keyutf8.get(), valjs, @@ -2415,13 +2448,11 @@ gjs_object_from_g_hash (JSContext *context, return true; } -bool -gjs_value_from_g_argument (JSContext *context, - JS::MutableHandleValue value_p, - GITypeInfo *type_info, - GArgument *arg, - bool copy_structs) -{ +bool gjs_value_from_g_argument(JSContext* context, + JS::MutableHandleValue value_p, + GITypeInfo* type_info, + GjsArgumentType argument_type, + GITransfer transfer, GArgument* arg) { GITypeTag type_tag; type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); @@ -2584,8 +2615,20 @@ gjs_value_from_g_argument (JSContext *context, gjs_arg_get(arg)); gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)interface_info); - if (!_gjs_flags_value_is_valid(context, gtype, value_int64)) - return false; + + if (gtype != G_TYPE_NONE) { + /* check make sure 32 bit flag */ + if (static_cast(value_int64) != value_int64) { /* Not a guint32 */ + gjs_throw(context, + "0x%" G_GINT64_MODIFIER "x is not a valid value for flags %s", + value_int64, g_type_name(gtype)); + return false; + } + + /* Pass only valid values*/ + GjsAutoTypeClass gflags_class(gtype); + value_int64 &= gflags_class->mask; + } value_p.setNumber(static_cast(value_int64)); return true; @@ -2665,9 +2708,22 @@ gjs_value_from_g_argument (JSContext *context, JSObject *obj; - if (copy_structs || g_type_is_a(gtype, G_TYPE_VARIANT)) + if (gtype == G_TYPE_VARIANT) { + transfer = GI_TRANSFER_EVERYTHING; + } else if (transfer == GI_TRANSFER_CONTAINER) { + switch (argument_type) { + case GJS_ARGUMENT_ARRAY_ELEMENT: + case GJS_ARGUMENT_LIST_ELEMENT: + case GJS_ARGUMENT_HASH_ELEMENT: + transfer = GI_TRANSFER_EVERYTHING; + default: + break; + } + } + + if (transfer == GI_TRANSFER_EVERYTHING) obj = BoxedInstance::new_for_c_struct( - context, interface_info, gjs_arg_get(arg)); + context, interface_info, gjs_arg_get(arg)); else obj = BoxedInstance::new_for_c_struct( context, interface_info, gjs_arg_get(arg), @@ -2748,14 +2804,16 @@ gjs_value_from_g_argument (JSContext *context, g_assert(param_info != nullptr); return gjs_array_from_zero_terminated_c_array( - context, value_p, param_info, gjs_arg_get(arg)); + context, value_p, param_info, transfer, + gjs_arg_get(arg)); } else { /* arrays with length are handled outside of this function */ g_assert(((void) "Use gjs_value_from_explicit_array() for " "arrays with length param", g_type_info_get_array_length(type_info) == -1)); - return gjs_array_from_fixed_size_array( - context, value_p, type_info, gjs_arg_get(arg)); + return gjs_array_from_fixed_size_array(context, value_p, + type_info, transfer, + gjs_arg_get(arg)); } } else if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_BYTE_ARRAY) { @@ -2776,15 +2834,15 @@ gjs_value_from_g_argument (JSContext *context, return gjs_array_from_boxed_array( context, value_p, g_type_info_get_array_type(type_info), - param_info, arg); + param_info, transfer, arg); } break; case GI_TYPE_TAG_GLIST: - return gjs_array_from_g_list(context, value_p, type_info, + return gjs_array_from_g_list(context, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GSLIST: - return gjs_array_from_g_list(context, value_p, type_info, + return gjs_array_from_g_list(context, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GHASH: @@ -2797,7 +2855,7 @@ gjs_value_from_g_argument (JSContext *context, g_assert(val_param_info != nullptr); return gjs_object_from_g_hash(context, value_p, key_param_info, - val_param_info, + val_param_info, transfer, gjs_arg_get(arg)); } break; @@ -2827,8 +2885,9 @@ gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { gjs_arg_set(&val_arg, val); if (!gjs_g_arg_release_internal(c->context, c->transfer, c->key_param_info, g_type_info_get_tag(c->key_param_info), - c->flags, &key_arg)) - c->failed = true; + GJS_ARGUMENT_HASH_ELEMENT, c->flags, + &key_arg)) + c->failed = true; GITypeTag val_type = g_type_info_get_tag(c->val_param_info); @@ -2841,10 +2900,10 @@ gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { break; default: - if (!gjs_g_arg_release_internal(c->context, c->transfer, - c->val_param_info, val_type, - c->flags, &val_arg)) - c->failed = true; + if (!gjs_g_arg_release_internal( + c->context, c->transfer, c->val_param_info, val_type, + GJS_ARGUMENT_HASH_ELEMENT, c->flags, &val_arg)) + c->failed = true; } return true; @@ -2860,11 +2919,10 @@ constexpr static bool is_transfer_in_nothing(GITransfer transfer, } GJS_JSAPI_RETURN_CONVENTION -static bool gjs_g_arg_release_internal(JSContext* context, GITransfer transfer, - GITypeInfo* type_info, - GITypeTag type_tag, - GjsArgumentFlags flags, - GIArgument* arg) { +static bool gjs_g_arg_release_internal( + JSContext* context, GITransfer transfer, GITypeInfo* type_info, + GITypeTag type_tag, [[maybe_unused]] GjsArgumentType argument_type, + GjsArgumentFlags flags, GIArgument* arg) { g_assert(transfer != GI_TRANSFER_NOTHING || flags != GjsArgumentFlags::NONE); @@ -2988,28 +3046,6 @@ static bool gjs_g_arg_release_internal(JSContext* context, GITransfer transfer, 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: @@ -3054,40 +3090,22 @@ static bool gjs_g_arg_release_internal(JSContext* context, GITransfer transfer, 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, GI_TRANSFER_EVERYTHING, param_info, - element_type, 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, GI_TRANSFER_EVERYTHING, param_info, - element_type, 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( + context, element_transfer, + flags | GjsArgumentFlags::ARG_OUT, param_info, 0, arg); + } else { + return gjs_g_argument_release_array_internal( + 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: @@ -3143,7 +3161,7 @@ static bool gjs_g_arg_release_internal(JSContext* context, GITransfer transfer, g_array_index(array, gpointer, i)); if (!gjs_g_arg_release_internal( context, transfer, param_info, element_type, - flags, &arg_iter)) + GJS_ARGUMENT_ARRAY_ELEMENT, flags, &arg_iter)) return false; } } @@ -3199,7 +3217,7 @@ static bool gjs_g_arg_release_internal(JSContext* context, GITransfer transfer, 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}; @@ -3241,8 +3259,8 @@ bool gjs_g_argument_release(JSContext* cx, GITransfer transfer, "Releasing GArgument %s out param or return value", g_type_tag_to_string(type_tag)); - return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, flags, - arg); + return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, + GJS_ARGUMENT_ARGUMENT, flags, arg); } bool gjs_g_argument_release_in_arg(JSContext* cx, GITransfer transfer, @@ -3267,7 +3285,7 @@ bool gjs_g_argument_release_in_arg(JSContext* cx, GITransfer transfer, if (type_needs_release (type_info, type_tag)) return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, - flags, arg); + GJS_ARGUMENT_ARGUMENT, flags, arg); return true; } @@ -3275,70 +3293,33 @@ bool gjs_g_argument_release_in_arg(JSContext* cx, GITransfer transfer, 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); - - 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); - } - } - - 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, - GjsArgumentFlags::ARG_IN, &elem)) { - return false; - } - } - } - - return true; + return gjs_g_argument_release_array_internal( + context, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, param_type, + length, 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); - GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); - type_tag = g_type_info_get_tag(param_type); - - 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, GI_TRANSFER_EVERYTHING, - param_type, type_tag, - GjsArgumentFlags::ARG_OUT, &elem)) { - return false; - } - } - } + GITransfer element_transfer = transfer == GI_TRANSFER_CONTAINER + ? GI_TRANSFER_NOTHING + : GI_TRANSFER_EVERYTHING; - return true; + GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); + return gjs_g_argument_release_array_internal(context, element_transfer, + GjsArgumentFlags::ARG_OUT, + param_type, length, arg); } diff --git a/gi/arg.h b/gi/arg.h index 0c6a3f71..c834d3b3 100644 --- a/gi/arg.h +++ b/gi/arg.h @@ -12,6 +12,7 @@ #include #include +#include // for GHashTable #include #include @@ -41,6 +42,10 @@ enum class GjsArgumentFlags : uint8_t { 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 @@ bool gjs_array_to_explicit_array(JSContext* cx, JS::HandleValue value, 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); @@ -76,18 +81,36 @@ bool inline gjs_value_to_g_argument(JSContext* cx, JS::HandleValue value, } GJS_JSAPI_RETURN_CONVENTION -bool gjs_value_from_g_argument(JSContext *context, +bool gjs_value_from_g_argument(JSContext* context, JS::MutableHandleValue value_p, - GITypeInfo *type_info, - GIArgument *arg, - bool copy_structs); + GITypeInfo* type_info, + GjsArgumentType argument_type, + GITransfer transfer, GIArgument* arg); + +GJS_JSAPI_RETURN_CONVENTION +inline bool gjs_value_from_g_argument(JSContext* cx, + JS::MutableHandleValue value_p, + GITypeInfo* type_info, GIArgument* arg, + bool copy_structs) { + return gjs_value_from_g_argument( + cx, value_p, type_info, GJS_ARGUMENT_ARGUMENT, + copy_structs ? GI_TRANSFER_EVERYTHING : GI_TRANSFER_NOTHING, arg); +} GJS_JSAPI_RETURN_CONVENTION -bool gjs_value_from_explicit_array(JSContext *context, +bool gjs_value_from_explicit_array(JSContext* context, JS::MutableHandleValue value_p, - GITypeInfo *type_info, - GIArgument *arg, - int length); + GITypeInfo* type_info, GITransfer transfer, + GIArgument* arg, int length); + +GJS_JSAPI_RETURN_CONVENTION +inline bool gjs_value_from_explicit_array(JSContext* context, + JS::MutableHandleValue value_p, + GITypeInfo* type_info, + GIArgument* arg, int length) { + return gjs_value_from_explicit_array(context, value_p, type_info, + GI_TRANSFER_EVERYTHING, arg, length); +} GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release(JSContext*, GITransfer, GITypeInfo*, @@ -135,6 +158,13 @@ bool gjs_array_to_strv (JSContext *context, GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, - GITypeInfo* param_info, const GValue* gvalue); + 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 --git a/gi/boxed.cpp b/gi/boxed.cpp index 30b85455..d9149b01 100644 --- a/gi/boxed.cpp +++ b/gi/boxed.cpp @@ -7,7 +7,6 @@ #include #include // for memcpy, size_t, strcmp -#include #include // for move, forward #include @@ -360,11 +359,11 @@ bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj, 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; } @@ -586,7 +585,8 @@ bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj, return gjs_value_from_explicit_array(cx, rval, type_info, &arg, length); } - return gjs_value_from_g_argument(cx, rval, type_info, &arg, true); + return gjs_value_from_g_argument(cx, rval, type_info, GJS_ARGUMENT_FIELD, + GI_TRANSFER_EVERYTHING, &arg); } /* @@ -1100,12 +1100,8 @@ bool BoxedInstance::init_from_c_struct(JSContext* cx, void* gboxed) { copy_boxed(gboxed); return true; } else if (gtype() == G_TYPE_VARIANT) { - // Sink the reference if it is floating - GVariant* temp = g_variant_take_ref(static_cast(gboxed)); - // Add an additional reference which will be unref-ed - // in the marshaller - own_ptr(g_variant_ref(temp)); - debug_lifecycle("Boxed pointer created by taking GVariant ref"); + own_ptr(g_variant_ref_sink(static_cast(gboxed))); + debug_lifecycle("Boxed pointer created by sinking GVariant ref"); return true; } else if (get_prototype()->can_allocate_directly()) { copy_memory(gboxed); diff --git a/gi/closure.cpp b/gi/closure.cpp index 01397d9f..cf2bc3fb 100644 --- a/gi/closure.cpp +++ b/gi/closure.cpp @@ -184,8 +184,6 @@ bool Closure::invoke(JS::HandleObject this_obj, 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 --git a/gi/cwrapper.h b/gi/cwrapper.h index 1167ba97..a176c10f 100644 --- a/gi/cwrapper.h +++ b/gi/cwrapper.h @@ -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 "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -122,7 +122,7 @@ class CWrapperPointerOps { 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 @@ class CWrapper : public CWrapperPointerOps { 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 --git a/gi/function.cpp b/gi/function.cpp index 58aa41d4..5666157d 100644 --- a/gi/function.cpp +++ b/gi/function.cpp @@ -4,10 +4,12 @@ #include +#include // for NULL, size_t #include -#include // for exit +#include #include // for unique_ptr +#include #include #include @@ -47,7 +49,6 @@ #include "gi/object.h" #include "gi/utils-inl.h" #include "cjs/context-private.h" -#include "cjs/context.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -270,13 +271,21 @@ set_return_ffi_arg_from_giargument (GITypeInfo *ret_type, } 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 +298,20 @@ 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", "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 +321,15 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { 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 +369,8 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { 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 +384,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)) { @@ -375,10 +392,8 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { // 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 +404,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) @@ -435,6 +442,7 @@ bool GjsCallbackTrampoline::callback_closure_inner( GITypeTag ret_tag = g_type_info_get_tag(ret_type); bool ret_type_is_void = ret_tag == GI_TYPE_TAG_VOID; + bool in_args_to_cleanup = false; for (int i = 0, n_jsargs = 0; i < n_args; i++) { GIArgInfo arg_info; @@ -456,6 +464,9 @@ bool GjsCallbackTrampoline::callback_closure_inner( if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT) n_outargs++; + if (g_arg_info_get_ownership_transfer(&arg_info) != GI_TRANSFER_NOTHING) + in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER; + param_type = m_param_types[i]; switch (param_type) { @@ -478,6 +489,7 @@ bool GjsCallbackTrampoline::callback_closure_inner( if (!gjs_value_from_explicit_array( context, jsargs[n_jsargs++], &type_info, + g_arg_info_get_ownership_transfer(&arg_info), args[i + c_args_offset], length)) return false; break; @@ -616,6 +628,56 @@ bool GjsCallbackTrampoline::callback_closure_inner( } } + if (!in_args_to_cleanup) + return true; + + for (int i = 0; i < n_args; i++) { + GIArgInfo arg_info; + g_callable_info_load_arg(m_info, i, &arg_info); + GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info); + + if (transfer == GI_TRANSFER_NOTHING) + continue; + + if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN) + continue; + + GIArgument* arg = args[i + c_args_offset]; + if (m_scope == GI_SCOPE_TYPE_CALL) { + GITypeInfo type_info; + g_arg_info_load_type(&arg_info, &type_info); + + if (!gjs_g_argument_release(context, transfer, &type_info, arg)) + return false; + + continue; + } + + struct InvalidateData { + GIArgInfo arg_info; + GIArgument arg; + }; + + auto* data = new InvalidateData({arg_info, *arg}); + g_closure_add_invalidate_notifier( + this, data, [](void* invalidate_data, GClosure* c) { + auto* self = static_cast(c); + std::unique_ptr data( + static_cast(invalidate_data)); + GITransfer transfer = + g_arg_info_get_ownership_transfer(&data->arg_info); + + GITypeInfo type_info; + g_arg_info_load_type(&data->arg_info, &type_info); + if (!gjs_g_argument_release(self->context(), transfer, + &type_info, &data->arg)) { + gjs_throw(self->context(), + "Impossible to release closure argument '%s'", + g_base_info_get_name(&data->arg_info)); + } + }); + } + return true; } @@ -927,6 +989,9 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, 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++) { @@ -970,7 +1035,7 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, } // 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. */ @@ -1268,20 +1333,19 @@ const JSFunctionSpec Function::proto_funcs[] = { 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 --git a/gi/function.h b/gi/function.h index faca609b..38787b04 100644 --- a/gi/function.h +++ b/gi/function.h @@ -79,7 +79,8 @@ struct GjsCallbackTrampoline : public Gjs::Closure { 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 @@ struct GjsCallbackTrampoline : public Gjs::Closure { // 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 @@ class GjsFunctionCallState { 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 @@ class GjsFunctionCallState { 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 @@ class GjsFunctionCallState { 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 --git a/gi/fundamental.cpp b/gi/fundamental.cpp index 7201878b..1a946d23 100644 --- a/gi/fundamental.cpp +++ b/gi/fundamental.cpp @@ -12,6 +12,7 @@ #include #include // for JS_ReportOutOfMemory #include // for WeakCache +#include // for DefaultHasher via WeakCache #include // for GetClass #include #include @@ -26,6 +27,7 @@ #include "gi/function.h" #include "gi/fundamental.h" #include "gi/repo.h" +#include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" @@ -483,11 +485,11 @@ bool FundamentalBase::to_gvalue(JSContext* cx, JS::HandleObject obj, return true; } else if (g_value_type_transformable(instance->gtype(), G_VALUE_TYPE(gvalue))) { - GValue instance_value = {0}; + Gjs::AutoGValue instance_value; g_value_init(&instance_value, instance->gtype()); g_value_set_instance(&instance_value, instance->m_ptr); - g_value_transform(&instance_value, gvalue); - return true; + if (g_value_transform(&instance_value, gvalue)) + return true; } gjs_throw(cx, diff --git a/gi/gerror.cpp b/gi/gerror.cpp index 73c28c93..4411741e 100644 --- a/gi/gerror.cpp +++ b/gi/gerror.cpp @@ -6,8 +6,6 @@ #include -#include - #include #include @@ -532,7 +530,7 @@ GError* gjs_gerror_make_from_thrown_value(JSContext* cx) { * * 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 @@ bool gjs_throw_gerror(JSContext* cx, GError* error) { 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 --git a/gi/gerror.h b/gi/gerror.h index ce89ba0f..db3d7376 100644 --- a/gi/gerror.h +++ b/gi/gerror.h @@ -163,6 +163,6 @@ GError* gjs_gerror_make_from_thrown_value(JSContext* cx); 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 --git a/gi/gobject.cpp b/gi/gobject.cpp index 24324b54..c84f03f8 100644 --- a/gi/gobject.cpp +++ b/gi/gobject.cpp @@ -161,7 +161,7 @@ static GObject* gjs_object_constructor( * 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 --git a/gi/gtype.cpp b/gi/gtype.cpp index cfafe95d..ec4c354c 100644 --- a/gi/gtype.cpp +++ b/gi/gtype.cpp @@ -12,6 +12,7 @@ #include #include #include // for WeakCache +#include // for DefaultHasher via WeakCache #include #include // for JSPROP_PERMANENT #include diff --git a/gi/object.cpp b/gi/object.cpp index 8ab10d14..7008b144 100644 --- a/gi/object.cpp +++ b/gi/object.cpp @@ -58,7 +58,6 @@ #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" -#include "cjs/context.h" #include "cjs/deprecation.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" @@ -165,10 +164,9 @@ bool ObjectInstance::check_gobject_disposed_or_finalized( "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 @@ bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) { 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); @@ -438,8 +437,9 @@ bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, return false; } - return gjs_value_from_g_argument(cx, rval, type, &arg, true); - /* copy_structs is irrelevant because g_field_info_get_field() doesn't + return gjs_value_from_g_argument(cx, rval, type, GJS_ARGUMENT_FIELD, + GI_TRANSFER_EVERYTHING, &arg); + /* transfer is irrelevant because g_field_info_get_field() doesn't * handle boxed types */ } @@ -451,7 +451,8 @@ bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) { 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); @@ -486,8 +487,12 @@ bool ObjectInstance::prop_setter_impl(JSContext* cx, JS::HandleString name, /* 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); @@ -507,7 +512,8 @@ bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { 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); @@ -547,21 +553,17 @@ bool ObjectInstance::field_setter_not_impl(JSContext* cx, } 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; } @@ -2082,7 +2084,7 @@ GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx, } 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 --git a/gi/param.cpp b/gi/param.cpp index 87fde3ec..15e77732 100644 --- a/gi/param.cpp +++ b/gi/param.cpp @@ -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 @@ gjs_typecheck_param(JSContext *context, 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 @@ gjs_typecheck_param(JSContext *context, 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 --git a/gi/repo.cpp b/gi/repo.cpp index a0052004..e346a296 100644 --- a/gi/repo.cpp +++ b/gi/repo.cpp @@ -4,7 +4,6 @@ #include -#include #include // for strlen #if GJS_VERBOSE_ENABLE_GI_USAGE @@ -83,8 +82,6 @@ GJS_JSAPI_RETURN_CONVENTION 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 @@ static bool resolve_namespace_object(JSContext* context, 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 @@ gjs_lookup_namespace_object(JSContext *context, 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 @@ lookup_override_function(JSContext *cx, { 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 @@ lookup_override_function(JSContext *cx, 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 @@ lookup_override_function(JSContext *cx, /* 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 @@ lookup_override_function(JSContext *cx, 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 --git a/gi/value.cpp b/gi/value.cpp index bb91a592..8b6ccfbd 100644 --- a/gi/value.cpp +++ b/gi/value.cpp @@ -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 "cjs/atoms.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" -#include "cjs/context.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/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,31 +89,29 @@ static bool gjs_value_from_g_value_internal(JSContext *context, * 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)); - return gjs_value_from_explicit_array(context, value_p, array_type_info, - &array_arg, array_length.toInt32()); + return gjs_value_from_explicit_array( + context, value_p, array_type_info, + no_copy ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING, &array_arg, + array_length.toInt32()); } // FIXME(3v1n0): Move into closure.cpp one day... @@ -129,7 +121,6 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, JSContext *context; unsigned i; GSignalQuery signal_query = { 0, }; - GISignalInfo *signal_info; gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", this); @@ -142,22 +133,26 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (G_UNLIKELY(gjs->sweeping())) { GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint; - - 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."); + std::ostringstream message; + + 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; } @@ -187,33 +182,31 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, /* 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); - - signal_info = get_signal_info_if_available(&signal_query); + struct ArgumentDetails { + int array_len_index_for = -1; + bool skip = false; + GITypeInfo type_info; + GjsAutoArgInfo arg_info; + }; + std::vector args_details(n_param_values); + + 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); @@ -222,12 +215,12 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, 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; @@ -236,17 +229,19 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, 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); + 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_query, - i); + 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) { @@ -270,7 +265,7 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, // 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()); @@ -549,41 +544,25 @@ gjs_value_to_g_value_internal(JSContext *context, 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; - - 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 { + bool is_array; + if (!JS::IsArrayObject(context, value, &is_array)) + return false; + if (!is_array) return throw_expect_type(context, value, "strv"); - } + + 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; @@ -592,7 +571,7 @@ gjs_value_to_g_value_internal(JSContext *context, 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()) { @@ -620,23 +599,38 @@ gjs_value_to_g_value_internal(JSContext *context, 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)) { - g_value_set_boxed(gvalue, gjs_byte_array_get_byte_array(obj)); + g_value_take_boxed(gvalue, + 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... */ @@ -686,7 +680,7 @@ gjs_value_to_g_value_internal(JSContext *context, 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()) { @@ -755,7 +749,7 @@ gjs_value_to_g_value_internal(JSContext *context, } 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()) @@ -853,14 +847,10 @@ gjs_value_to_g_value_no_copy(JSContext *context, } 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); @@ -869,17 +859,21 @@ gjs_value_from_g_value_internal(JSContext *context, "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); @@ -919,11 +913,9 @@ gjs_value_from_g_value_internal(JSContext *context, 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); @@ -936,34 +928,35 @@ gjs_value_from_g_value_internal(JSContext *context, 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); - - if (!gjs_array_from_g_value_array(context, value_p, element_info, - gvalue)) { + 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; @@ -972,14 +965,7 @@ gjs_value_from_g_value_internal(JSContext *context, 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) @@ -989,7 +975,7 @@ gjs_value_from_g_value_internal(JSContext *context, } /* 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) @@ -999,7 +985,7 @@ gjs_value_from_g_value_internal(JSContext *context, } /* 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)); } @@ -1048,33 +1034,22 @@ gjs_value_from_g_value_internal(JSContext *context, 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); @@ -1090,13 +1065,7 @@ gjs_value_from_g_value_internal(JSContext *context, 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; @@ -1139,5 +1108,5 @@ gjs_value_from_g_value(JSContext *context, 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 --git a/gi/value.h b/gi/value.h index de9677d9..352edef7 100644 --- a/gi/value.h +++ b/gi/value.h @@ -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 --git a/gi/wrapperutils.h b/gi/wrapperutils.h index 6c06d86a..e0450dc9 100644 --- a/gi/wrapperutils.h +++ b/gi/wrapperutils.h @@ -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 @@ class GIWrapperBase : public CWrapperPointerOps { 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 --git a/gjs.doap b/gjs.doap index 32b10286..6770e970 100644 --- a/gjs.doap +++ b/gjs.doap @@ -20,7 +20,7 @@ - + C++ @@ -45,7 +45,7 @@ Giovanni Campagna - gcampagna + diff --git a/installed-tests/extra/lsan.supp b/installed-tests/extra/lsan.supp index 91cadbb3..b792a49f 100644 --- a/installed-tests/extra/lsan.supp +++ b/installed-tests/extra/lsan.supp @@ -10,3 +10,10 @@ leak:libfontconfig.so.1 # https://bugzilla.mozilla.org/show_bug.cgi?id=1478679 leak:js::coverage::LCovSource::writeScript leak:js/src/util/Text.cpp + +# 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 --git a/installed-tests/js/libgjstesttools/meson.build b/installed-tests/js/libgjstesttools/meson.build index e391844a..2e57483a 100644 --- a/installed-tests/js/libgjstesttools/meson.build +++ b/installed-tests/js/libgjstesttools/meson.build @@ -13,7 +13,7 @@ libgjstesttools = library('gjstesttools', gjstest_tools_gir = gnome.generate_gir(libgjstesttools, includes: ['GObject-2.0', 'Gio-2.0'], sources: gjstest_tools_sources, namespace: 'GjsTestTools', nsversion: '1.0', - symbol_prefix: 'gjs_test_tools_', fatal_warnings: get_option('werror'), + symbol_prefix: 'gjs_test_tools_', extra_args: '--warn-error', install: get_option('installed_tests'), install_dir_gir: false, install_dir_typelib: installed_tests_execdir) gjstest_tools_typelib = gjstest_tools_gir[1] diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build index 00c5a8a4..6228fedd 100644 --- a/installed-tests/js/meson.build +++ b/installed-tests/js/meson.build @@ -73,10 +73,9 @@ libregress = library('regress', regress_sources, regress_gir = gnome.generate_gir(libregress, includes: regress_gir_includes, sources: regress_sources, namespace: 'Regress', nsversion: '1.0', identifier_prefix: 'Regress', symbol_prefix: 'regress_', - extra_args: ['--warn-all'] + regress_gir_c_args, + extra_args: ['--warn-all', '--warn-error'] + regress_gir_c_args, install: get_option('installed_tests'), install_dir_gir: false, - install_dir_typelib: installed_tests_execdir, - fatal_warnings: get_option('werror')) + install_dir_typelib: installed_tests_execdir) regress_typelib = regress_gir[1] if not skip_warnlib @@ -105,7 +104,7 @@ libgimarshallingtests = library('gimarshallingtests', gimarshallingtests_gir = gnome.generate_gir(libgimarshallingtests, includes: ['Gio-2.0'], sources: gimarshallingtests_sources, namespace: 'GIMarshallingTests', nsversion: '1.0', - symbol_prefix: 'gi_marshalling_tests_', fatal_warnings: get_option('werror'), + symbol_prefix: 'gi_marshalling_tests_', extra_args: '--warn-error', install: get_option('installed_tests'), install_dir_gir: false, install_dir_typelib: installed_tests_execdir) gimarshallingtests_typelib = gimarshallingtests_gir[1] @@ -165,6 +164,7 @@ gschemas_compiled = gnome.compile_schemas( tests_dependencies = [ gschemas_compiled, + gjs_private_typelib, gjstest_tools_typelib, gimarshallingtests_typelib, regress_typelib, @@ -213,30 +213,30 @@ if not get_option('skip_gtk_tests') endif endif - bus_config = files('../../test/test-bus.conf') +bus_config = files('../../test/test-bus.conf') foreach test : dbus_tests test_file = files('test@0@.js'.format(test)) 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') -endif + args: ['--config-file', bus_config, '--', minijasmine, test_file], + env: tests_environment, protocol: 'tap', suite: 'dbus') + endif dbus_test_description_subst = { 'name': 'test@0@.js'.format(test), - 'installed_tests_execdir': installed_tests_execdir, -} + 'installed_tests_execdir': installed_tests_execdir, + } dbus_test_description = configure_file( configuration: dbus_test_description_subst, input: '../minijasmine.test.in', output: 'test@0@.test'.format(test), - install: get_option('installed_tests'), - install_dir: installed_tests_metadir) + install: get_option('installed_tests'), + install_dir: installed_tests_metadir) -if get_option('installed_tests') + if get_option('installed_tests') install_data(test_file, install_dir: installed_js_tests_dir) -endif + endif endforeach # tests using ES modules are also separate because they need an extra diff --git a/installed-tests/js/minijasmine-executor.js b/installed-tests/js/minijasmine-executor.js index 3fe8b782..1f1bb222 100644 --- a/installed-tests/js/minijasmine-executor.js +++ b/installed-tests/js/minijasmine-executor.js @@ -7,12 +7,11 @@ 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. - */ +// 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 +26,9 @@ GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { 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 --git a/installed-tests/js/minijasmine.js b/installed-tests/js/minijasmine.js index 01c6c272..730aa212 100644 --- a/installed-tests/js/minijasmine.js +++ b/installed-tests/js/minijasmine.js @@ -1,4 +1,4 @@ -#!/usr/bin/env cjs +#!/usr/bin/env -S cjs -m // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento @@ -122,10 +122,11 @@ if (valgrind || (gcZeal && (gcZeal === '2' || gcZeal.startsWith('2,') || gcZeal 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 --git a/installed-tests/js/org.cinnamon.CjsTest.gschema.xml b/installed-tests/js/org.cinnamon.CjsTest.gschema.xml index be229dd9..5205f324 100644 --- a/installed-tests/js/org.cinnamon.CjsTest.gschema.xml +++ b/installed-tests/js/org.cinnamon.CjsTest.gschema.xml @@ -2,7 +2,7 @@ - + (-1, -1) diff --git a/installed-tests/js/org.gnome.GjsTest.gschema.xml b/installed-tests/js/org.gnome.GjsTest.gschema.xml deleted file mode 100644 index 07bcd487..00000000 --- a/installed-tests/js/org.gnome.GjsTest.gschema.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - (-1, -1) - - - false - - - false - - - - - - - 10 - - - diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js index 1df1aadb..a6c5321a 100644 --- a/installed-tests/js/testGIMarshalling.js +++ b/installed-tests/js/testGIMarshalling.js @@ -559,6 +559,9 @@ describe('Zero-terminated C array', function () { ['none', 'container', 'full'].forEach(transfer => { it(`marshals as a transfer-${transfer} in and out parameter`, function () { + if (transfer === 'full') + pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/399'); + const returnedArray = GIMarshallingTests[`array_gvariant_${transfer}_in`](variantArray); expect(returnedArray.map(v => v.deepUnpack())).toEqual([27, 'Hello']); @@ -895,6 +898,13 @@ describe('GValue', function () { .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]); @@ -2074,7 +2084,7 @@ describe('GObject properties', function () { }); }); -xdescribe('GObject signals', function () { +describe('GObject signals', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.SignalsObject(); @@ -2085,15 +2095,13 @@ xdescribe('GObject signals', function () { 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); }); } @@ -2103,4 +2111,13 @@ xdescribe('GObject signals', function () { 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 --git a/installed-tests/js/testGLib.js b/installed-tests/js/testGLib.js index 4e6ee4a9..4d048f95 100644 --- a/installed-tests/js/testGLib.js +++ b/installed-tests/js/testGLib.js @@ -92,6 +92,25 @@ describe('GVariant unpack', function () { }); }); +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 () { diff --git a/installed-tests/js/testGObjectDestructionAccess.js b/installed-tests/js/testGObjectDestructionAccess.js index 889cebd7..32f5b0df 100644 --- a/installed-tests/js/testGObjectDestructionAccess.js +++ b/installed-tests/js/testGObjectDestructionAccess.js @@ -381,9 +381,7 @@ describe('Disposed or finalized GObject', function () { 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 @@ describe('GObject with toggle references', function () { '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 @@ describe('GObject with toggle references', function () { GjsTestTools.clear_saved(); System.gc(); expect(GjsTestTools.get_weak()).toBeNull(); - }); + }).pend('Flaky, see https://gitlab.gnome.org/GNOME/gjs/-/issues/NNN'); }); diff --git a/installed-tests/js/testGio.js b/installed-tests/js/testGio.js index 08de27fe..e70a95d6 100644 --- a/installed-tests/js/testGio.js +++ b/installed-tests/js/testGio.js @@ -110,24 +110,36 @@ describe('Gio.Settings overrides', function () { }); it("doesn't crash when forgetting to specify a schema path", function () { - expect(() => new Gio.Settings({schema: 'org.gnome.GjsTest.Sub'})) + expect(() => new Gio.Settings({schema: 'org.cinnamon.CjsTest.Sub'})) .toThrowError(/schema/); }); it("doesn't crash when specifying conflicting schema paths", function () { expect(() => new Gio.Settings({ - schema: 'org.gnome.GjsTest', + schema: 'org.cinnamon.CjsTest', path: '/conflicting/path/', })).toThrowError(/schema/); }); + it('can construct with a settings schema object', function () { + const source = Gio.SettingsSchemaSource.get_default(); + const settingsSchema = source.lookup('org.cinnamon.CjsTest', 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']; let settings; beforeEach(function () { - settings = new Gio.Settings({schema: 'org.gnome.GjsTest'}); + settings = new Gio.Settings({schema: 'org.cinnamon.CjsTest'}); }); it("doesn't crash when resetting a nonexistent key", function () { diff --git a/installed-tests/js/testGtk3.js b/installed-tests/js/testGtk3.js index 9f4af90b..f409f255 100644 --- a/installed-tests/js/testGtk3.js +++ b/installed-tests/js/testGtk3.js @@ -177,9 +177,7 @@ describe('Gtk overrides', function () { 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 @@ describe('Gtk overrides', function () { 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 --git a/installed-tests/js/testIntrospection.js b/installed-tests/js/testIntrospection.js index bdd4ddd4..aa0d2552 100644 --- a/installed-tests/js/testIntrospection.js +++ b/installed-tests/js/testIntrospection.js @@ -158,7 +158,7 @@ describe('Garbage collection of introspected objects', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*property screenfull*'); - const settings = new Gio.Settings({schema: 'org.gnome.GjsTest'}); + const settings = new Gio.Settings({schema: 'org.cinnamon.CjsTest'}); let obj = new SomeObject(); settings.bind('fullscreen', obj, 'screenfull', Gio.SettingsBindFlags.DEFAULT); const handler = settings.connect('changed::fullscreen', () => { diff --git a/installed-tests/js/testPrint.js b/installed-tests/js/testPrint.js index 5432c8c3..e84e3f71 100644 --- a/installed-tests/js/testPrint.js +++ b/installed-tests/js/testPrint.js @@ -194,4 +194,12 @@ describe('prettyPrint', function () { 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 --git a/installed-tests/js/testRegress.js b/installed-tests/js/testRegress.js index 7e4f8bb8..813ab538 100644 --- a/installed-tests/js/testRegress.js +++ b/installed-tests/js/testRegress.js @@ -1218,6 +1218,15 @@ describe('Life, the Universe and Everything', function () { 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 @@ describe('Life, the Universe and Everything', function () { 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 @@ describe('Life, the Universe and Everything', function () { 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 @@ describe('Life, the Universe and Everything', function () { }); 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 --git a/installed-tests/meson.build b/installed-tests/meson.build index ee5039eb..d270cb4a 100644 --- a/installed-tests/meson.build +++ b/installed-tests/meson.build @@ -10,6 +10,10 @@ installed_tests_metadir = abs_datadir / 'installed-tests' / meson.project_name() # 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 @@ foreach test : simple_tests 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 @@ foreach test : debugger_tests 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 --git a/installed-tests/scripts/testCommandLine.sh b/installed-tests/scripts/testCommandLine.sh index 86f5a0d0..b3796f4e 100755 --- a/installed-tests/scripts/testCommandLine.sh +++ b/installed-tests/scripts/testCommandLine.sh @@ -148,14 +148,14 @@ report_xfail "Invalid option should exit with failure" $gjs --invalid-option 2>&1 | grep -q invalid-option report "Invalid option should print a relevant message" -# Test that System.exit() works in gjs-console +# Test that System.exit() works in cjs-console $gjs -c 'imports.system.exit(0)' report "System.exit(0) should exit successfully" $gjs -c 'imports.system.exit(42)' test $? -eq 42 report "System.exit(42) should exit with the correct exit code" -# Test the System.programPath works in gjs-console +# Test the System.programPath works in cjs-console $gjs argv.js report "System.programPath should end in '/argv.js' when gjs argv.js is run" @@ -327,7 +327,8 @@ report "avoid statically importing two versions of the same module" # 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 --git a/meson.build b/meson.build index 6ea15133..18e0b1cc 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan -project('cjs', 'cpp', 'c', version: '6.0.0', license: ['MIT', 'LGPL2+'], +project('cjs', 'cpp', 'c', version: '6.1.0', license: ['MIT', 'LGPL2+'], meson_version: '>=0.56.0', default_options: ['cpp_std=c++17', 'cpp_rtti=false', 'c_std=c99', 'warning_level=2', 'b_pch=true' ]) @@ -39,7 +39,7 @@ if cc.get_id() == 'msvc' 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 @@ gio = dependency('gio-2.0', version: glib_required_version, 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... @@ -330,6 +330,9 @@ header_conf.set('HAVE_DTRACE', get_option('dtrace'), description: 'Using dtrace probes') header_conf.set('HAVE_PRINTF_ALTERNATIVE_INT', have_printf_alternative_int, description: 'printf() accepts "%Id" for alternative integer output') +header_conf.set('HAVE_OPEN_MEMSTREAM', + cxx.has_function('open_memstream', prefix : '#include '), + description: 'open_memstream() is available') if build_readline header_conf.set('HAVE_READLINE_READLINE_H', cxx.check_header('readline/readline.h', prefix: '#include ', @@ -587,7 +590,7 @@ libgjs_dep = declare_dependency(link_with: [libgjs, libgjs_jsapi], gjs_private_gir = gnome.generate_gir(libgjs, includes: ['GObject-2.0', 'Gio-2.0'], sources: libgjs_private_sources, namespace: 'CjsPrivate', nsversion: '1.0', identifier_prefix: 'Gjs', - symbol_prefix: 'gjs_', fatal_warnings: get_option('werror'), install: true, + symbol_prefix: 'gjs_', extra_args: '--warn-error', install: true, install_dir_gir: false, install_dir_typelib: pkglibdir / 'girepository-1.0') gjs_private_typelib = gjs_private_gir[1] @@ -634,7 +637,7 @@ js_tests_builddir = meson.current_build_dir() / 'installed-tests' / 'js' libgjs_test_tools_builddir = js_tests_builddir / 'libgjstesttools' # GJS_PATH is empty here since we want to force the use of our own # resources. G_FILENAME_ENCODING ensures filenames are not UTF-8 -tests_environment.set('TOP_BUILDDIR', meson.project_build_root()) +tests_environment.set('TOP_BUILDDIR', meson.build_root()) tests_environment.set('GJS_USE_UNINSTALLED_FILES', '1') tests_environment.set('GJS_PATH', '') tests_environment.set('GJS_DEBUG_OUTPUT', 'stderr') @@ -653,6 +656,7 @@ tests_environment.set('LSAN_OPTIONS', 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') @@ -809,3 +813,10 @@ summary({ '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 --git a/modules/console.cpp b/modules/console.cpp index 3fec8df5..f13a2b87 100644 --- a/modules/console.cpp +++ b/modules/console.cpp @@ -34,6 +34,7 @@ #include #include #include +#include // for CurrentGlobalOrNull #include #include #include @@ -224,7 +225,7 @@ gjs_console_interact(JSContext *context, { 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() @@ -300,7 +301,7 @@ gjs_console_interact(JSContext *context, if (!ok) { /* If this was an uncatchable exception, throw another uncatchable * exception on up to the surrounding JS::Evaluate() in main(). This - * happens when you run gjs-console and type imports.system.exit(0); + * happens when you run cjs-console and type imports.system.exit(0); * at the prompt. If we don't throw another uncatchable exception * here, then it's swallowed and main() won't exit. */ return false; diff --git a/modules/core/overrides/Gio.js b/modules/core/overrides/Gio.js index a3122dd0..836d14aa 100644 --- a/modules/core/overrides/Gio.js +++ b/modules/core/overrides/Gio.js @@ -743,6 +743,8 @@ function _init() { 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 --git a/modules/internal/loader.js b/modules/internal/loader.js index 2f3f71d8..5a5c065b 100644 --- a/modules/internal/loader.js +++ b/modules/internal/loader.js @@ -329,37 +329,32 @@ class ModuleLoader extends InternalModuleLoader { * 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 --git a/modules/script/_bootstrap/default.js b/modules/script/_bootstrap/default.js index 59c24930..df805401 100644 --- a/modules/script/_bootstrap/default.js +++ b/modules/script/_bootstrap/default.js @@ -26,6 +26,9 @@ function prettyPrint(value) { switch (typeof value) { case 'object': + if (value === null) + return 'null'; + if (value.toString === Object.prototype.toString || value.toString === Array.prototype.toString || value.toString === Date.prototype.toString) { @@ -42,6 +45,8 @@ return JSON.stringify(value); case 'symbol': return formatSymbol(value); + case 'undefined': + return 'undefined'; default: return value.toString(); } diff --git a/test/check-pch.sh b/test/check-pch.sh index 777074c4..8a83fc2c 100755 --- a/test/check-pch.sh +++ b/test/check-pch.sh @@ -14,7 +14,7 @@ if [ -n "$SELFTEST" ]; then local code_path="$(mktemp -t -d "check-pch-XXXXXX")" test_paths+=("$code_path") cd "$code_path" - mkdir gjs gi + mkdir cjs gi echo "#include " >> cjs/gjs_pch.hh } @@ -117,7 +117,7 @@ fi PCH_FILES=(cjs/gjs_pch.hh) IGNORE_COMMENT="check-pch: ignore" -CODE_PATHS=(gjs gi) +CODE_PATHS=(cjs gi) INCLUDED_FILES=( \*.c \*.cpp diff --git a/test/extra/Dockerfile b/test/extra/Dockerfile index 2a59fc52..82ae8d4d 100644 --- a/test/extra/Dockerfile +++ b/test/extra/Dockerfile @@ -3,14 +3,14 @@ # === Build Spidermonkey stage === -FROM registry.fedoraproject.org/fedora:36 AS mozjs-build -ARG MOZJS_BRANCH=mozjs91 +FROM registry.fedoraproject.org/fedora:38 AS mozjs-build +ARG MOZJS_BRANCH=mozjs102 ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} ARG BUILD_OPTS= ENV SHELL=/bin/bash -# mozjs91 cannot be built with python3.10 +# mozjs102 cannot be built with python3.11 and possibly 3.10 RUN dnf -y install 'dnf-command(builddep)' \ autoconf213 \ clang \ @@ -40,7 +40,7 @@ RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs # === Actual Docker image === -FROM registry.fedoraproject.org/fedora:36 +FROM registry.fedoraproject.org/fedora:38 ARG LOCALES=tr_TR ENV SHELL=/bin/bash diff --git a/test/extra/Dockerfile.debug b/test/extra/Dockerfile.debug index fc7b7e3a..302a4d15 100644 --- a/test/extra/Dockerfile.debug +++ b/test/extra/Dockerfile.debug @@ -3,14 +3,14 @@ # === Build stage === -FROM registry.fedoraproject.org/fedora:36 AS build -ARG MOZJS_BRANCH=mozjs91 +FROM registry.fedoraproject.org/fedora:38 AS build +ARG MOZJS_BRANCH=mozjs102 ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} ARG BUILD_OPTS= ENV SHELL=/bin/bash -# mozjs91 cannot be built with python3.10 +# mozjs102 cannot be built with python3.11 and possibly 3.10 RUN dnf -y install 'dnf-command(builddep)' \ autoconf213 \ clang \ @@ -30,9 +30,9 @@ RUN dnf -y builddep ${MOZJS_BUILDDEPS} WORKDIR /root RUN mkdir -p include-what-you-use/_build -ADD https://include-what-you-use.org/downloads/include-what-you-use-0.18.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.18.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 make -j$(nproc) 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:36 +FROM registry.fedoraproject.org/fedora:38 ARG LOCALES=tr_TR ENV SHELL=/bin/bash @@ -116,6 +126,7 @@ RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ 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 --git a/test/gjs-test-call-args.cpp b/test/gjs-test-call-args.cpp index 32380574..fb625e57 100644 --- a/test/gjs-test-call-args.cpp +++ b/test/gjs-test-call-args.cpp @@ -11,6 +11,7 @@ #include #include #include +#include // for CurrentGlobalOrNull #include #include #include @@ -299,7 +300,7 @@ setup(GjsUnitTestFixture *fx, { 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 --git a/test/gjs-test-coverage.cpp b/test/gjs-test-coverage.cpp index 2fea8a4b..95369b83 100644 --- a/test/gjs-test-coverage.cpp +++ b/test/gjs-test-coverage.cpp @@ -36,7 +36,7 @@ static void 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 --git a/test/gjs-test-jsapi-utils.cpp b/test/gjs-test-jsapi-utils.cpp index 2865a729..5f319e19 100644 --- a/test/gjs-test-jsapi-utils.cpp +++ b/test/gjs-test-jsapi-utils.cpp @@ -146,6 +146,22 @@ static void test_gjs_autopointer_dtor_cpp_array() { 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 @@ static void test_gjs_autotypeclass_init() { 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 @@ void gjs_test_add_tests_for_jsapi_utils(void) { 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 --git a/test/gjs-test-toggle-queue.cpp b/test/gjs-test-toggle-queue.cpp index 410a86ef..5ac8e682 100644 --- a/test/gjs-test-toggle-queue.cpp +++ b/test/gjs-test-toggle-queue.cpp @@ -82,7 +82,7 @@ static void setup(GjsUnitTestFixture* fx, const void*) { 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 @@ static void test_toggle_queue_object_from_main_thread_already_enqueued( 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 @@ static void test_toggle_queue_object_from_main_thread_unref_already_enqueued( 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 @@ static void test_toggle_queue_object_from_other_thread_ref_unref( 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 @@ static void test_toggle_queue_object_handle_up(GjsUnitTestFixture* fx, 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 @@ static void test_toggle_queue_object_handle_up_down(GjsUnitTestFixture* fx, 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 @@ static void test_toggle_queue_object_handle_up_down_delayed( 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 @@ static void test_toggle_queue_object_handle_up_down_delayed( 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 @@ static void test_toggle_queue_object_handle_up_down_on_gc( 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 @@ static void test_toggle_queue_object_handle_many_up(GjsUnitTestFixture* fx, 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 @@ static void test_toggle_queue_object_handle_many_up_and_down( // 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 --git a/test/gjs-test-utils.cpp b/test/gjs-test-utils.cpp index c3921758..3c14821b 100644 --- a/test/gjs-test-utils.cpp +++ b/test/gjs-test-utils.cpp @@ -8,10 +8,11 @@ #include #include +#include // for JS_GetContextPrivate #include -#include #include +#include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "test/gjs-test-common.h" @@ -21,8 +22,8 @@ void gjs_unit_test_fixture_setup(GjsUnitTestFixture* fx, const void*) { 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 --git a/test/gjs-test-utils.h b/test/gjs-test-utils.h index 68b7ba83..a751cda0 100644 --- a/test/gjs-test-utils.h +++ b/test/gjs-test-utils.h @@ -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 "cjs/context.h" diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp index 3796e797..962bd8b2 100644 --- a/test/gjs-tests.cpp +++ b/test/gjs-tests.cpp @@ -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 @@ gjstest_test_func_gjs_context_construct_eval(void) { 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 @@ gjstest_test_func_gjs_context_construct_eval(void) 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() { 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_relative() { 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 @@ static void gjstest_test_func_gjs_context_eval_dynamic_import_bad() { 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 @@ static void 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 @@ gjstest_test_func_gjs_context_exit(void) 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 @@ gjstest_test_func_gjs_context_exit(void) 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() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_file_throw() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_file_exit() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_file_exit() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_file_fail_instantiate() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_war 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 @@ gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_no_warning() { 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 @@ static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_throw() { 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 @@ static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_no_throw() 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() { 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 @@ static void gjstest_test_func_gjs_context_register_module_eval_module_file() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_unregistered() { 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 @@ static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw 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; - g_clear_error(&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(); + + 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 @@ static void 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 @@ gjstest_test_func_gjs_gobject_js_defined_type(void) 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_gobject_without_introspection(void) { 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 @@ static void gjstest_test_func_gjs_context_eval_exit_code_omitted_throw() { 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 @@ static void gjstest_test_func_gjs_context_eval_exit_code_omitted_no_throw() { 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 @@ gjstest_test_profiler_start_stop(void) 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()" @@ -1012,14 +1129,13 @@ static void gjstest_test_args_rounded_values() { static void gjstest_test_func_gjs_context_argv_array() { GjsAutoUnref gjs = gjs_context_new(); - GError* error = NULL; + GjsAutoError error; int status; const char* argv[1] = {"test"}; bool ok = gjs_context_define_string_array(gjs, "ARGV", 1, argv, &error); g_assert_no_error(error); - g_clear_error(&error); g_assert_true(ok); ok = gjs_context_eval(gjs, R"js( @@ -1091,6 +1207,14 @@ main(int argc, 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 @@ main(int argc, 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 --git a/test/test-ci.sh b/test/test-ci.sh index 0905340f..ee7c9551 100755 --- a/test/test-ci.sh +++ b/test/test-ci.sh @@ -10,7 +10,8 @@ do_Set_Env () { #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 @@ do_Set_Env () { 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}" } @@ -48,7 +60,7 @@ do_Get_Upstream_Base () { git fetch --depth=30 --no-tags upstream "$base_branch" fi - git branch ci-upstream-base-branch FETCH_HEAD + git branch -f ci-upstream-base-branch FETCH_HEAD # Work out the newest common ancestor between the detached HEAD that this CI # job has checked out, and the upstream target branch (which will typically @@ -139,7 +151,7 @@ elif test "$1" = "BUILD"; then 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 --git a/tools/process_iwyu.py b/tools/process_iwyu.py index 718f7b16..9f44ec0e 100755 --- a/tools/process_iwyu.py +++ b/tools/process_iwyu.py @@ -73,16 +73,10 @@ class Colors: ('gi/function.cpp', '#include ', 'for max'), ('gi/function.cpp', '#include ', 'for fill_n, max'), # also! ('gi/private.cpp', '#include ', 'for max'), - ('gjs/context.cpp', '#include ', 'for copy, max, find'), ('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'), @@ -92,12 +86,12 @@ class Colors: # For some reason IWYU wants these with angle brackets when they are # already present with quotes # https://github.com/include-what-you-use/include-what-you-use/issues/1087 - ('gjs/context.cpp', '#include ', ''), - ('gjs/coverage.cpp', '#include ', ''), - ('gjs/error-types.cpp', '#include ', ''), - ('gjs/jsapi-util.cpp', '#include ', ''), - ('gjs/mem.cpp', '#include ', ''), - ('gjs/profiler.cpp', '#include ', ''), + ('gjs/context.cpp', '#include ', ''), + ('gjs/coverage.cpp', '#include ', ''), + ('gjs/error-types.cpp', '#include ', ''), + ('gjs/jsapi-util.cpp', '#include ', ''), + ('gjs/mem.cpp', '#include ', ''), + ('gjs/profiler.cpp', '#include ', ''), ) diff --git a/tools/run_coverage.sh b/tools/run_coverage.sh index f1c8e5ac..16bc7272 100755 --- a/tools/run_coverage.sh +++ b/tools/run_coverage.sh @@ -11,7 +11,7 @@ GENHTML_ARGS='--legend --show-details --branch-coverage' 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 --git a/tools/run_iwyu.sh b/tools/run_iwyu.sh index 2b7b87be..6ff7e971 100755 --- a/tools/run_iwyu.sh +++ b/tools/run_iwyu.sh @@ -47,7 +47,7 @@ IWYU="python3 $(which iwyu_tool || which iwyu-tool || which iwyu_tool.py) -p ." 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" diff --git a/tools/yarn.lock b/tools/yarn.lock deleted file mode 100644 index 0741fc67..00000000 --- a/tools/yarn.lock +++ /dev/null @@ -1,649 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@es-joy/jsdoccomment@~0.20.1": - version "0.20.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.20.1.tgz#fe89f435f045ae5aaf89c7a4df3616c03e9d106e" - integrity sha512-oeJK41dcdqkvdZy/HctKklJNkt/jh+av3PZARrZEl+fs/8HaHeeYoAvEwOV0u5I6bArTF17JEsTZMY359e/nfQ== - dependencies: - comment-parser "1.3.0" - esquery "^1.4.0" - jsdoc-type-pratt-parser "~2.2.3" - -"@eslint/eslintrc@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" - integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@humanwhocodes/config-array@^0.9.2": - version "0.9.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" - integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -acorn-jsx@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -comment-parser@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.0.tgz#68beb7dbe0849295309b376406730cd16c719c44" - integrity sha512-hRpmWIKgzd81vn0ydoWoyPoALEOnF4wt8yKD35Ib1D6XC2siLiYaiqfGkYrunuKdsXGwpBpHU3+9r+RVw2NZfA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -debug@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-jsdoc@^37.9.6: - version "37.9.6" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.9.6.tgz#4ac0b6f171d818856a6e52987413f54d66a50800" - integrity sha512-GDCB0nEwKVaeIzam+t/yB8XG/6tgvc9XgrSwuxqCXVlKRWUqTuTqntZoqAKZAIbWIgYsrnrvrWAyIX/QvhwBcw== - dependencies: - "@es-joy/jsdoccomment" "~0.20.1" - comment-parser "1.3.0" - debug "^4.3.3" - escape-string-regexp "^4.0.0" - esquery "^1.4.0" - regextras "^0.8.0" - semver "^7.3.5" - spdx-expression-parse "^3.0.1" - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" - integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== - dependencies: - "@eslint/eslintrc" "^1.2.0" - "@humanwhocodes/config-array" "^0.9.2" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.6.0" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== - dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.3.0" - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" - integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -glob-parent@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.1.tgz#42054f685eb6a44e7a7d189a96efa40a54971aa7" - integrity sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^13.6.0, globals@^13.9.0: - version "13.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" - integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== - dependencies: - type-fest "^0.20.2" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e" - integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^4.0.0, is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsdoc-type-pratt-parser@~2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.3.tgz#3910cd0120054a512a73942d644ef1eb5a9df6e9" - integrity sha512-QPyxq62Q8veBSDtDrWmqaEPjSCeknUV9dH/OAGt3q9an8qC8UQDqitQiw1NvoMskIESpoRZ6qzt4H3rlK0xo8A== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -regextras@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.8.0.tgz#ec0f99853d4912839321172f608b544814b02217" - integrity sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" - integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -uri-js@^4.2.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" - integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== - dependencies: - punycode "^2.1.0" - -v8-compile-cache@^2.0.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" - integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==