diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 372ae4593..000000000 --- a/.eslintignore +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Claudio André - -installed-tests/js/jasmine.js -installed-tests/js/modules/badOverrides/WarnLib.js -installed-tests/js/modules/subBadInit/__init__.js -modules/script/jsUnit.js -/_build -/builddir diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index aabe6e193..000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,275 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Claudio André -env: - es2021: true -extends: 'eslint:recommended' -plugins: - - jsdoc -rules: - array-bracket-newline: - - error - - consistent - array-bracket-spacing: - - error - - never - array-callback-return: error - arrow-parens: - - error - - as-needed - arrow-spacing: error - block-scoped-var: error - block-spacing: error - brace-style: error - # Waiting for this to have matured a bit in eslint - # camelcase: - # - error - # - properties: never - # allow: [^vfunc_, ^on_, _instance_init] - comma-dangle: - - error - - arrays: always-multiline - objects: always-multiline - imports: always-multiline - functions: never - comma-spacing: - - error - - before: false - after: true - comma-style: - - error - - last - computed-property-spacing: error - curly: - - error - - multi-or-nest - - consistent - dot-location: - - error - - property - eol-last: error - eqeqeq: error - func-call-spacing: error - func-name-matching: error - func-style: - - error - - declaration - - allowArrowFunctions: true - indent: - - error - - 4 - - ignoredNodes: - # Allow not indenting the body of GObject.registerClass, since in the - # future it's intended to be a decorator - - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child' - # Allow dedenting chained member expressions - MemberExpression: 'off' - jsdoc/check-alignment: error - jsdoc/check-param-names: error - jsdoc/check-tag-names: error - jsdoc/check-types: error - jsdoc/implements-on-classes: error - jsdoc/require-jsdoc: error - jsdoc/require-param: error - jsdoc/require-param-description: error - jsdoc/require-param-name: error - jsdoc/require-param-type: error - jsdoc/tag-lines: - - error - - always - - count: 0 - startLines: 1 - key-spacing: - - error - - beforeColon: false - afterColon: true - keyword-spacing: - - error - - before: true - after: true - linebreak-style: - - error - - unix - lines-between-class-members: - - error - - always - - exceptAfterSingleLine: true - max-nested-callbacks: error - max-statements-per-line: error - new-parens: error - no-array-constructor: error - no-await-in-loop: error - no-caller: error - no-constant-condition: - - error - - checkLoops: false - no-div-regex: error - no-empty: - - error - - allowEmptyCatch: true - no-extra-bind: error - no-extra-parens: - - error - - all - - conditionalAssign: false - nestedBinaryExpressions: false - returnAssign: false - no-implicit-coercion: - - error - - allow: - - '!!' - no-invalid-this: error - no-iterator: error - no-label-var: error - no-lonely-if: error - no-loop-func: error - no-nested-ternary: error - no-new-object: error - no-new-wrappers: error - no-octal-escape: error - no-proto: error - no-prototype-builtins: 'off' - no-restricted-globals: [error, window] - no-restricted-properties: - - error - - object: imports - property: format - message: Use template strings - - object: pkg - property: initFormat - message: Use template strings - - object: Lang - property: copyProperties - message: Use Object.assign() - - object: Lang - property: bind - message: Use arrow notation or Function.prototype.bind() - - object: Lang - property: Class - message: Use ES6 classes - no-restricted-syntax: - - error - - selector: >- - MethodDefinition[key.name="_init"] > - FunctionExpression[params.length=1] > - BlockStatement[body.length=1] - CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] > - Identifier:first-child - message: _init() that only calls super._init() is unnecessary - - selector: >- - MethodDefinition[key.name="_init"] > - FunctionExpression[params.length=0] > - BlockStatement[body.length=1] - CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"] - message: _init() that only calls super._init() is unnecessary - - selector: BinaryExpression[operator="instanceof"][right.name="Array"] - message: Use Array.isArray() - no-return-assign: error - no-return-await: error - no-self-compare: error - no-shadow: error - no-shadow-restricted-names: error - no-spaced-func: error - no-tabs: error - no-template-curly-in-string: error - no-throw-literal: error - no-trailing-spaces: error - no-undef-init: error - no-unneeded-ternary: error - no-unused-expressions: error - no-unused-vars: - - error - # Vars use a suffix _ instead of a prefix because of file-scope private vars - - varsIgnorePattern: (^unused|_$) - argsIgnorePattern: ^(unused|_) - no-useless-call: error - no-useless-computed-key: error - no-useless-concat: error - no-useless-constructor: error - no-useless-rename: error - no-useless-return: error - no-whitespace-before-property: error - no-with: error - nonblock-statement-body-position: - - error - - below - object-curly-newline: - - error - - consistent: true - multiline: true - object-curly-spacing: error - object-shorthand: error - operator-assignment: error - operator-linebreak: error - padded-blocks: - - error - - never - # These may be a bit controversial, we can try them out and enable them later - # prefer-const: error - # prefer-destructuring: error - prefer-numeric-literals: error - prefer-promise-reject-errors: error - prefer-rest-params: error - prefer-spread: error - prefer-template: error - quotes: - - error - - single - - avoidEscape: true - require-await: error - rest-spread-spacing: error - semi: - - error - - always - semi-spacing: - - error - - before: false - after: true - semi-style: error - space-before-blocks: error - space-before-function-paren: - - error - - named: never - # for `function ()` and `async () =>`, preserve space around keywords - anonymous: always - asyncArrow: always - space-in-parens: error - space-infix-ops: - - error - - int32Hint: false - space-unary-ops: error - spaced-comment: error - switch-colon-spacing: error - symbol-description: error - template-curly-spacing: error - template-tag-spacing: error - unicode-bom: error - wrap-iife: - - error - - inside - yield-star-spacing: error - yoda: error -settings: - jsdoc: - mode: typescript -globals: - ARGV: readonly - Debugger: readonly - GIRepositoryGType: readonly - globalThis: readonly - imports: readonly - Intl: readonly - log: readonly - logError: readonly - print: readonly - printerr: readonly - window: readonly - TextEncoder: readonly - TextDecoder: readonly - console: readonly - setTimeout: readonly - setInterval: readonly - clearTimeout: readonly - clearInterval: readonly -parserOptions: - ecmaVersion: 2022 diff --git a/.gitignore b/.gitignore index a65980eb5..346fde5be 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,3 @@ /tools/node_modules # artifact from ci-templates: /container-build-report.xml -debian/tmp -debian/libcjs0 -debian/libcjs-dbg -debian/libcjs-dev -debian/cjs -debian/.debhelper -debian/*.substvars -debian/*.debhelper.log -debian/debhelper-build-stamp -debian/files \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 292b0b1a4..9a96606d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,5 +2,5 @@ # SPDX-FileCopyrightText: 2024 Philip Chimento [submodule "subprojects/gobject-introspection-tests"] path = subprojects/gobject-introspection-tests - url = ../gobject-introspection-tests.git + url = ../../GNOME/gobject-introspection-tests.git shallow = true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e22c9ee60..bd6ce9e71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -264,7 +264,8 @@ other programming languages. GJS was originally written in C, and the current state of the C++ code reflects that. Gradually, we want to move the code to a more idiomatic C++ style, using -smart pointer classes such as `GjsAutoChar` to help avoid memory leaks. +smart pointer classes such as `Gjs::AutoChar` to help avoid memory +leaks. Even farther in the future, we expect the Rust bindings for SpiderMonkey to mature as Mozilla's Servo browser engine progresses, and we may consider rewriting part or all of GJS in Rust. diff --git a/LICENSES/CC-BY-3.0.txt b/LICENSES/CC-BY-3.0.txt new file mode 100644 index 000000000..604209a80 --- /dev/null +++ b/LICENSES/CC-BY-3.0.txt @@ -0,0 +1,359 @@ +Creative Commons Legal Code + +Attribution-ShareAlike 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR + DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and + other pre-existing works, such as a translation, adaptation, + derivative work, arrangement of music or other alterations of a + literary or artistic work, or phonogram or performance and includes + cinematographic adaptations or any other form in which the Work may be + recast, transformed, or adapted including in any form recognizably + derived from the original, except that a work that constitutes a + Collection will not be considered an Adaptation for the purpose of + this License. For the avoidance of doubt, where the Work is a musical + work, performance or phonogram, the synchronization of the Work in + timed-relation with a moving image ("synching") will be considered an + Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as + encyclopedias and anthologies, or performances, phonograms or + broadcasts, or other works or subject matter other than works listed + in Section 1(f) below, which, by reason of the selection and + arrangement of their contents, constitute intellectual creations, in + which the Work is included in its entirety in unmodified form along + with one or more other contributions, each constituting separate and + independent works in themselves, which together are assembled into a + collective whole. A work that constitutes a Collection will not be + considered an Adaptation (as defined below) for the purposes of this + License. + c. "Creative Commons Compatible License" means a license that is listed + at https://creativecommons.org/compatiblelicenses that has been + approved by Creative Commons as being essentially equivalent to this + License, including, at a minimum, because that license: (i) contains + terms that have the same purpose, meaning and effect as the License + Elements of this License; and, (ii) explicitly permits the relicensing + of adaptations of works made available under that license under this + License or a Creative Commons jurisdiction license with the same + License Elements as this License. + d. "Distribute" means to make available to the public the original and + copies of the Work or Adaptation, as appropriate, through sale or + other transfer of ownership. + e. "License Elements" means the following high-level license attributes + as selected by Licensor and indicated in the title of this License: + Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that + offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, + the individual, individuals, entity or entities who created the Work + or if no individual or entity can be identified, the publisher; and in + addition (i) in the case of a performance the actors, singers, + musicians, dancers, and other persons who act, sing, deliver, declaim, + play in, interpret or otherwise perform literary or artistic works or + expressions of folklore; (ii) in the case of a phonogram the producer + being the person or legal entity who first fixes the sounds of a + performance or other sounds; and, (iii) in the case of broadcasts, the + organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms + of this License including without limitation any production in the + literary, scientific and artistic domain, whatever may be the mode or + form of its expression including digital form, such as a book, + pamphlet and other writing; a lecture, address, sermon or other work + of the same nature; a dramatic or dramatico-musical work; a + choreographic work or entertainment in dumb show; a musical + composition with or without words; a cinematographic work to which are + assimilated works expressed by a process analogous to cinematography; + a work of drawing, painting, architecture, sculpture, engraving or + lithography; a photographic work to which are assimilated works + expressed by a process analogous to photography; a work of applied + art; an illustration, map, plan, sketch or three-dimensional work + relative to geography, topography, architecture or science; a + performance; a broadcast; a phonogram; a compilation of data to the + extent it is protected as a copyrightable work; or a work performed by + a variety or circus performer to the extent it is not otherwise + considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with + respect to the Work, or who has received express permission from the + Licensor to exercise rights under this License despite a previous + violation. + j. "Publicly Perform" means to perform public recitations of the Work and + to communicate to the public those public recitations, by any means or + process, including by wire or wireless means or public digital + performances; to make available to the public Works in such a way that + members of the public may access these Works from a place and at a + place individually chosen by them; to perform the Work to the public + by any means or process and the communication to the public of the + performances of the Work, including by public digital performance; to + broadcast and rebroadcast the Work by any means including signs, + sounds or images. + k. "Reproduce" means to make copies of the Work by any means including + without limitation by sound or visual recordings and the right of + fixation and reproducing fixations of the Work, including storage of a + protected performance or phonogram in digital form or other electronic + medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more + Collections, and to Reproduce the Work as incorporated in the + Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, + including any translation in any medium, takes reasonable steps to + clearly label, demarcate or otherwise identify that changes were made + to the original Work. For example, a translation could be marked "The + original work was translated from English to Spanish," or a + modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated + in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor + reserves the exclusive right to collect such royalties for any + exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme can be waived, the Licensor waives the + exclusive right to collect such royalties for any exercise by You + of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to + collect royalties, whether individually or, in the event that the + Licensor is a member of a collecting society that administers + voluntary licensing schemes, via that society, from any exercise + by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms + of this License. You must include a copy of, or the Uniform Resource + Identifier (URI) for, this License with every copy of the Work You + Distribute or Publicly Perform. You may not offer or impose any terms + on the Work that restrict the terms of this License or the ability of + the recipient of the Work to exercise the rights granted to that + recipient under the terms of the License. You may not sublicense the + Work. You must keep intact all notices that refer to this License and + to the disclaimer of warranties with every copy of the Work You + Distribute or Publicly Perform. When You Distribute or Publicly + Perform the Work, You may not impose any effective technological + measures on the Work that restrict the ability of a recipient of the + Work from You to exercise the rights granted to that recipient under + the terms of the License. This Section 4(a) applies to the Work as + incorporated in a Collection, but this does not require the Collection + apart from the Work itself to be made subject to the terms of this + License. If You create a Collection, upon notice from any Licensor You + must, to the extent practicable, remove from the Collection any credit + as required by Section 4(c), as requested. If You create an + Adaptation, upon notice from any Licensor You must, to the extent + practicable, remove from the Adaptation any credit as required by + Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the + terms of: (i) this License; (ii) a later version of this License with + the same License Elements as this License; (iii) a Creative Commons + jurisdiction license (either this or a later license version) that + contains the same License Elements as this License (e.g., + Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible + License. If you license the Adaptation under one of the licenses + mentioned in (iv), you must comply with the terms of that license. If + you license the Adaptation under the terms of any of the licenses + mentioned in (i), (ii) or (iii) (the "Applicable License"), you must + comply with the terms of the Applicable License generally and the + following provisions: (I) You must include a copy of, or the URI for, + the Applicable License with every copy of each Adaptation You + Distribute or Publicly Perform; (II) You may not offer or impose any + terms on the Adaptation that restrict the terms of the Applicable + License or the ability of the recipient of the Adaptation to exercise + the rights granted to that recipient under the terms of the Applicable + License; (III) You must keep intact all notices that refer to the + Applicable License and to the disclaimer of warranties with every copy + of the Work as included in the Adaptation You Distribute or Publicly + Perform; (IV) when You Distribute or Publicly Perform the Adaptation, + You may not impose any effective technological measures on the + Adaptation that restrict the ability of a recipient of the Adaptation + from You to exercise the rights granted to that recipient under the + terms of the Applicable License. This Section 4(b) applies to the + Adaptation as incorporated in a Collection, but this does not require + the Collection apart from the Adaptation itself to be made subject to + the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or + Collections, You must, unless a request has been made pursuant to + Section 4(a), keep intact all copyright notices for the Work and + provide, reasonable to the medium or means You are utilizing: (i) the + name of the Original Author (or pseudonym, if applicable) if supplied, + and/or if the Original Author and/or Licensor designate another party + or parties (e.g., a sponsor institute, publishing entity, journal) for + attribution ("Attribution Parties") in Licensor's copyright notice, + terms of service or by other reasonable means, the name of such party + or parties; (ii) the title of the Work if supplied; (iii) to the + extent reasonably practicable, the URI, if any, that Licensor + specifies to be associated with the Work, unless such URI does not + refer to the copyright notice or licensing information for the Work; + and (iv) , consistent with Ssection 3(b), in the case of an + Adaptation, a credit identifying the use of the Work in the Adaptation + (e.g., "French translation of the Work by Original Author," or + "Screenplay based on original Work by Original Author"). The credit + required by this Section 4(c) may be implemented in any reasonable + manner; provided, however, that in the case of a Adaptation or + Collection, at a minimum such credit will appear, if a credit for all + contributing authors of the Adaptation or Collection appears, then as + part of these credits and in a manner at least as prominent as the + credits for the other contributing authors. For the avoidance of + doubt, You may only use the credit required by this Section for the + purpose of attribution in the manner set out above and, by exercising + Your rights under this License, You may not implicitly or explicitly + assert or imply any connection with, sponsorship or endorsement by the + Original Author, Licensor and/or Attribution Parties, as appropriate, + of You or Your use of the Work, without the separate, express prior + written permission of the Original Author, Licensor and/or Attribution + Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be + otherwise permitted by applicable law, if You Reproduce, Distribute or + Publicly Perform the Work either by itself or as part of any + Adaptations or Collections, You must not distort, mutilate, modify or + take other derogatory action in relation to the Work which would be + prejudicial to the Original Author's honor or reputation. Licensor + agrees that in those jurisdictions (e.g. Japan), in which any exercise + of the right granted in Section 3(b) of this License (the right to + make Adaptations) would be deemed to be a distortion, mutilation, + modification or other derogatory action prejudicial to the Original + Author's honor and reputation, the Licensor will waive or not assert, + as appropriate, this Section, to the fullest extent permitted by the + applicable national law, to enable You to reasonably exercise Your + right under Section 3(b) of this License (right to make Adaptations) + but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate + automatically upon any breach by You of the terms of this License. + Individuals or entities who have received Adaptations or Collections + from You under this License, however, will not have their licenses + terminated provided such individuals or entities remain in full + compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will + survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the + Work under different license terms or to stop distributing the Work at + any time; provided, however that any such election will not serve to + withdraw this License (or any other license that has been, or is + required to be, granted under the terms of this License), and this + License will continue in full force and effect unless terminated as + stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, + the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor + offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this License, and without further action + by the parties to this agreement, such provision shall be reformed to + the minimum extent necessary to make such provision valid and + enforceable. + d. No term or provision of this License shall be deemed waived and no + breach consented to unless such waiver or consent shall be in writing + and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with + respect to the Work licensed here. There are no understandings, + agreements or representations with respect to the Work not specified + here. Licensor shall not be bound by any additional provisions that + may appear in any communication from You. This License may not be + modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this + License were drafted utilizing the terminology of the Berne Convention + for the Protection of Literary and Artistic Works (as amended on + September 28, 1979), the Rome Convention of 1961, the WIPO Copyright + Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 + and the Universal Copyright Convention (as revised on July 24, 1971). + These rights and subject matter take effect in the relevant + jurisdiction in which the License terms are sought to be enforced + according to the corresponding provisions of the implementation of + those treaty provisions in the applicable national law. If the + standard suite of rights granted under applicable copyright law + includes additional rights not granted under this License, such + additional rights are deemed to be included in the License; this + License is not intended to restrict the license of any rights under + applicable law. + + +Creative Commons Notice + + Creative Commons is not a party to this License, and makes no warranty + whatsoever in connection with the Work. Creative Commons will not be + liable to You or any party on any legal theory for any damages + whatsoever, including without limitation any general, special, + incidental or consequential damages arising in connection to this + license. Notwithstanding the foregoing two (2) sentences, if Creative + Commons has expressly identified itself as the Licensor hereunder, it + shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the + Work is licensed under the CCPL, Creative Commons does not authorize + the use by either party of the trademark "Creative Commons" or any + related trademark or logo of Creative Commons without the prior + written consent of Creative Commons. Any permitted use will be in + compliance with Creative Commons' then-current trademark usage + guidelines, as may be published on its website or otherwise made + available upon request from time to time. For the avoidance of doubt, + this trademark restriction does not form part of the License. + + Creative Commons may be contacted at https://creativecommons.org/. diff --git a/NEWS b/NEWS index 36cd3070a..e8cc05386 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,300 @@ +Version 1.86.0 +-------------- + +- GJS now requires GLib 2.86 to build. + +- Closed bugs and merge requests: + * Fix coverage CI job [!1023, Philip Chimento] + * Test failure: Package module doesn't find a non-existent interface method + (caused by TypeError: GObject.type_default_interface_ref is not a function) + [#711, !1024, Florian Müllner] + * Test failure: not ok 1 - Issue 443 GObject wrapper disposed warning 1..1 + [#712, !1025, !1026, Marco Trevisan, Michael Catanzaro] + * gi/Gio: Improve mapping for platform-specific symbols into Gio [!1027, Marco + Trevisan] + * Remove .eslintignore [!1028, Florian Müllner] + * (type filepath) returned from c is decoded as utf8 (and can fail) [#706, + !1029, Gary Li] + * Various maintenance [!1030, Philip Chimento] + +Version 1.85.90 +--------------- + +- Closed bugs and merge requests: + * Support flat C array return values [#603, 1015, Gary Li] + * Make Gtk4Warnings test work again on CI [#698, !1017, Gary Li] + * overrides/Gio: Add wrappers for platform-specific Gio functions [!1018, + Marco Trevisan, Philip Chimento] + * Consider whether to handle invalid UTF-8 coming from C when converting to + JS string [#658, !1019, Gary Li] + * cleanup: Remove dead code using older GLib [!1020, Marco Trevisan] + * gjs -m makes mojibake [#707, !1021, Gary Li] + * Various maintenance [!1022, Philip Chimento] + +Version 1.85.2 +-------------- + +- New JavaScript features! This version of GJS is based on SpiderMonkey 140, an + upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 128. + Here are the highlights of the new JavaScript features, taken from + SpiderMonkey's release notes. + For more information, look them up on MDN or devdocs.io. + + * Float16Array typed arrays are now supported, along with + `DataView.prototype.getFloat16()` and `DataView.prototype.setFloat16()` for + reading and setting Float16Array values from a DataView, and the + `Math.f16round()` static method, which can be used to round numbers to 16 + bits. + * Regular expressions can now use the same name for named capturing groups in + different disjunction alternatives. + This is allowed because only one alternative in a disjunction will match, so + a name declared in several alternatives can only reference one captured + group. + The names must still be unique within a particular alternative, and across + the rest of the pattern. + * Support for synchronous iterator helper methods has been added, including: + `Iterator.prototype.drop()`, `Iterator.prototype.every()`, + `Iterator.prototype.filter()`, `Iterator.prototype.find()`, + `Iterator.prototype.flatMap()`, `Iterator.prototype.forEach()`, + `Iterator.prototype.map()`, `Iterator.prototype.reduce()`, + `Iterator.prototype.some()`, and `Iterator.prototype.take()`. + These helpers allow Array-like operations on iterators without having to + create intermediate Array objects. + They can also be used with very large data sets where creating an + intermediate Array would not even be possible. + * The `(?ims-ims:...)` regular expression modifiers allow you to make changes + to only take effect in a specific part of a regex pattern. + * Support for Uint8Array methods to ease conversions between base64- and + hex-encoded strings and byte arrays. + The new methods include: + + `Uint8Array.fromBase64()` and `Uint8Array.fromHex()` static methods for + constructing a new Uint8Array object from a base64- and hex-encoded + string, respectively. + + `Uint8Array.prototype.setFromBase64()`, and + `Uint8Array.prototype.setFromHex()` instance methods for populating an + existing Uint8Array object with bytes from a base64- or hex-encoded + string. + + `Uint8Array.prototype.toBase64()` and `Uint8Array.prototype.toHex() + instance methods, which return a base64- and hex- encoded string from the + data in a Uint8Array object. + * Support for the `RegExp.escape()` static method that can be used to escape + any potential regex syntax characters in a string, returning a new string + that can be safely used as a literal pattern for the `RegExp()` constructor. + * The `Promise.try()` convenience method is now supported. + The method takes a callback of any kind (a function that returns or throws, synchronously or asynchronously) and wraps its result in a Promise. + This allows you to use promise semantics (`.then()`, `.catch()`) to handle + the result from any kind of method. + * The JSON parse with source proposal is now supported, which aims to provide + features to mitigate issues around loss of precision when converting values + such as large floats and date values between JavaScript values and JSON + text. + Specifically, the following features are now available: + + The `JSON.parse()` reviver parameter context argument: Provides access to + the original JSON source text that was parsed. + + `JSON.isRawJSON()`: Tests whether a value is an object returned by + `JSON.rawJSON()`. + + `JSON.rawJSON()`: Creates a "raw JSON" object containing a piece of JSON + text, which can then be included in an object to preserve the specified + value when that object is stringified. + * `Intl.DurationFormat` is now supported, enabling locale-sensitive formatting + of durations. + * The `Math.sumPrecise()` static method is now supported. + This takes an iterable (such as an Array) of numbers and returns their sum. + It is more precise than summing the numbers in a loop because it avoids + floating point precision loss in intermediate results. + * The `Atomics.pause()` static method is now supported. + This method provides a hint to the CPU that the current thread is in a + spinlock while waiting on access to a shared resource. + The system can then reduce the resources allocated to the core (such as + power) or thread, without yielding the current thread. + * The `Error.captureStackTrace()` static method is now supported. + This installs stack trace information on a provided object as the + `Error.stack` property. + Its main use case is to install a stack trace on a custom error object that + does not derive from the Error interface. + * The `Error.isError()` static method can now be used to check whether or not + an object is an instance of an Error or a GError. + This is more reliable than using `instanceof` for the same purpose. + * The `import` declaration now supports importing JSON modules using the + `with` attribute. + * The Temporal API is now supported, this aims to simplify working with dates + and times in various scenarios, with built-in time zone and calendar + representations. This includes: + + A duration (difference between two time points): `Temporal.Duration` + + Points in time: + - As a unique instant in history: + * A timestamp: `Temporal.Instant` + * A date-time with a time zone: `Temporal.ZonedDateTime` + - Time-zone-unaware date/time ("Plain"): + * Date (year, month, day) + time (hour, minute, second, millisecond, + nanosecond): `Temporal.PlainDateTime` + * Date (year, month, day): `Temporal.PlainDate` + * Year, month: `Temporal.PlainYearMonth` + * Month, day: `Temporal.PlainMonthDay` + * Time (hour, minute, second, millisecond, nanosecond): + `Temporal.PlainTime` + + Now (current time) as various class instances, or in a specific format: + `Temporal.Now` + +- Closed bugs and merge requests: + * Port to libgirepository-2.0 needed [#684, !1001, Philip Chimento] + * installed-tests: install sourcemap-number-module.js [!1002, Jeremy Bicha] + * gjs-1.84.1 fails testsuite on s390x [#685, !1003, Pranav P, Jeremy Bicha] + * build: Add a mozjs_dep_name pkgconfig variable [!1004, Philip Chimento] + * Assertion when calling Gtk.MapListModel's map function [#691, !1005, Philip + Chimento] + * Various maintenance [!1006, !1014, Philip Chimento] + * package: Fix port to gobject-introspection-2.0 [!1007, Florian Müllner] + * SpiderMonkey 140 [#690, !1008, Xi Ruoyao, Philip Chimento] + * build: Fix libffi dependency in .pc [!1009, Florian Müllner] + * maint: Switch to flat eslint config [!1010, Florian Müllner] + * Update Docker images to Fedora 42 and mozjs140 [!1011, !1012, !1013, Philip + Chimento] + +Version 1.85.1 +-------------- + +- Closed bugs and merge requests: + * Various maintenance [!985, !990, !991, !1000, Philip Chimento] + * ObjectInstance::unlink is slow since s_wrapped_gobject_list is a vector + [#682, !989, road2react] + * Make GTK3 tests optional [#679, !993, Philip Chimento] + * Preparation for gobject-introspection-2.0 [!994, Philip Chimento] + * Add aarch64 CI job [#364, !996, Gary Li] + * Add null-safe C++ wrappers for libgirepository [!997, Philip Chimento] + * Extra handling for enum/flags in setter and getter callers [!998, Pranav P] + * Register Cairo.Path and Cairo.Pattern as foreign structs [#659, !999, Gary + Li] + * Crash when passing certain Cairo types as transfer-full in arguments [#660, + !999, Gary Li] + +Version 1.84.2 +-------------- + +- Closed bugs and merge requests: + * GtkNotebook.pages GListModel is inaccessible from GJS [#686, !992, Philip + Chimento] + +Version 1.84.1 +-------------- + +- 1.84.0 was never released due to Freedesktop outage. 1.84.1 is + identical. + +Version 1.84.0 +-------------- + +- Closed bugs and merge requests: + * tests: Prevent failures when GTK4/DISPLAY is missing [!986, Jan Tojnar] + * testWarnings: run gc wrapper test only under Gtk4 [!987, Gary Li] + +Version 1.83.90 +--------------- + +- Closed bugs and merge requests: + * Various maintenance [!982, Philip Chimento] + * Add type checking job [!983, Philip Chimento] + * Write g-i regression tests for flags and enum values with gaps [#538, !984, + Gary Li] + +Version 1.83.4 +-------------- + +- Brown bag release to fix codespell error in NEWS file. + +Version 1.83.3 +-------------- + +- The gjs-console REPL is now asynchronous. You can, for example, create a + window with a button, connect a signal handler, click the button, and the + signal handler will run when the button is clicked. Previously, the signal + handler wouldn't run because it was blocked by the console waiting for input. + This doesn't yet make `await` work in the console, but it is a prerequisite. + +- Usually for C APIs that use GValue, GJS transparently substitutes native JS + values. However, in some cases you need to use the GObject.Value wrapper in + JS. There is now a new API to construct GObject.Value. Instead of constructing + an empty Value object, calling `init()` with the type, and then `set_...` to + fill it, you can now do it in one: `new GObject.Value(String, 'a string')`. + (The old way still works.) + +- Closed bugs and merge requests: + * interactive interpreter + mainloop [#67, !670, !975, Evan Welsh, Philip + Chimento] + * object: Add support for static virtual functions [!802, Marco Trevisan, + Philip Chimento] + * "%Id" support in format strings for alternative digits disabled due to error + in detection at configure/build time [#671, !972, Philip Chimento] + * null-prototype objects should be pretty-printed less confusingly [#626, + !973, Gary Li] + * Missing property with gjs 1.83.2 [#677, !976, Philip Chimento] + * arg-types-inl: Replace `` pairs with a single TAG [!977, + Philip Chimento] + * Introduce simpler override for GObject.Value [#456, !978, Gary Li] + * Use Meson 1.4 and full_path() feature [!979, Philip Chimento] + * Update gobject-introspection-tests [!981, Philip Chimento] + +Version 1.83.2 +-------------- + +- Closed bugs and merge requests: + + * profiler: only build dynamic string for profiler label if profiling + [#668, !971, Gary Li] + * object: Fix missing static_type_name template parameter + [!974, Philip Chimento] + +Version 1.83.1 +-------------- + +- GJS now supports source maps. If you use build tools such as TypeScript for + source code transformation, you can ship source map files alongside your built + JS files and make sure your build tool adds the magic source map comment. You + will then get the original source locations printed in stack traces and in the + debugger. + +- In the interactive interpreter (gjs-console), command history is now saved + between runs. You can set the environment variable GJS_REPL_HISTORY to save + the command history to a custom file, or set it to an empty string to switch + this feature off. + +- The debugger now supports examining private fields. + +- Some performance and memory usage improvements around calling GNOME platform + functions and accessing properties of GNOME platform objects. + +- Backwards-incompatible change: Gettext.setlocale() now only affects the locale + of the current thread. This will not affect your JS code, but it may affect + your app if you use a C library with worker threads and you relied on being + able to set the locale in those worker threads from JS. + +- Closed bugs and merge requests: + + * Rewrite arguments cache using C++ inheritance [!519, Marco Trevisan, Philip + Chimento] + * package: Try to load resource module name if available [!839, Marco + Trevisan] + * object, args-cache: Improve performance with properties basic types [!866, + Marco Trevisan, Philip Chimento] + * Use property accessors and setters directly [#524, !867, Marco Trevisan, + Philip Chimento] + * gjs-util: make gjs_setlocale thread-safe [!893, Ray Strode] + * Support Source Maps [#474, !938, Gary Li] + * Fix return value of load_contents_async [!956, Sebastian Wiesner] + * Various maintenance [!957, !961, !967, Philip Chimento] + * Add history support to REPL [#645, !958, Gary Li] + * Some prep for type safety refactors [!959, Philip Chimento] + * Update to latest gobject-introspection-tests [!962, Philip Chimento] + * Build failure regression for i686 [#669, !963, Philip Chimento] + * Segfault when using GtkListView and custom widgets [#443, !964, Gary Li] + * ci: Switch to GNOME GitLab mirror of ci-templates [!965, Bartłomiej + Piotrowski] + * Connecting to signal of a GstElement errors with "too much recursion" [#557, + !966, Gary Li] + * Update to use GNOME Release Service [!968, Philip Chimento] + * Enable inspecting symbol properties and private fields in the debugger + [#455, !969, Gary Li] + Version 1.82.1 -------------- @@ -77,7 +374,7 @@ Version 1.81.2 * Invalid search paths cause failed assertions when printing imports.gi [#629, !935, Gary Li] * SpiderMonkey 128 [#630, !936, !945, Philip Chimento] - * Pretty-printing byte array in cjs-console throws a type conversion + * Pretty-printing byte array in gjs-console throws a type conversion error [#434, !937, Gary Li] * js: Add gjs_debug_callable() debug function [!940, Philip Chimento] * build: Build Cairo from subproject if not found [!941, Philip @@ -390,7 +687,7 @@ Version 1.77.1 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 + * installed-tests/meson: Add tests dependencies on gjs console and GjsPrivate [!835, Marco Trevisan] * gi/arg: Cleanup handling of C arrays and GValue arrays [!836, Marco Trevisan] @@ -2198,7 +2495,7 @@ Version 1.55.90 * GDBus proxy overrides should support Gio.DBusProxy.call_with_unix_fd_list() [#204, !263, Philip Chimento] * Add regression tests for GObject vfuncs [!259, Jason Hicks] - * CjsPrivate: Sources should be C files [!262, Philip Chimento] + * GjsPrivate: Sources should be C files [!262, Philip Chimento] * build: Vendor last-good version of AX_CODE_COVERAGE [!264, Philip Chimento] Version 1.55.4 @@ -2661,7 +2958,7 @@ Version 1.53.2 Philip Chimento] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] - * gjs: JS_GetContextPrivate(): cjs-console killed by SIGSEGV [#148, !121, + * gjs: JS_GetContextPrivate(): gjs-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 @@ -2770,7 +3067,7 @@ Version 1.52.1 * Add support for passing flags to Gio.DBusProxy in makeProxyWrapper [#122, !81, Florian Müllner] * Cannot instantiate Cairo.Context [#126, !91, Philip Chimento] - * GISCAN CjsPrivate-1.0.gir fails [#128, !90, Philip Chimento] + * GISCAN GjsPrivate-1.0.gir fails [#128, !90, Philip Chimento] * Invalid read of g_object_finalized flag [#129, !117, Philip Chimento] * Fix race condition in coverage file test [#130, !99, Philip Chimento] * Linter jobs should only fail if new lint errors were added [#133, !94, @@ -3254,7 +3551,7 @@ Version 1.49.2 - Closed bugs: * Make gjs build on Windows/Visual Studio [#775868, Chun-wei Fan] - * Bring back fancy error reporter in cjs-console [#781882, Philip Chimento] + * Bring back fancy error reporter in gjs-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, @@ -3611,7 +3908,7 @@ Version 1.47.0 'A_FIELD': 'A value', }); -- Backwards-incompatible change: we have changed the way cjs-console interprets +- Backwards-incompatible change: we have changed the way gjs-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: @@ -3793,7 +4090,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 cjs-console + - Add an interactive console gjs-console - Allow code in directory modules (i.e. the code should reside in __init__.js files) - Fix handling of enum/flags return values @@ -3806,7 +4103,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/cjs-console) + - Add support for UNIX shebang (i.e. #!/usr/bin/gjs-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/README.MSVC.md b/README.MSVC.md index 2880cbe77..bc45d1de3 100644 --- a/README.MSVC.md +++ b/README.MSVC.md @@ -7,13 +7,13 @@ recent-enough Windows SDK from Microsoft is still required if using clang-cl, as we will still use items from the Windows SDK. Recent official binary installers of CLang (which contains clang-cl) -from the LLVM website are known to work to build SpiderMonkey 128 and +from the LLVM website are known to work to build SpiderMonkey 140 and GJS. You will need the following items to build GJS using Visual Studio or clang-cl (they can be built with Visual Studio 2015 or later, unless otherwise noted): -- SpiderMonkey 128.x (mozjs-128). This must be built with clang-cl as +- SpiderMonkey 140.x (mozjs-140). This must be built with clang-cl as the Visual Studio compiler is no longer supported for building this. Please see the below section carefully on this... - GObject-Introspection (G-I) 1.66.x or later @@ -40,12 +40,12 @@ SpiderMonkey nowadays, so it may be helpful to look in ftp://ftp.gnome.org/pub/gnome/teams/releng/tarballs-needing-help/mozjs/ -for the suitable release series of SpiderMonkey that corresponds to -the GJS version that is being built, as GJS depends on ESR (Extended +for the suitable release series of SpiderMonkey that corresponds to +the GJS version that is being built, as GJS depends on ESR (Extended Service Release, a.k.a Long-term support) releases of SpiderMonkey. -You may also be able to obtain the SpiderMonkey 128.x sources via the -FireFox (ESR) or Thunderbird 128.x sources, in $(srcroot)/js. +You may also be able to obtain the SpiderMonkey 140.x sources via the +FireFox (ESR) or Thunderbird 140.x sources, in $(srcroot)/js. Please do note that the build must be done carefully, in addition to the official instructions that are posted on the Mozilla website: @@ -53,7 +53,7 @@ official instructions that are posted on the Mozilla website: https://firefox-source-docs.mozilla.org/js/build.html You will need to create a .mozconfig file that will describe your build -options for the build in the root directory of the Firefox/ThunderBird 128.x +options for the build in the root directory of the Firefox/ThunderBird 140.x sources. A sample content of the .mozconfig file can be added as follows: ``` @@ -65,7 +65,7 @@ ac_add_options --disable-tests ac_add_options --enable-optimize ac_add_options --disable-debug ac_add_options --disable-jemalloc -ac_add_options --prefix=c:/software.b/mozjs128.bin +ac_add_options --prefix=c:/software.b/mozjs140.bin ``` An explanation of the lines above: @@ -77,10 +77,10 @@ An explanation of the lines above: * `ac_add_options --enable-optimize`: Use for release builds of SpiderMonkey. Use `--disable-optimize` instead if building with `--enable-debug` * `ac_add_options --enable-debug`: Include debugging functions, for debug builds. Use `--disable-debug` instead if building with `--enable-optimize` * `ac_add_options --disable-jemalloc`: This is absolutely needed, otherwise GJS will not build and run correctly -* `ac_add_options --prefix=c:/software.b/mozjs128.bin`: Some installation path, change as needed +* `ac_add_options --prefix=c:/software.b/mozjs140.bin`: Some installation path, change as needed If your GJS build crashes upon launch, use Dependency Walker to ensure that -mozjs-128.dll does not depend on mozglue.dll! If it does, or if GJS fails to +mozjs-140.dll does not depend on mozglue.dll! If it does, or if GJS fails to link with missing arena_malloc() and friends symbols, you have built SpiderMoney incorrectly and will need to rebuild SpiderMonkey (with the build options as noted above) and retry the build. @@ -90,7 +90,7 @@ You should explicitly enable one and disable the other, as `--enable-debug` will make the resulting build depend on the debug CRT, and mixing between the release and debug CRT in the same DLL is often a sign of trouble when using with GJS, meaning that you will need to rebuild SpiderMonkey with the appropriate -options set in your `.mozconfig` file. Please note that for SpiderMonkey builds, +options set in your `.mozconfig` file. Please note that for SpiderMonkey builds, PDB files are generated even if `--disable-debug` is used. You will need to check that `js-config.h` has the correct entries that correspond @@ -109,25 +109,25 @@ instructions, as Rust is being involved here. Run `./mach build` to carry out the build, and then `./mach build install` to copy the completed build to the directory specified by `ac_add_options --prefix=xxx`. -If `./mach build install` does not work for you for some reason, the DLLs you +If `./mach build install` does not work for you for some reason, the DLLs you need and js.exe can be found in $(buildroot)/dist/bin (you need *all* the DLLs, -make sure that there is no mozglue.dll, otherwise you will need to redo your +make sure that there is no mozglue.dll, otherwise you will need to redo your build as noted above), and the required headers are found in -$(buildroot)/dist/include. Note that for PDB files and .lib files, +$(buildroot)/dist/include. Note that for PDB files and .lib files, you will need to search for them in $(buildroot), where the PDB file names match the filenames for the DLLs/EXEs in $(buildroot)/dist/bin, and you will need to look for the following .lib files: --mozjs-128.lib +-mozjs-140.lib -js_static.lib (optional) -You may want to put the .lib's and DLLs/EXEs into $(PREFIX)\lib and +You may want to put the .lib's and DLLs/EXEs into $(PREFIX)\lib and $(PREFIX)\bin respectively, and put the headers into -$(PREFIX)\include\mozjs-128 for convenience. +$(PREFIX)\include\mozjs-140 for convenience. -You will need to place the generated mozjs-128.pc pkg-config file into +You will need to place the generated mozjs-140.pc pkg-config file into $(PREFIX)\lib\pkgconfig and ensure that pkg-config can find it by setting PKG_CONFIG_PATH. Ensure that the 'includedir' and 'libdir' -in there is correct so that the mozjs-128.pc can be used correctly in +in there is correct so that the mozjs-140.pc can be used correctly in Visual Studio/clang-cl builds, and replace the `-isystem` with `-I` if building GJS with Visual Studio. You will also need to ensure that the existing GObject-Introspection installation (if used) is on the same diff --git a/build/choose-tests-locale.sh b/build/choose-tests-locale.sh old mode 100644 new mode 100755 diff --git a/build/flatpak/org.gnome.GjsConsole.json b/build/flatpak/org.gnome.GjsConsole.json index 1f82454b4..35f24d00e 100644 --- a/build/flatpak/org.gnome.GjsConsole.json +++ b/build/flatpak/org.gnome.GjsConsole.json @@ -9,7 +9,7 @@ "runtime": "org.gnome.Platform", "runtime-version": "master", "sdk": "org.gnome.Sdk", - "command": "cjs-console", + "command": "gjs-console", "finish-args": [ "--share=ipc", "--socket=fallback-x11", diff --git a/build/maintainer-upload-release.sh b/build/maintainer-tag-release.sh old mode 100644 new mode 100755 similarity index 64% rename from build/maintainer-upload-release.sh rename to build/maintainer-tag-release.sh index 011df8cf2..e25e01689 --- a/build/maintainer-upload-release.sh +++ b/build/maintainer-tag-release.sh @@ -10,11 +10,7 @@ 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 +project_version="${1:?project version is required}" # Don't forget to write release notes head -n1 "${MESON_SOURCE_ROOT}/NEWS" | grep "$project_version" @@ -27,6 +23,8 @@ case $project_version in 1.79.* | 1.80.*) gnome_series=46 ;; 1.8[12].*) gnome_series=47 ;; 1.8[34].*) gnome_series=48 ;; + 1.8[56].*) gnome_series=49 ;; + 1.8[78].*) gnome_series=50 ;; *) echo "Version $project_version not handled by this script" exit 1 @@ -44,17 +42,7 @@ pushd "$MESON_SOURCE_ROOT" # 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 + git tag -s "$project_version" -m "Version $project_version" 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 --unattended "$tarball_basename" diff --git a/build/symlink-cjs.py b/build/symlink-gjs.py similarity index 82% rename from build/symlink-cjs.py rename to build/symlink-gjs.py index 213b73536..1ab197f82 100644 --- a/build/symlink-cjs.py +++ b/build/symlink-gjs.py @@ -14,8 +14,8 @@ if os.name == 'nt': # Using symlinks on Windows often require administrative privileges, - # which is not what we want. Instead, copy cjs-console.exe. - shutil.copyfile('cjs-console.exe', os.path.join(installed_bin_dir, 'cjs.exe')) + # which is not what we want. Instead, copy gjs-console.exe. + shutil.copyfile('gjs-console.exe', os.path.join(installed_bin_dir, 'gjs.exe')) else: try: temp_link = tempfile.mktemp(dir=installed_bin_dir) diff --git a/cjs/auto.h b/cjs/auto.h new file mode 100644 index 000000000..58f5fefb7 --- /dev/null +++ b/cjs/auto.h @@ -0,0 +1,274 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2017 Chun-wei Fan +// SPDX-FileCopyrightText: 2018-2020 Canonical, Ltd +// SPDX-FileCopyrightText: 2018, 2024 Philip Chimento + +#pragma once + +#include + +#include + +#include +#include + +#include +#include + +#include // for UniqueChars + +// Auto pointers. We don't use GLib's g_autofree and friends because they only +// work on GCC and Clang, and we try to support MSVC where possible. But this is +// C++, so we use C++ classes. + +namespace Gjs { + +// A sentinel object used to pick the AutoPointer constructor that adds a +// reference: AutoFoo foo{pointer, TakeOwnership{}}; +struct TakeOwnership {}; + +template +using AutoPointerRefFunction = F* (*)(F*); + +template +using AutoPointerFreeFunction = void (*)(F*); + +template free_func = free, + AutoPointerRefFunction ref_func = nullptr> +struct AutoPointer { + using Tp = + 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 = AutoPointer; + + private: + template + static constexpr bool has_function() { + using NullType = std::integral_constant; + using ActualType = std::integral_constant; + + return !std::is_same_v; + } + + public: + static constexpr bool has_free_function() { + return has_function, free_func>(); + } + + static constexpr bool has_ref_function() { + return has_function, ref_func>(); + } + + constexpr AutoPointer(Ptr ptr = nullptr) // NOLINT(runtime/explicit) + : m_ptr(ptr) {} + template && + std::is_array_v>> + explicit constexpr AutoPointer(U ptr[]) : m_ptr(ptr) {} + + constexpr AutoPointer(Ptr ptr, const TakeOwnership&) : AutoPointer(ptr) { + m_ptr = copy(); + } + constexpr AutoPointer(ConstPtr ptr, const TakeOwnership& o) + : AutoPointer(const_cast(ptr), o) {} + constexpr AutoPointer(AutoPointer&& other) : AutoPointer() { + this->swap(other); + } + constexpr AutoPointer(AutoPointer const& other) : AutoPointer() { + *this = other; + } + + constexpr AutoPointer& operator=(Ptr ptr) { + reset(ptr); + return *this; + } + + constexpr AutoPointer& operator=(AutoPointer&& other) { + this->swap(other); + return *this; + } + + constexpr AutoPointer& operator=(AutoPointer const& other) { + AutoPointer dup{other.get(), TakeOwnership{}}; + this->swap(dup); + return *this; + } + + template + constexpr std::enable_if_t, Ptr> operator->() { + return m_ptr; + } + + template + constexpr std::enable_if_t, ConstPtr> operator->() + const { + 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; } + constexpr operator ConstPtr() const { return m_ptr; } + constexpr operator bool() const { return m_ptr != nullptr; } + + constexpr Ptr get() const { return m_ptr; } + constexpr Ptr* out() { return &m_ptr; } + constexpr ConstPtr* out() const { return const_cast(&m_ptr); } + + constexpr Ptr release() { + auto* ptr = m_ptr; + m_ptr = nullptr; + return ptr; + } + + constexpr void reset(Ptr ptr = nullptr) { + Ptr old_ptr = m_ptr; + m_ptr = ptr; + + if constexpr (has_free_function()) { + if (old_ptr) + free_func(reinterpret_cast(old_ptr)); + } + } + + constexpr void swap(AutoPointer& other) { + std::swap(this->m_ptr, other.m_ptr); + } + + /* constexpr */ ~AutoPointer() { // one day, with -std=c++2a + reset(); + } + + template + [[nodiscard]] + constexpr std::enable_if_t, Ptr> copy() const { + static_assert(has_ref_function(), "No ref function provided"); + return m_ptr ? reinterpret_cast( + ref_func(reinterpret_cast(m_ptr))) + : nullptr; + } + + template + [[nodiscard]] + constexpr C* as() const { + return const_cast(reinterpret_cast(m_ptr)); + } + + private: + Ptr m_ptr; +}; + +template free_func, + AutoPointerRefFunction ref_func> +constexpr bool operator==(AutoPointer const& lhs, + AutoPointer const& rhs) { + return lhs.get() == rhs.get(); +} + +template +using AutoFree = AutoPointer; + +struct AutoCharFuncs { + static char* dup(char* str) { return g_strdup(str); } + static void free(char* str) { g_free(str); } +}; +using AutoChar = + AutoPointer; + +// This moves a string owned by the JS runtime into the GLib domain. This is +// only possible because currently, js_free() and g_free() both ultimately call +// free(). It would cause crashes if SpiderMonkey were to stop supporting +// embedders using the system allocator in the future. In that case, this +// function would have to copy the string. +[[nodiscard]] inline AutoChar js_chars_to_glib(JS::UniqueChars&& js_chars) { + return {js_chars.release()}; +} + +using AutoStrv = AutoPointer; + +template +using AutoUnref = AutoPointer; + +using AutoGVariant = + AutoPointer; + +using AutoParam = + AutoPointer; + +using AutoGClosure = + AutoPointer; + +template +constexpr void AutoPointerDeleter(T v) { + if constexpr (std::is_array_v) + delete[] reinterpret_cast*>(v); + else + delete v; +} + +template +using AutoCppPointer = AutoPointer>; + +template +class AutoTypeClass : public AutoPointer { + explicit AutoTypeClass(void* ptr = nullptr) + : AutoPointer(static_cast(ptr)) {} + + public: + explicit AutoTypeClass(GType gtype) + : AutoTypeClass(g_type_class_ref(gtype)) {} +}; + +template +struct SmartPointer : AutoPointer { + using AutoPointer::AutoPointer; +}; + +template <> +struct SmartPointer : AutoStrv { + using AutoStrv::AutoPointer; +}; + +template <> +struct SmartPointer : AutoStrv { + using AutoStrv::AutoPointer; +}; + +template <> +struct SmartPointer : AutoUnref { + using AutoUnref::AutoPointer; +}; + +template <> +struct SmartPointer : AutoGVariant { + using AutoGVariant::AutoPointer; +}; + +template <> +struct SmartPointer : AutoPointer { + using AutoPointer::AutoPointer; +}; + +template <> +struct SmartPointer : AutoPointer { + using AutoPointer::AutoPointer; +}; + +} // namespace Gjs diff --git a/cjs/byteArray.cpp b/cjs/byteArray.cpp index 20b0258a9..0202b51f9 100644 --- a/cjs/byteArray.cpp +++ b/cjs/byteArray.cpp @@ -121,7 +121,7 @@ from_gbytes_func(JSContext *context, "bytes", &bytes_obj)) return false; - if (!BoxedBase::typecheck(context, bytes_obj, nullptr, G_TYPE_BYTES)) + if (!BoxedBase::typecheck(context, bytes_obj, G_TYPE_BYTES)) return false; gbytes = BoxedBase::to_c_ptr(context, bytes_obj); diff --git a/cjs/cjs.stp.in b/cjs/cjs.stp.in index 423334918..d0b51ad34 100644 --- a/cjs/cjs.stp.in +++ b/cjs/cjs.stp.in @@ -3,20 +3,20 @@ * SPDX-FileCopyrightText: 2010 Red Hat, Inc. */ -probe cjs.object_wrapper_new = process("@EXPANDED_LIBDIR@/libcjs-gi.so.0.0.0").mark("object__wrapper__new") +probe gjs.object_wrapper_new = process("@EXPANDED_LIBDIR@/libgjs-gi.so.0.0.0").mark("object__wrapper__new") { wrapper_address = $arg1; gobject_address = $arg2; gi_namespace = user_string($arg3); gi_name = user_string($arg4); - probestr = sprintf("cjs.object_wrapper_new(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); + probestr = sprintf("gjs.object_wrapper_new(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } -probe cjs.object_wrapper_finalize = process("@EXPANDED_LIBDIR@/libcjs-gi.so.0.0.0").mark("object__wrapper__finalize") +probe gjs.object_wrapper_finalize = process("@EXPANDED_LIBDIR@/libgjs-gi.so.0.0.0").mark("object__wrapper__finalize") { wrapper_address = $arg1; gobject_address = $arg2; gi_namespace = user_string($arg3); gi_name = user_string($arg4); - probestr = sprintf("cjs.object_wrapper_finalize(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); + probestr = sprintf("gjs.object_wrapper_finalize(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } diff --git a/cjs/cjs_pch.hh b/cjs/cjs_pch.hh index 298c62a7f..06de6505e 100644 --- a/cjs/cjs_pch.hh +++ b/cjs/cjs_pch.hh @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -33,8 +32,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -66,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,7 @@ #include #include #include +#include #include #include #ifdef HAVE_READLINE_READLINE_H diff --git a/cjs/console.cpp b/cjs/console.cpp index c5064657c..c13b9bc57 100644 --- a/cjs/console.cpp +++ b/cjs/console.cpp @@ -19,14 +19,16 @@ #include #include -#include -#include - -static GjsAutoStrv include_path; -static GjsAutoStrv coverage_prefixes; -static GjsAutoChar coverage_output_path; -static GjsAutoChar profile_output_path; -static GjsAutoChar command; +#include "cjs/auto.h" +#include "cjs/gerror-result.h" +#include "cjs/gjs.h" +#include "util/console.h" + +static Gjs::AutoStrv include_path; +static Gjs::AutoStrv coverage_prefixes; +static Gjs::AutoChar coverage_output_path; +static Gjs::AutoChar profile_output_path; +static Gjs::AutoChar command; static gboolean print_version = false; static gboolean print_js_version = false; static gboolean debugging = false; @@ -36,7 +38,7 @@ static bool enable_profiler = false; static gboolean parse_profile_arg(const char *, const char *, void *, GError **); using GjsAutoGOptionContext = - GjsAutoPointer; + Gjs::AutoPointer; // clang-format off static GOptionEntry entries[] = { @@ -48,8 +50,10 @@ static GOptionEntry entries[] = { "Add the prefix PREFIX to the list of files to generate coverage info for", "PREFIX" }, { "coverage-output", 0, 0, G_OPTION_ARG_STRING, coverage_output_path.out(), "Write coverage output to a directory DIR. This option is mandatory when using --coverage-prefix", "DIR", }, - { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, include_path.out(), "Add the directory DIR to the list of directories to search for js files.", "DIR" }, - { "module", 'm', 0, G_OPTION_ARG_NONE, &exec_as_module, "Execute the file as a module." }, + { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, include_path.out(), + "Add DIR to the list of paths to search for JS files", "DIR" }, + { "module", 'm', 0, G_OPTION_ARG_NONE, &exec_as_module, + "Execute the file as a module" }, { "profile", 0, G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, reinterpret_cast(&parse_profile_arg), "Enable the profiler and write output to FILE (default: gjs-$PID.syscap)", @@ -59,30 +63,17 @@ static GOptionEntry entries[] = { }; // clang-format on -[[nodiscard]] static GjsAutoStrv strndupv(int n, char* const* strv) { -#if GLIB_CHECK_VERSION(2, 68, 0) - GjsAutoPointer builder( - g_strv_builder_new()); +[[nodiscard]] static Gjs::AutoStrv strndupv(int n, char* const* strv) { + Gjs::AutoPointer builder{ + g_strv_builder_new()}; for (int i = 0; i < n; ++i) g_strv_builder_add(builder, strv[i]); return g_strv_builder_end(builder); - -#else - - int ix; - if (n == 0) - return NULL; - char **retval = g_new(char *, n + 1); - for (ix = 0; ix < n; ix++) - retval[ix] = g_strdup(strv[ix]); - retval[n] = NULL; - return retval; -#endif // GLIB_CHECK_VERSION(2, 68, 0) } -[[nodiscard]] static GjsAutoStrv strcatv(char** strv1, char** strv2) { +[[nodiscard]] static Gjs::AutoStrv strcatv(char** strv1, char** strv2) { if (strv1 == NULL && strv2 == NULL) return NULL; if (strv1 == NULL) @@ -90,36 +81,19 @@ static GOptionEntry entries[] = { if (strv2 == NULL) return g_strdupv(strv1); -#if GLIB_CHECK_VERSION(2, 70, 0) - GjsAutoPointer builder( - g_strv_builder_new()); + Gjs::AutoPointer builder{ + g_strv_builder_new()}; g_strv_builder_addv(builder, const_cast(strv1)); g_strv_builder_addv(builder, const_cast(strv2)); return g_strv_builder_end(builder); - -#else - - unsigned len1 = g_strv_length(strv1); - unsigned len2 = g_strv_length(strv2); - char **retval = g_new(char *, len1 + len2 + 1); - unsigned ix; - - for (ix = 0; ix < len1; ix++) - retval[ix] = g_strdup(strv1[ix]); - for (ix = 0; ix < len2; ix++) - retval[len1 + ix] = g_strdup(strv2[ix]); - retval[len1 + len2] = NULL; - - return retval; -#endif // GLIB_CHECK_VERSION(2, 70, 0) } static gboolean parse_profile_arg(const char* option_name [[maybe_unused]], const char* value, void*, GError**) { enable_profiler = true; - profile_output_path = GjsAutoChar(value, GjsAutoTakeOwnership()); + profile_output_path = Gjs::AutoChar{value, Gjs::TakeOwnership{}}; return true; } @@ -127,10 +101,10 @@ static void check_script_args_for_stray_gjs_args(int argc, char * const *argv) { - GjsAutoError error; - GjsAutoStrv new_coverage_prefixes; - GjsAutoChar new_coverage_output_path; - GjsAutoStrv new_include_paths; + Gjs::AutoError error; + Gjs::AutoStrv new_coverage_prefixes; + Gjs::AutoChar new_coverage_output_path; + Gjs::AutoStrv new_include_paths; // Don't add new entries here. This is only for arguments that were // previously accepted after the script name on the command line, for // backwards compatibility. @@ -143,7 +117,7 @@ check_script_args_for_stray_gjs_args(int argc, }; // clang-format on - GjsAutoStrv argv_copy = g_new(char*, argc + 2); + Gjs::AutoStrv argv_copy{g_new(char*, argc + 2)}; int ix; argv_copy[0] = g_strdup("dummy"); /* Fake argv[0] for GOptionContext */ @@ -189,12 +163,12 @@ 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)); - GjsAutoError error; + Gjs::AutoError error; /* evaluate the script */ int code = 0; if (exec_as_module) { - GjsAutoUnref output = g_file_new_for_commandline_arg(filename); - GjsAutoChar uri = g_file_get_uri(output); + Gjs::AutoUnref output{g_file_new_for_commandline_arg(filename)}; + Gjs::AutoChar uri{g_file_get_uri(output)}; if (!gjs_context_register_module(js_context, uri, uri, &error)) { g_critical("%s", error->message); code = 1; @@ -217,7 +191,7 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc, } int main(int argc, char** argv) { - GjsAutoError error; + Gjs::AutoError error; const char *filename; const char *program_name; gsize len; @@ -232,7 +206,7 @@ int main(int argc, char** argv) { g_option_context_set_ignore_unknown_options(context, true); g_option_context_set_help_enabled(context, false); - GjsAutoStrv argv_copy_addr(g_strdupv(argv)); + Gjs::AutoStrv argv_copy_addr{g_strdupv(argv)}; char** argv_copy = argv_copy_addr; g_option_context_add_main_entries(context, entries, NULL); @@ -256,7 +230,7 @@ int main(int argc, char** argv) { break; } } - GjsAutoStrv gjs_argv_addr = strndupv(gjs_argc, argv); + Gjs::AutoStrv gjs_argv_addr{strndupv(gjs_argc, argv)}; char** gjs_argv = gjs_argv_addr; script_argc = argc - gjs_argc; script_argv = argv + gjs_argc; @@ -273,8 +247,8 @@ int main(int argc, char** argv) { g_option_context_set_ignore_unknown_options(context, false); g_option_context_set_help_enabled(context, true); if (!g_option_context_parse_strv(context, &gjs_argv, &error)) { - GjsAutoChar help_text = - g_option_context_get_help(context, true, nullptr); + Gjs::AutoChar help_text{ + g_option_context_get_help(context, true, nullptr)}; g_printerr("%s\n\n%s\n", error->message, help_text.get()); return EXIT_FAILURE; } @@ -289,9 +263,9 @@ int main(int argc, char** argv) { return EXIT_SUCCESS; } - GjsAutoChar program_path = nullptr; + Gjs::AutoChar program_path; gjs_argc = g_strv_length(gjs_argv); - GjsAutoChar script; + Gjs::AutoChar script; if (command) { script = command; len = strlen(script); @@ -312,7 +286,8 @@ int main(int argc, char** argv) { } else { /* All unprocessed options should be in script_argv */ g_assert(gjs_argc == 2); - GjsAutoUnref input = g_file_new_for_commandline_arg(gjs_argv[1]); + Gjs::AutoUnref input{ + g_file_new_for_commandline_arg(gjs_argv[1])}; if (!g_file_load_contents(input, nullptr, script.out(), &len, nullptr, &error)) { g_printerr("%s\n", error->message); @@ -350,10 +325,23 @@ int main(int argc, char** argv) { if (coverage_prefixes) gjs_coverage_enable(); - GjsAutoUnref js_context(GJS_CONTEXT(g_object_new( - GJS_TYPE_CONTEXT, "search-path", include_path.get(), "program-name", - program_name, "program-path", program_path.get(), "profiler-enabled", - enable_profiler, "exec-as-module", exec_as_module, nullptr))); +#ifdef HAVE_READLINE_READLINE_H + Gjs::AutoChar repl_history_path = gjs_console_get_repl_history_path(); +#else + Gjs::AutoChar repl_history_path = nullptr; +#endif + + Gjs::AutoUnref js_context{GJS_CONTEXT(g_object_new( + GJS_TYPE_CONTEXT, + // clang-format off + "search-path", include_path.get(), + "program-name", program_name, + "program-path", program_path.get(), + "profiler-enabled", enable_profiler, + "exec-as-module", exec_as_module, + "repl-history-path", repl_history_path.get(), + // clang-format on + nullptr))}; env_coverage_output_path = g_getenv("GJS_COVERAGE_OUTPUT"); if (env_coverage_output_path != NULL) { @@ -361,13 +349,13 @@ int main(int argc, char** argv) { coverage_output_path = g_strdup(env_coverage_output_path); } - GjsAutoUnref coverage; + Gjs::AutoUnref coverage; if (coverage_prefixes) { if (!coverage_output_path) g_error("--coverage-output is required when taking coverage statistics"); - GjsAutoUnref output = - g_file_new_for_commandline_arg(coverage_output_path); + Gjs::AutoUnref output{ + g_file_new_for_commandline_arg(coverage_output_path)}; coverage = gjs_coverage_new(coverage_prefixes, js_context, output); } diff --git a/cjs/context-private.h b/cjs/context-private.h index 79064b821..12c204039 100644 --- a/cjs/context-private.h +++ b/cjs/context-private.h @@ -14,10 +14,12 @@ #include #include #include +#include #include // for pair #include #include // for GMemoryMonitor +#include #include #include @@ -37,8 +39,9 @@ #include // for ScriptEnvironmentPreparer #include "gi/closure.h" +#include "cjs/auto.h" #include "cjs/context.h" -#include "cjs/jsapi-util.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util-root.h" #include "cjs/macros.h" #include "cjs/mainloop.h" @@ -65,6 +68,12 @@ class GjsContextPrivate : public JS::JobQueue { using DestroyNotify = void (*)(JSContext*, void* data); private: + struct destroy_data_hash { + size_t operator()(const std::pair& p) const { + return std::hash()(reinterpret_cast(p.second)); + } + }; + GjsContext* m_public_context; JSContext* m_cx; JS::Heap m_main_loop_hook; @@ -77,6 +86,8 @@ class GjsContextPrivate : public JS::JobQueue { char** m_search_path; + char* m_repl_history_path; + unsigned m_auto_gc_id; GjsAtoms* m_atoms; @@ -86,9 +97,10 @@ class GjsContextPrivate : public JS::JobQueue { JobQueueStorage m_job_queue; Gjs::PromiseJobDispatcher m_dispatcher; Gjs::MainLoop m_main_loop; - GjsAutoUnref m_memory_monitor; + Gjs::AutoUnref m_memory_monitor; - std::vector> m_destroy_notifications; + std::unordered_set, destroy_data_hash> + m_destroy_notifications; std::vector m_async_closures; std::unordered_map m_unhandled_rejection_stacks; FunctionVector m_cleanup_tasks; @@ -140,10 +152,11 @@ class GjsContextPrivate : public JS::JobQueue { 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, - const char* identifier, - uint8_t* exit_code, GError** error); + [[nodiscard]] + Gjs::GErrorResult<> handle_exit_code(bool no_sync_error_pending, + const char* source_type, + const char* identifier, + uint8_t* exit_code); [[nodiscard]] bool auto_profile_enter(void); void auto_profile_exit(bool status); @@ -191,6 +204,9 @@ class GjsContextPrivate : public JS::JobQueue { [[nodiscard]] const char* program_name() const { return m_program_name; } void set_program_name(char* value) { m_program_name = value; } GJS_USE const char* program_path(void) const { return m_program_path; } + GJS_USE const char* repl_history_path() const { + return m_repl_history_path; + } void set_program_path(char* value) { m_program_path = value; } void set_search_path(char** value) { m_search_path = value; } void set_should_profile(bool value) { m_should_profile = value; } @@ -198,6 +214,7 @@ class GjsContextPrivate : public JS::JobQueue { void set_should_listen_sigusr2(bool value) { m_should_listen_sigusr2 = value; } + void set_repl_history_path(char* value) { m_repl_history_path = value; } void set_args(std::vector&& args); GJS_JSAPI_RETURN_CONVENTION JSObject* build_args_array(); [[nodiscard]] bool is_owner_thread() const { @@ -219,15 +236,19 @@ class GjsContextPrivate : public JS::JobQueue { return from_cx(cx)->global(); } - [[nodiscard]] bool eval(const char* script, size_t script_len, - const char* filename, int* exit_status_p, - GError** error); + void register_non_module_sourcemap(const char* script, + const char* filename); + + [[nodiscard]] + Gjs::GErrorResult<> eval(const char* script, size_t script_len, + const char* filename, int* exit_status_p); GJS_JSAPI_RETURN_CONVENTION bool eval_with_scope(JS::HandleObject scope_object, const char* script, size_t script_len, const char* filename, JS::MutableHandleValue retval); - [[nodiscard]] bool eval_module(const char* identifier, uint8_t* exit_code_p, - GError** error); + [[nodiscard]] + Gjs::GErrorResult<> eval_module(const char* identifier, + uint8_t* exit_code_p); GJS_JSAPI_RETURN_CONVENTION bool call_function(JS::HandleObject this_obj, JS::HandleValue func_val, const JS::HandleValueArray& args, @@ -243,7 +264,7 @@ class GjsContextPrivate : public JS::JobQueue { // Implementations of JS::JobQueue virtual functions GJS_JSAPI_RETURN_CONVENTION - JSObject* getIncumbentGlobal(JSContext* cx) override; + bool getHostDefinedData(JSContext*, JS::MutableHandleObject) const override; GJS_JSAPI_RETURN_CONVENTION bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, JS::HandleObject job, @@ -269,8 +290,9 @@ class GjsContextPrivate : public JS::JobQueue { void unregister_notifier(DestroyNotify notify_func, void* data); void async_closure_enqueue_for_gc(Gjs::Closure*); - [[nodiscard]] bool register_module(const char* identifier, - const char* filename, GError** error); + [[nodiscard]] + Gjs::GErrorResult<> register_module(const char* identifier, + const char* filename); static void trace(JSTracer* trc, void* data); diff --git a/cjs/context.cpp b/cjs/context.cpp index bcd59ae44..77b45493a 100644 --- a/cjs/context.cpp +++ b/cjs/context.cpp @@ -19,41 +19,47 @@ # include #endif -#include +#ifdef HAVE_READLINE_READLINE_H +# include +#endif + #include // for u16string #include // for get_id #include +#include #include // for move #include #include -#include +#include #include #include -#include // for SystemAllocPolicy +#include // for SystemAllocPolicy #include // for Call, JS_CallFunctionValue -#include // for UndefinedHandleValue +#include // for UndefinedHandleValue #include #include #include #include +#include #include -#include // for StealPendingExceptionStack -#include // for JS_GC, JS_AddExtraGCRootsTr... -#include // for WeakCache -#include // for RootedVector -#include // for CurrentGlobalOrNull -#include // for ExposeObjectToActiveJS +#include // for StealPendingExceptionStack +#include // for JS_GC, JS_AddExtraGCRootsTr... +#include // for WeakCache +#include // for RootedVector +#include // for CurrentGlobalOrNull +#include // for ExposeObjectToActiveJS #include #include -#include // for JobQueue::SavedJobQueue +#include // for JobQueue::SavedJobQueue #include #include // for JSPROP_PERMANENT, JSPROP_RE... #include #include #include #include +#include // for JS_NewStringCopyZ #include #include #include @@ -61,8 +67,9 @@ #include #include #include -#include // for JS_GetFunctionObject, JS_Ge... -#include // for ScriptEnvironmentPreparer +#include // for JS_GetFunctionObject, JS_Ge... +#include // for ScriptEnvironmentPreparer +#include #include // for UniquePtr::get #include "gi/closure.h" // for Closure::Ptr, Closure @@ -70,13 +77,14 @@ #include "gi/object.h" #include "gi/private.h" #include "gi/repo.h" -#include "gi/utils-inl.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/engine.h" #include "cjs/error-types.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/importer.h" #include "cjs/internal.h" @@ -100,6 +108,9 @@ namespace mozilla { union Utf8Unit; } +using Gjs::GErrorResult; +using mozilla::Err, mozilla::Ok; + static void gjs_context_dispose (GObject *object); static void gjs_context_finalize (GObject *object); static void gjs_context_constructed (GObject *object); @@ -151,12 +162,13 @@ enum { PROP_PROFILER_ENABLED, PROP_PROFILER_SIGUSR2, PROP_EXEC_AS_MODULE, + PROP_REPL_HISTORY_PATH }; static GMutex contexts_lock; static GList *all_contexts = NULL; -static GjsAutoChar dump_heap_output; +static Gjs::AutoChar dump_heap_output; static unsigned dump_heap_idle_id = 0; #ifdef G_OS_UNIX @@ -170,8 +182,8 @@ gjs_context_dump_heaps(void) gjs_memory_report("signal handler", false); /* dump to sequential files to allow easier comparisons */ - GjsAutoChar filename = g_strdup_printf("%s.%jd.%u", dump_heap_output.get(), - intmax_t(getpid()), counter); + Gjs::AutoChar filename{g_strdup_printf("%s.%jd.%u", dump_heap_output.get(), + intmax_t(getpid()), counter)}; ++counter; FILE *fp = fopen(filename, "w"); @@ -320,19 +332,35 @@ gjs_context_class_init(GjsContextClass *klass) g_object_class_install_property(object_class, PROP_EXEC_AS_MODULE, pspec); g_param_spec_unref(pspec); - /* For CjsPrivate */ + /** + * GjsContext:repl-history-path: + * + * Set this property to persist repl command history in the console or + * debugger. If NULL, then command history will not be persisted. + */ + pspec = g_param_spec_string( + "repl-history-path", "REPL History Path", + "The writable path to persist repl history", nullptr, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(object_class, PROP_REPL_HISTORY_PATH, + pspec); + g_param_spec_unref(pspec); + + /* For GjsPrivate */ if (!g_getenv("GJS_USE_UNINSTALLED_FILES")) { #ifdef G_OS_WIN32 extern HMODULE gjs_dll; - GjsAutoChar basedir = - g_win32_get_package_installation_directory_of_module(gjs_dll); - GjsAutoChar priv_typelib_dir = g_build_filename( - basedir, "lib", "gjs", "girepository-1.0", nullptr); + Gjs::AutoChar basedir{ + g_win32_get_package_installation_directory_of_module(gjs_dll)}; + Gjs::AutoChar priv_typelib_dir{g_build_filename( + basedir, "lib", "gjs", "girepository-1.0", nullptr)}; #else - GjsAutoChar priv_typelib_dir = - g_build_filename(PKGLIBDIR, "girepository-1.0", nullptr); + Gjs::AutoChar priv_typelib_dir{ + g_build_filename(PKGLIBDIR, "girepository-1.0", nullptr)}; #endif - g_irepository_prepend_search_path(priv_typelib_dir); + GI::Repository repo; + repo.prepend_search_path(priv_typelib_dir); } auto& registry = Gjs::NativeModuleDefineFuncs::get(); registry.add("_promiseNative", gjs_define_native_promise_stuff); @@ -409,13 +437,13 @@ void GjsContextPrivate::free_profiler(void) { void GjsContextPrivate::register_notifier(DestroyNotify notify_func, void* data) { - m_destroy_notifications.push_back({notify_func, data}); + m_destroy_notifications.insert({notify_func, data}); } void GjsContextPrivate::unregister_notifier(DestroyNotify notify_func, void* data) { auto target = std::make_pair(notify_func, data); - Gjs::remove_one_from_unsorted_vector(&m_destroy_notifications, target); + m_destroy_notifications.erase(target); } void GjsContextPrivate::dispose(void) { @@ -487,6 +515,7 @@ GjsContextPrivate::~GjsContextPrivate(void) { g_clear_pointer(&m_search_path, g_strfreev); g_clear_pointer(&m_program_path, g_free); g_clear_pointer(&m_program_name, g_free); + g_clear_pointer(&m_repl_history_path, g_free); } static void @@ -528,6 +557,17 @@ gjs_context_constructed(GObject *object) g_mutex_unlock(&contexts_lock); setup_dump_heap(); + +#ifdef HAVE_READLINE_READLINE_H + const char* path = gjs_context_get_repl_history_path(js_context); + // Populate command history from persisted file + if (path) { + int err = read_history(path); + if (err != 0 && g_getenv("GJS_REPL_HISTORY")) + g_warning("Could not read REPL history file %s: %s", path, + g_strerror(err)); + } +#endif } static bool on_context_module_rejected_log_exception(JSContext* cx, @@ -747,11 +787,15 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) g_error("Failed to define module global in internal global."); } - if (!gjs_load_internal_module(cx, "loader")) { + if (!gjs_load_internal_module(cx, "internalLoader")) { gjs_log_exception(cx); g_error("Failed to load internal module loaders."); } + load_context_module(cx, + "resource:///org/cinnamon/cjs/modules/internal/loader.js", + "module loader"); + { Gjs::AutoMainRealm ar{this}; load_context_module( @@ -801,6 +845,9 @@ gjs_context_get_property (GObject *object, case PROP_PROGRAM_PATH: g_value_set_string(value, gjs->program_path()); break; + case PROP_REPL_HISTORY_PATH: + g_value_set_string(value, gjs->repl_history_path()); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -834,6 +881,9 @@ gjs_context_set_property (GObject *object, case PROP_EXEC_AS_MODULE: gjs->set_execute_as_module(g_value_get_boolean(value)); break; + case PROP_REPL_HISTORY_PATH: + gjs->set_repl_history_path(g_value_dup_string(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -923,7 +973,6 @@ void GjsContextPrivate::on_garbage_collection(JSGCStatus status, JS::GCReason re m_async_closures.shrink_to_fit(); break; case JSGC_END: - m_destroy_notifications.shrink_to_fit(); gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "End garbage collection"); break; default: @@ -956,9 +1005,11 @@ void GjsContextPrivate::stop_draining_job_queue(void) { m_dispatcher.stop(); } -JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) { +bool GjsContextPrivate::getHostDefinedData(JSContext* cx, + JS::MutableHandleObject data) const { // This is equivalent to SpiderMonkey's behavior. - return JS::CurrentGlobalOrNull(cx); + data.set(JS::CurrentGlobalOrNull(cx)); + return true; } // See engine.cpp and JS::SetJobQueue(). @@ -1267,6 +1318,13 @@ gjs_context_get_native_context (GjsContext *js_context) return gjs->context(); } +static inline bool result_to_c(GErrorResult<> result, GError** error_out) { + if (result.isOk()) + return true; + *error_out = result.unwrapErr().release(); + return false; +} + bool gjs_context_eval(GjsContext *js_context, const char *script, @@ -1279,20 +1337,22 @@ gjs_context_eval(GjsContext *js_context, size_t real_len = script_len < 0 ? strlen(script) : script_len; - GjsAutoUnref js_context_ref(js_context, GjsAutoTakeOwnership()); - + Gjs::AutoUnref js_context_ref{js_context, Gjs::TakeOwnership{}}; GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); - return gjs->eval(script, real_len, filename, exit_status_p, error); + + gjs->register_non_module_sourcemap(script, filename); + return result_to_c(gjs->eval(script, real_len, filename, exit_status_p), + error); } bool gjs_context_eval_module(GjsContext* js_context, const char* identifier, uint8_t* exit_code, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); - GjsAutoUnref js_context_ref(js_context, GjsAutoTakeOwnership()); + Gjs::AutoUnref js_context_ref{js_context, Gjs::TakeOwnership{}}; GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); - return gjs->eval_module(identifier, exit_code, error); + return result_to_c(gjs->eval_module(identifier, exit_code), error); } bool gjs_context_register_module(GjsContext* js_context, const char* identifier, @@ -1301,7 +1361,7 @@ bool gjs_context_register_module(GjsContext* js_context, const char* identifier, GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); - return gjs->register_module(identifier, uri, error); + return result_to_c(gjs->register_module(identifier, uri), error); } bool GjsContextPrivate::auto_profile_enter() { @@ -1323,57 +1383,61 @@ void GjsContextPrivate::auto_profile_exit(bool auto_profile) { gjs_profiler_stop(m_profiler); } -bool GjsContextPrivate::handle_exit_code(bool no_sync_error_pending, - const char* source_type, - const char* identifier, - uint8_t* exit_code, GError** error) { +GErrorResult<> GjsContextPrivate::handle_exit_code(bool no_sync_error_pending, + const char* source_type, + const char* identifier, + uint8_t* exit_code) { uint8_t code; if (should_exit(&code)) { /* exit_status_p is public API so can't be changed, but should be * uint8_t, not int */ - g_set_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_SYSTEM_EXIT, "Exit with code %d", code); *exit_code = code; - return false; // Don't log anything + return Err(error.release()); // Don't log anything } // Once the main loop exits an exception could // be pending even if the script returned // true synchronously if (JS_IsExceptionPending(m_cx)) { - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); gjs_log_exception_uncaught(m_cx); *exit_code = 1; - return false; + return Err(error.release()); } if (m_unhandled_exception) { - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); *exit_code = 1; - return false; + return Err(error.release()); } // Assume success if no error was thrown and should exit isn't // set if (no_sync_error_pending) { *exit_code = 0; - return true; + return Ok{}; } g_critical("%s %s terminated with an uncatchable exception", source_type, identifier); - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "%s %s terminated with an uncatchable exception", source_type, identifier); gjs_log_exception_uncaught(m_cx); /* No exit code from script, but we don't want to exit(0) */ *exit_code = 1; - return false; + return Err(error.release()); } bool GjsContextPrivate::set_main_loop_hook(JSObject* callable) { @@ -1400,9 +1464,43 @@ bool GjsContextPrivate::run_main_loop_hook() { JS::HandleValueArray::empty(), &ignored_rval); } -bool GjsContextPrivate::eval(const char* script, size_t script_len, - const char* filename, int* exit_status_p, - GError** error) { +// source maps parsing is built upon hooks for resolving or loading modules +// for non-module runs we need to invoke this logic manually +void GjsContextPrivate::register_non_module_sourcemap(const char* script, + const char* filename) { + using AutoURI = Gjs::AutoPointer; + Gjs::AutoMainRealm ar{this}; + + JS::RootedObject global{m_cx, JS::CurrentGlobalOrNull(m_cx)}; + JS::RootedValue v_loader{ + m_cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)}; + g_assert(v_loader.isObject()); + JS::RootedObject v_loader_obj{m_cx, &v_loader.toObject()}; + + JS::RootedValueArray<3> args{m_cx}; + JS::RootedString script_str{m_cx, JS_NewStringCopyZ(m_cx, script)}; + JS::RootedString file_name_str{m_cx, JS_NewStringCopyZ(m_cx, filename)}; + args[0].setString(script_str); + args[1].setString(file_name_str); + + // if the file uri is not an absolute uri with a scheme, build one assuming + // file:// this parameter is used to help locate non-inlined source map file + AutoURI uri = g_uri_parse(filename, G_URI_FLAGS_NONE, nullptr); + if (!uri) { + Gjs::AutoUnref file = g_file_new_for_path(filename); + Gjs::AutoChar file_uri = g_file_get_uri(file); + JS::RootedString abs_filename_scheme_str{ + m_cx, JS_NewStringCopyZ(m_cx, file_uri.get())}; + args[2].setString(abs_filename_scheme_str); + } + + JS::RootedValue ignored{m_cx}; + JS::Call(m_cx, v_loader_obj, "populateSourceMap", args, &ignored); +} + +GErrorResult<> GjsContextPrivate::eval(const char* script, size_t script_len, + const char* filename, + int* exit_status_p) { AutoResetExit reset(this); bool auto_profile = auto_profile_enter(); @@ -1451,10 +1549,10 @@ bool GjsContextPrivate::eval(const char* script, size_t script_len, auto_profile_exit(auto_profile); uint8_t out_code; - ok = handle_exit_code(ok, "Script", filename, &out_code, error); + GErrorResult<> result = handle_exit_code(ok, "Script", filename, &out_code); if (exit_status_p) { - if (ok && retval.isInt32()) { + if (result.isOk() && retval.isInt32()) { int code = retval.toInt32(); gjs_debug(GJS_DEBUG_CONTEXT, "Script returned integer code %d", code); @@ -1464,11 +1562,11 @@ bool GjsContextPrivate::eval(const char* script, size_t script_len, } } - return ok; + return result; } -bool GjsContextPrivate::eval_module(const char* identifier, - uint8_t* exit_status_p, GError** error) { +GErrorResult<> GjsContextPrivate::eval_module(const char* identifier, + uint8_t* exit_status_p) { AutoResetExit reset(this); bool auto_profile = auto_profile_enter(); @@ -1479,22 +1577,24 @@ bool GjsContextPrivate::eval_module(const char* identifier, JS::RootedId key(m_cx, gjs_intern_string_to_id(m_cx, identifier)); JS::RootedObject obj(m_cx); if (!gjs_global_registry_get(m_cx, registry, key, &obj) || !obj) { - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Cannot load module with identifier: '%s'", identifier); if (exit_status_p) *exit_status_p = 1; - return false; + return Err(error.release()); } if (!JS::ModuleLink(m_cx, obj)) { gjs_log_exception(m_cx); - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Failed to resolve imports for module: '%s'", identifier); if (exit_status_p) *exit_status_p = 1; - return false; + return Err(error.release()); } JS::RootedValue evaluation_promise(m_cx); @@ -1541,19 +1641,20 @@ bool GjsContextPrivate::eval_module(const char* identifier, auto_profile_exit(auto_profile); uint8_t out_code; - ok = handle_exit_code(ok, "Module", identifier, &out_code, error); + GErrorResult<> result = + handle_exit_code(ok, "Module", identifier, &out_code); if (exit_status_p) *exit_status_p = out_code; - return ok; + return result; } -bool GjsContextPrivate::register_module(const char* identifier, const char* uri, - GError** error) { +GErrorResult<> GjsContextPrivate::register_module(const char* identifier, + const char* uri) { Gjs::AutoMainRealm ar{this}; if (gjs_module_load(m_cx, identifier, uri)) - return true; + return Ok{}; const char* msg = "unknown"; JS::ExceptionStack exn_stack(m_cx); @@ -1566,11 +1667,12 @@ bool GjsContextPrivate::register_module(const char* identifier, const char* uri, JS_ClearPendingException(m_cx); } - g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, + Gjs::AutoError error; + g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Failed to parse module '%s': %s", identifier, msg ? msg : "unknown"); - return false; + return Err(error.release()); } bool @@ -1579,9 +1681,9 @@ gjs_context_eval_file(GjsContext *js_context, int *exit_status_p, GError **error) { - GjsAutoChar script; + Gjs::AutoChar script; size_t script_len; - GjsAutoUnref file = g_file_new_for_commandline_arg(filename); + Gjs::AutoUnref file{g_file_new_for_commandline_arg(filename)}; if (!g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr, error)) @@ -1593,8 +1695,8 @@ gjs_context_eval_file(GjsContext *js_context, bool gjs_context_eval_module_file(GjsContext* js_context, const char* filename, uint8_t* exit_status_p, GError** error) { - GjsAutoUnref file = g_file_new_for_commandline_arg(filename); - GjsAutoChar uri = g_file_get_uri(file); + Gjs::AutoUnref file{g_file_new_for_commandline_arg(filename)}; + Gjs::AutoChar uri{g_file_get_uri(file)}; return gjs_context_register_module(js_context, uri, uri, error) && gjs_context_eval_module(js_context, uri, exit_status_p, error); @@ -1632,7 +1734,7 @@ bool GjsContextPrivate::eval_with_scope(JS::HandleObject scope_object, if (!buf.init(m_cx, source, source_len, JS::SourceOwnership::Borrowed)) return false; - JS::RootedObjectVector scope_chain(m_cx); + JS::EnvironmentChain scope_chain{m_cx, JS::SupportUnscopables::No}; if (!scope_chain.append(eval_obj)) { JS_ReportOutOfMemory(m_cx); return false; @@ -1641,8 +1743,8 @@ bool GjsContextPrivate::eval_with_scope(JS::HandleObject scope_object, JS::CompileOptions options(m_cx); options.setFileAndLine(filename, 1).setNonSyntacticScope(true); - GjsAutoUnref file = g_file_new_for_commandline_arg(filename); - GjsAutoChar uri = g_file_get_uri(file); + Gjs::AutoUnref file{g_file_new_for_commandline_arg(filename)}; + Gjs::AutoChar uri{g_file_get_uri(file)}; JS::RootedObject priv(m_cx, gjs_script_module_build_private(m_cx, uri)); if (!priv) return false; @@ -1823,6 +1925,17 @@ gjs_get_js_version(void) return JS_GetImplementationVersion(); } +/** + * gjs_context_get_repl_history_path: + * + * Returns the path property for persisting REPL command history + * + * Returns: a string + */ +const char* gjs_context_get_repl_history_path(GjsContext* self) { + return GjsContextPrivate::from_object(self)->repl_history_path(); +} + /** * gjs_context_run_in_realm: * @self: the #GjsContext diff --git a/cjs/context.h b/cjs/context.h index bbb97abf0..519d14b64 100644 --- a/cjs/context.h +++ b/cjs/context.h @@ -15,10 +15,6 @@ #include #include /* for ssize_t */ -#ifndef _WIN32 -# include /* for siginfo_t */ -#endif - #include #include @@ -106,6 +102,7 @@ GJS_EXPORT GJS_USE const char* gjs_get_js_version(void); GJS_EXPORT void gjs_context_setup_debugger_console(GjsContext* gjs); +GJS_EXPORT GJS_USE const char* gjs_context_get_repl_history_path(GjsContext*); G_END_DECLS #endif /* GJS_CONTEXT_H_ */ diff --git a/cjs/coverage.cpp b/cjs/coverage.cpp index c52010189..4f0a014e3 100644 --- a/cjs/coverage.cpp +++ b/cjs/coverage.cpp @@ -23,15 +23,22 @@ #include #include // for EnableCodeCoverage #include // for JS_WrapObject +#include +#include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/coverage.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" +using Gjs::GErrorResult; +using mozilla::Err, mozilla::Ok; + static bool s_coverage_enabled = false; struct _GjsCoverage { @@ -68,26 +75,35 @@ static GParamSpec *properties[PROP_N] = { NULL, }; return path; } -[[nodiscard]] static bool write_source_file_header(GOutputStream* stream, - GFile* source_file, - GError** error) { - GjsAutoChar path = get_file_identifier(source_file); - return g_output_stream_printf(stream, NULL, NULL, error, "SF:%s\n", path.get()); +[[nodiscard]] +static GErrorResult<> write_source_file_header(GOutputStream* stream, + GFile* source_file) { + Gjs::AutoChar path{get_file_identifier(source_file)}; + Gjs::AutoError error; + if (!g_output_stream_printf(stream, nullptr, nullptr, error.out(), + "SF:%s\n", path.get())) + return Err(error.release()); + return Ok{}; } -[[nodiscard]] static bool copy_source_file_to_coverage_output( - GFile* source_file, GFile* destination_file, GError** error) { +[[nodiscard]] +static GErrorResult<> copy_source_file_to_coverage_output( + GFile* source_file, GFile* destination_file) { /* We need to recursively make the directory we * want to copy to, as g_file_copy doesn't do that */ - GjsAutoUnref destination_dir = g_file_get_parent(destination_file); - if (!g_file_make_directory_with_parents(destination_dir, NULL, error)) { - if (!g_error_matches(*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) - return false; - g_clear_error(error); + Gjs::AutoError error; + Gjs::AutoUnref destination_dir{g_file_get_parent(destination_file)}; + if (!g_file_make_directory_with_parents(destination_dir, nullptr, + error.out())) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + return Err(error.release()); + error.reset(); } - return g_file_copy(source_file, destination_file, G_FILE_COPY_OVERWRITE, - nullptr, nullptr, nullptr, error); + if (!g_file_copy(source_file, destination_file, G_FILE_COPY_OVERWRITE, + nullptr, nullptr, nullptr, error.out())) + return Err(error.release()); + return Ok{}; } /* This function will strip a URI scheme and return @@ -129,7 +145,7 @@ static GParamSpec *properties[PROP_N] = { NULL, }; */ [[nodiscard]] static char* find_diverging_child_components(GFile* child, GFile* parent) { - GjsAutoUnref ancestor(parent, GjsAutoTakeOwnership()); + Gjs::AutoUnref ancestor{parent, Gjs::TakeOwnership{}}; while (ancestor) { char *relpath = g_file_get_relative_path(ancestor, child); if (relpath) @@ -141,53 +157,57 @@ static GParamSpec *properties[PROP_N] = { NULL, }; /* This is a special case of getting the URI below. The difference is that * this gives you a regular path name; getting it through the URI would * give a URI-encoded path (%20 for spaces, etc.) */ - GjsAutoUnref root = g_file_new_for_path("/"); + Gjs::AutoUnref root{g_file_new_for_path("/")}; char* child_path = g_file_get_relative_path(root, child); if (child_path) return child_path; - GjsAutoChar child_uri = g_file_get_uri(child); + Gjs::AutoChar child_uri{g_file_get_uri(child)}; return strip_uri_scheme(child_uri); } [[nodiscard]] static bool filename_has_coverage_prefixes(GjsCoverage* self, const char* filename) { auto priv = static_cast(gjs_coverage_get_instance_private(self)); - GjsAutoChar workdir = g_get_current_dir(); - GjsAutoChar abs_filename = g_canonicalize_filename(filename, workdir); + Gjs::AutoChar workdir{g_get_current_dir()}; + Gjs::AutoChar abs_filename{g_canonicalize_filename(filename, workdir)}; for (const char * const *prefix = priv->prefixes; *prefix; prefix++) { - GjsAutoChar abs_prefix = g_canonicalize_filename(*prefix, workdir); + Gjs::AutoChar abs_prefix{g_canonicalize_filename(*prefix, workdir)}; if (g_str_has_prefix(abs_filename, abs_prefix)) return true; } return false; } -static inline bool -write_line(GOutputStream *out, - const char *line, - GError **error) -{ - return g_output_stream_printf(out, nullptr, nullptr, error, "%s\n", line); +[[nodiscard]] +static inline GErrorResult<> write_line(GOutputStream* out, const char* line) { + Gjs::AutoError error; + if (!g_output_stream_printf(out, nullptr, nullptr, error.out(), "%s\n", + line)) + return Err(error.release()); + return Ok{}; } -[[nodiscard]] static GjsAutoUnref write_statistics_internal( - GjsCoverage* coverage, JSContext* cx, GError** error) { +[[nodiscard]] +GErrorResult> write_statistics_internal( + GjsCoverage* coverage, JSContext* cx) { if (!s_coverage_enabled) { g_critical( "Code coverage requested, but gjs_coverage_enable() was not called." " You must call this function before creating any GjsContext."); - return nullptr; + return Gjs::AutoUnref{}; } GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); /* Create output directory if it doesn't exist */ - if (!g_file_make_directory_with_parents(priv->output_dir, nullptr, error)) { - if (!g_error_matches(*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) - return nullptr; - g_clear_error(error); + Gjs::AutoError error; + if (!g_file_make_directory_with_parents(priv->output_dir, nullptr, + error.out())) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + return Err(error.release()); + error.reset(); } GFile *output_file = g_file_get_child(priv->output_dir, "coverage.lcov"); @@ -195,15 +215,12 @@ write_line(GOutputStream *out, size_t lcov_length; JS::UniqueChars lcov = js::GetCodeCoverageSummary(cx, &lcov_length); - GjsAutoUnref ostream = - G_OUTPUT_STREAM(g_file_append_to(output_file, - G_FILE_CREATE_NONE, - NULL, - error)); + Gjs::AutoUnref ostream{G_OUTPUT_STREAM(g_file_append_to( + output_file, G_FILE_CREATE_NONE, nullptr, error.out()))}; if (!ostream) - return nullptr; + return Err(error.release()); - GjsAutoStrv lcov_lines = g_strsplit(lcov.get(), "\n", -1); + Gjs::AutoStrv lcov_lines{g_strsplit(lcov.get(), "\n", -1)}; const char* test_name = NULL; bool ignoring_file = false; @@ -227,32 +244,30 @@ write_line(GOutputStream *out, } /* Now we can write the test name before writing the source file */ - if (!write_line(ostream, test_name, error)) - return nullptr; + MOZ_TRY(write_line(ostream, test_name)); /* The source file could be a resource, so we must use * g_file_new_for_commandline_arg() to disambiguate between URIs and * filesystem paths. */ - GjsAutoUnref source_file = g_file_new_for_commandline_arg(filename); - GjsAutoChar diverged_paths = - find_diverging_child_components(source_file, priv->output_dir); - GjsAutoUnref destination_file = - g_file_resolve_relative_path(priv->output_dir, diverged_paths); - if (!copy_source_file_to_coverage_output(source_file, destination_file, error)) - return nullptr; + Gjs::AutoUnref source_file{ + g_file_new_for_commandline_arg(filename)}; + Gjs::AutoChar diverged_paths{ + find_diverging_child_components(source_file, priv->output_dir)}; + Gjs::AutoUnref destination_file{ + g_file_resolve_relative_path(priv->output_dir, diverged_paths)}; + MOZ_TRY(copy_source_file_to_coverage_output(source_file, + destination_file)); /* Rewrite the source file path to be relative to the output * dir so that genhtml will find it */ - if (!write_source_file_header(ostream, destination_file, error)) - return nullptr; + MOZ_TRY(write_source_file_header(ostream, destination_file)); continue; } - if (!write_line(ostream, *iter, error)) - return nullptr; + MOZ_TRY(write_line(ostream, *iter)); } - return output_file; + return Gjs::AutoUnref{output_file}; } /** @@ -273,18 +288,18 @@ void gjs_coverage_write_statistics(GjsCoverage *coverage) { auto priv = static_cast(gjs_coverage_get_instance_private(coverage)); - GjsAutoError error; - auto cx = static_cast(gjs_context_get_native_context(priv->context)); 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); + GErrorResult> result{ + write_statistics_internal(coverage, cx)}; + if (result.isErr()) { + g_critical("Error writing coverage data: %s", + result.inspectErr()->message); return; } - GjsAutoChar output_file_path = g_file_get_path(output_file); + Gjs::AutoChar output_file_path{g_file_get_path(result.unwrap())}; g_message("Wrote coverage statistics to %s", output_file_path.get()); } diff --git a/cjs/debugger.cpp b/cjs/debugger.cpp index 75aff4fd9..54add3ea4 100644 --- a/cjs/debugger.cpp +++ b/cjs/debugger.cpp @@ -15,6 +15,7 @@ #include #include +#include // for ReportUncatchableException #include #include #include @@ -26,12 +27,14 @@ #include // for JS_WrapObject #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/global.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" +#include "cjs/module.h" #include "util/console.h" @@ -45,7 +48,8 @@ static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); gjs->exit(exitcode); - return false; // without gjs_throw() == "throw uncatchable exception" + JS::ReportUncatchableException(cx); + return false; } GJS_JSAPI_RETURN_CONVENTION @@ -56,7 +60,7 @@ static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) { if (!gjs_parse_call_args(cx, "readline", args, "|s", "prompt", &prompt)) return false; - GjsAutoChar line; + Gjs::AutoChar line; do { const char* real_prompt = prompt ? prompt.get() : "db> "; #ifdef HAVE_READLINE_READLINE_H @@ -93,16 +97,33 @@ static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) { /* Add line to history and convert it to a JSString so that we can pass it * back as the return value */ #ifdef HAVE_READLINE_READLINE_H + GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); add_history(line); + gjs_console_write_repl_history(gjs->repl_history_path()); #endif args.rval().setString(JS_NewStringCopyZ(cx, line)); return true; } +static bool get_source_map_registry(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); + + JS::RootedObject registry{cx, gjs_get_source_map_registry(gjs->global())}; + if (!JS_WrapObject(cx, ®istry)) { + gjs_log_exception(cx); + return false; + } + args.rval().setObject(*registry); + return true; +} + // clang-format off static JSFunctionSpec debugger_funcs[] = { JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS), JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS), + JS_FN("getSourceMapRegistry", get_source_map_registry, 0, GJS_MODULE_PROP_FLAGS), JS_FS_END }; // clang-format on diff --git a/cjs/deprecation.cpp b/cjs/deprecation.cpp index fca66ff70..c53adc296 100644 --- a/cjs/deprecation.cpp +++ b/cjs/deprecation.cpp @@ -62,7 +62,7 @@ struct DeprecationEntry { std::string loc; DeprecationEntry(GjsDeprecationMessageId an_id, const char* a_loc) - : id(an_id), loc(a_loc) {} + : id(an_id), loc(a_loc ? a_loc : "unknown") {} bool operator==(const DeprecationEntry& other) const { return id == other.id && loc == other.loc; @@ -81,10 +81,11 @@ struct hash { static std::unordered_set logged_messages; GJS_JSAPI_RETURN_CONVENTION -static JS::UniqueChars get_callsite(JSContext* cx, unsigned max_frames) { +static JS::UniqueChars get_callsite(JSContext* cx, + unsigned max_frames /* = 1 */) { JS::RootedObject stack_frame(cx); if (!JS::CaptureCurrentStack(cx, &stack_frame, - JS::StackCapture(JS::MaxFrames(max_frames))) || + JS::StackCapture{JS::MaxFrames{max_frames}}) || !stack_frame) return nullptr; @@ -99,8 +100,8 @@ static JS::UniqueChars get_callsite(JSContext* cx, unsigned max_frames) { static void warn_deprecated_unsafe_internal(JSContext* cx, const GjsDeprecationMessageId id, const char* msg, - unsigned max_frames) { - JS::UniqueChars callsite(get_callsite(cx, max_frames)); + unsigned max_frames /* = 1 */) { + JS::UniqueChars callsite{get_callsite(cx, max_frames)}; DeprecationEntry entry(id, callsite.get()); if (!logged_messages.count(entry)) { JS::UniqueChars stack_dump = @@ -121,8 +122,7 @@ void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, void _gjs_warn_deprecated_once_per_callsite( JSContext* cx, GjsDeprecationMessageId id, - const std::vector& args, - unsigned max_frames) { + const std::vector& args, unsigned max_frames) { // In C++20, use std::format() for this std::string_view format_string{messages[id]}; std::stringstream message; @@ -152,5 +152,6 @@ void _gjs_warn_deprecated_once_per_callsite( message << format_string.substr(copied, std::string::npos); std::string message_formatted = message.str(); - warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str(), max_frames); + warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str(), + max_frames); } diff --git a/cjs/deprecation.h b/cjs/deprecation.h index 7a7678030..5de252bb4 100644 --- a/cjs/deprecation.h +++ b/cjs/deprecation.h @@ -7,6 +7,7 @@ #include +#include #include struct JSContext; @@ -26,6 +27,6 @@ void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, void _gjs_warn_deprecated_once_per_callsite( JSContext* cx, GjsDeprecationMessageId id, - const std::vector& args, unsigned max_frames = 1); + const std::vector& args, unsigned max_frames = 1); #endif // GJS_DEPRECATION_H_ diff --git a/cjs/engine.cpp b/cjs/engine.cpp index 10543e10a..17b86b2ed 100644 --- a/cjs/engine.cpp +++ b/cjs/engine.cpp @@ -32,8 +32,10 @@ #include // for Atomic in JSPrincipals #include +#include "gi/gerror.h" #include "cjs/context-private.h" #include "cjs/engine.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/profiler-private.h" #include "util/log.h" @@ -75,7 +77,7 @@ static void on_cleanup_finalization_registry(JSFunction* cleanup_task, bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src, size_t* length) { - GjsAutoError error; + Gjs::AutoError error; const char* path = filename + 11; // len("resource://") GBytes* script_bytes = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); @@ -180,9 +182,19 @@ JSPrincipals* get_internal_principals() { static const JSSecurityCallbacks security_callbacks = { /* contentSecurityPolicyAllows = */ nullptr, + /* codeForEvalGets = */ nullptr, &ModuleLoaderPrincipals::subsumes, }; +static bool instance_class_is_error(const JSClass* klass) { + return klass == &ErrorBase::klass; +} + +static const js::DOMCallbacks dom_callbacks = { + /* instanceClassHasProtoAtDepth = */ nullptr, + &instance_class_is_error, +}; + JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) { g_assert(gjs_is_inited); JSContext *cx = JS_NewContext(32 * 1024 * 1024 /* max bytes */); @@ -213,6 +225,7 @@ JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) { uninitialized_gjs); JS::SetHostCleanupFinalizationRegistryCallback( cx, on_cleanup_finalization_registry, uninitialized_gjs); + js::SetDOMCallbacks(cx, &dom_callbacks); // We use this to handle "lazy sources" that SpiderMonkey doesn't need to // keep in memory. Most sources should be kept in memory, but we can skip diff --git a/cjs/gerror-result.h b/cjs/gerror-result.h new file mode 100644 index 000000000..446a6c879 --- /dev/null +++ b/cjs/gerror-result.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2018-2020 Canonical, Ltd + +#pragma once + +#include + +#include + +#include +#include // IWYU pragma: keep (see Result::Impl) + +#include "cjs/auto.h" + +namespace mozilla { +namespace detail { +template +class ResultImplementation; +} +} // namespace mozilla + +// Auto pointer type for GError, as well as a Result type that can be used as a +// type-safe return type for fallible GNOME-platform operations. +// To indicate success, return Ok{}, and to indicate failure, return Err(error) +// or Err(error.release()) if using AutoError. +// When calling a function that returns GErrorResult inside another function +// that returns GErrorResult, you can use the MOZ_TRY() macro as if it were the +// Rust ? operator, to bail out on error even if the GErrorResult's success +// types are different. + +// COMPAT: We use Mozilla's Result type because std::expected does not appear in +// the standard library until C++23. + +namespace Gjs { + +struct AutoErrorFuncs { + static GError* error_copy(GError* error) { return g_error_copy(error); } +}; + +// TODO(ptomato): AutoError could be more strict. Once we have an owned GError, +// we probably don't need to be able to copy it. Note that Err(auto_error) with +// current strictness is incorrect; you need to do Err(auto_error.release()). +// https://gitlab.gnome.org/GNOME/gjs/-/issues/654 +struct AutoError + : AutoPointer { + 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(); + } +}; + +template <> +struct SmartPointer : AutoError { + using AutoError::AutoError; + using AutoError::operator=; + using AutoError::operator&; +}; + +template +using GErrorResult = mozilla::Result; + +} // namespace Gjs + +namespace mozilla { +namespace detail { +// Custom packing for GErrorResult<> +template <> +class SelectResultImpl { + public: + class Type { + Gjs::AutoError m_value; + + public: + explicit constexpr Type(Ok) : m_value() {} + explicit constexpr Type(GError* error) : m_value(error) {} + constexpr Type(Type&& other) : m_value(other.m_value.release()) {} + Type& operator=(Type&& other) { + m_value = other.m_value.release(); + return *this; + } + constexpr bool isOk() const { return !m_value; } + constexpr const Ok inspect() const { return {}; } + constexpr Ok unwrap() { return {}; } + constexpr const GError* inspectErr() const { return m_value.get(); } + Gjs::AutoError unwrapErr() { return m_value.release(); } + }; +}; + +// Packing for any other pointer. Unlike in SpiderMonkey, GLib-allocated +// pointers may not be aligned, so their bottom bit cannot be used for a flag. +template +class SelectResultImpl { + public: + using Type = ResultImplementation; +}; + +} // namespace detail +} // namespace mozilla diff --git a/cjs/global.cpp b/cjs/global.cpp index 9e5ccf037..cdfbcddb8 100644 --- a/cjs/global.cpp +++ b/cjs/global.cpp @@ -33,6 +33,7 @@ #include // for JS_IdToValue, JS_InitReflectParse #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/global.h" @@ -90,9 +91,9 @@ class GjsBaseGlobal { GJS_JSAPI_RETURN_CONVENTION static bool run_bootstrap(JSContext* cx, const char* bootstrap_script, JS::HandleObject global) { - GjsAutoChar uri = g_strdup_printf( + Gjs::AutoChar uri{g_strdup_printf( "resource:///org/cinnamon/cjs/modules/script/_bootstrap/%s.js", - bootstrap_script); + bootstrap_script)}; JSAutoRealm ar(cx, global); @@ -205,6 +206,13 @@ class GjsGlobal : GjsBaseGlobal { gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY, JS::ObjectValue(*module_registry)); + JS::RootedObject source_map_registry{cx, JS::NewMapObject(cx)}; + if (!source_map_registry) + return false; + + gjs_set_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY, + JS::ObjectValue(*source_map_registry)); + JS::Value v_importer = gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS); g_assert(((void) "importer should be defined before passing null " @@ -282,6 +290,8 @@ class GjsInternalGlobal : GjsBaseGlobal { JS_FN("compileInternalModule", gjs_internal_compile_internal_module, 2, 0), JS_FN("getRegistry", gjs_internal_get_registry, 1, 0), + JS_FN("getSourceMapRegistry", gjs_internal_get_source_map_registry, 1, + 0), JS_FN("loadResourceOrFile", gjs_internal_load_resource_or_file, 1, 0), JS_FN("loadResourceOrFileAsync", gjs_internal_load_resource_or_file_async, 1, 0), @@ -292,6 +302,7 @@ class GjsInternalGlobal : GjsBaseGlobal { 0), JS_FN("setModulePrivate", gjs_internal_set_module_private, 2, 0), JS_FN("uriExists", gjs_internal_uri_exists, 1, 0), + JS_FN("atob", gjs_internal_atob, 1, 0), JS_FS_END}; static constexpr JSClass klass = { @@ -339,6 +350,13 @@ class GjsInternalGlobal : GjsBaseGlobal { gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY, JS::ObjectValue(*module_registry)); + JS::RootedObject source_map_registry{cx, JS::NewMapObject(cx)}; + if (!source_map_registry) + return false; + + gjs_set_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY, + JS::ObjectValue(*source_map_registry)); + return JS_DefineFunctions(cx, global, static_funcs); } }; @@ -383,11 +401,10 @@ JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type, /** * gjs_global_is_type: + * @cx: the current #JSContext + * @type: the global type to test for * - * @param cx the current #JSContext - * @param type the global type to test for - * - * @returns whether the current global is the same type as #type + * Returns: whether the current global is the same type as @type */ bool gjs_global_is_type(JSContext* cx, GjsGlobalType type) { JSObject* global = JS::CurrentGlobalOrNull(cx); @@ -427,16 +444,17 @@ GjsGlobalType gjs_global_get_type(JSObject* global) { /** * gjs_global_registry_set: + * @cx: the current #JSContext + * @registry: a JS Map object + * @key: a module identifier, typically a string or symbol + * @module: a module object + * + * This function inserts a module object into a global registry. 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. * - * @brief This function inserts a module object into a global registry. - * 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. - - * @param cx the current #JSContext - * @param registry a JS Map object - * @param key a module identifier, typically a string or symbol - * @param module a module object + * Returns: false if an exception is pending, otherwise true. */ bool gjs_global_registry_set(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::HandleObject module) { @@ -457,16 +475,16 @@ bool gjs_global_registry_set(JSContext* cx, JS::HandleObject registry, /** * gjs_global_registry_get: + * @cx: the current #JSContext + * @registry: a JS Map object + * @key: a module identifier, typically a string or symbol + * @module_out: (out): handle where a module object will be stored + * + * 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. * - * @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. - - * @param cx the current #JSContext - * @param registry a JS Map object - * @param key a module identifier, typically a string or symbol - * @param module a module object + * Returns: false if an exception is pending, otherwise true. */ bool gjs_global_registry_get(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, @@ -488,6 +506,39 @@ bool gjs_global_registry_get(JSContext* cx, JS::HandleObject registry, return true; } +/** + * gjs_global_source_map_get: + * @cx: the current #JSContext + * @registry: a JS Map object + * @key: a source string, such as retrieved from a stack frame + * @source_map_consumer_obj: handle where a source map consumer object will be + * stored + * + * This function retrieves a source map consumer from the source map registry, + * or null if the source does not have a source map consumer. + * + * Returns: false if an exception is pending, otherwise true. + */ +bool gjs_global_source_map_get( + JSContext* cx, JS::HandleObject registry, JS::HandleString key, + JS::MutableHandleObject source_map_consumer_obj) { + JS::RootedValue v_key{cx, JS::StringValue(key)}; + JS::RootedValue v_value{cx}; + if (!JS::MapGet(cx, registry, v_key, &v_value)) + return false; + + g_assert((v_value.isUndefined() || v_value.isObject()) && + "Invalid value in source map registry"); + + if (v_value.isObject()) { + source_map_consumer_obj.set(&v_value.toObject()); + return true; + } + + source_map_consumer_obj.set(nullptr); + return true; +} + /** * gjs_define_global_properties: * @cx: a #JSContext diff --git a/cjs/global.h b/cjs/global.h index a1d02e955..f37e8c427 100644 --- a/cjs/global.h +++ b/cjs/global.h @@ -41,6 +41,8 @@ enum class GjsGlobalSlot : uint32_t { MODULE_LOADER, // Stores the module registry (a Map object) MODULE_REGISTRY, + // Stores the source map registry (a Map object) + SOURCE_MAP_REGISTRY, NATIVE_REGISTRY, // prettyPrint() function defined in JS but used internally in C++ PRETTY_PRINT_FUNC, @@ -81,6 +83,11 @@ bool gjs_global_registry_get(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::MutableHandleObject value); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_global_source_map_get(JSContext* cx, JS::HandleObject registry, + JS::HandleString key, + JS::MutableHandleObject value); + GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type, JS::HandleObject existing_global = nullptr); diff --git a/cjs/importer.cpp b/cjs/importer.cpp index 379a35e04..9ee01c323 100644 --- a/cjs/importer.cpp +++ b/cjs/importer.cpp @@ -42,7 +42,9 @@ #include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/importer.h" #include "cjs/jsapi-util.h" @@ -67,7 +69,7 @@ importer_to_string(JSContext *cx, { GJS_GET_THIS(cx, argc, vp, args, importer); - GjsAutoChar output; + Gjs::AutoChar output; const JSClass* klass = JS::GetClass(importer); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); @@ -141,7 +143,7 @@ define_meta_properties(JSContext *context, &parent_module_path)) return false; - GjsAutoChar module_path_buf; + Gjs::AutoChar module_path_buf; if (parent_module_path.isNull()) { module_path_buf = g_strdup(module_name); } else { @@ -154,8 +156,8 @@ define_meta_properties(JSContext *context, if (!gjs_string_from_utf8(context, module_path_buf, &module_path)) return false; - GjsAutoChar to_string_tag_buf = g_strdup_printf("GjsModule %s", - module_path_buf.get()); + Gjs::AutoChar to_string_tag_buf{ + g_strdup_printf("GjsModule %s", module_path_buf.get())}; if (!gjs_string_from_utf8(context, to_string_tag_buf, &to_string_tag)) return false; } else { @@ -305,12 +307,12 @@ import_module_init(JSContext *context, JS::HandleObject module_obj) { gsize script_len = 0; - GjsAutoError error; + Gjs::AutoError error; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); JS::RootedValue ignored(context); - GjsAutoChar script; + Gjs::AutoChar script; if (!g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr, &error)) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) && @@ -324,7 +326,7 @@ import_module_init(JSContext *context, } g_assert(script); - GjsAutoChar full_path = g_file_get_parse_name(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; return gjs->eval_with_scope(module_obj, script, script_len, full_path, &ignored); @@ -347,7 +349,7 @@ static JSObject* load_module_init(JSContext* cx, JS::HandleObject in_object, if (v_module.isObject()) return &v_module.toObject(); - GjsAutoChar full_path = g_file_get_parse_name(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; gjs_throw(cx, "Unexpected non-object module __init__ imported from %s", full_path.get()); return nullptr; @@ -395,8 +397,8 @@ static bool import_symbol_from_init_js(JSContext* cx, JS::HandleObject importer, GFile* directory, const char* name, bool* result) { bool found; - GjsAutoUnref file = - g_file_get_child(directory, MODULE_INIT_FILENAME); + Gjs::AutoUnref file{ + g_file_get_child(directory, MODULE_INIT_FILENAME)}; JS::RootedObject module_obj(cx, load_module_init(cx, importer, file)); if (!module_obj || !JS_AlreadyHasOwnProperty(cx, module_obj, name, &found)) @@ -429,7 +431,7 @@ static bool attempt_import(JSContext* cx, JS::HandleObject obj, if (!module_obj) return false; - GjsAutoChar full_path = g_file_get_parse_name(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; return define_meta_properties(cx, module_obj, full_path, module_name, obj) && @@ -501,7 +503,7 @@ static bool do_import(JSContext* context, JS::HandleObject obj, return true; } - GjsAutoChar filename = g_strdup_printf("%s.js", name.get()); + Gjs::AutoChar filename{g_strdup_printf("%s.js", name.get())}; std::vector directories; JS::RootedValue elem(context); JS::RootedString str(context); @@ -532,8 +534,8 @@ static bool do_import(JSContext* context, JS::HandleObject obj, if (dirname[0] == '\0') continue; - GjsAutoUnref directory = - g_file_new_for_commandline_arg(dirname.get()); + Gjs::AutoUnref directory{ + g_file_new_for_commandline_arg(dirname.get())}; /* Try importing __init__.js and loading the symbol from it */ bool found = false; @@ -544,11 +546,11 @@ static bool do_import(JSContext* context, JS::HandleObject obj, return true; /* Second try importing a directory (a sub-importer) */ - GjsAutoUnref file = g_file_get_child(directory, name.get()); + Gjs::AutoUnref file{g_file_get_child(directory, name.get())}; if (g_file_query_file_type(file, GFileQueryInfoFlags(0), nullptr) == G_FILE_TYPE_DIRECTORY) { - GjsAutoChar full_path = g_file_get_parse_name(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; gjs_debug(GJS_DEBUG_IMPORTER, "Adding directory '%s' to child importer '%s'", full_path.get(), name.get()); @@ -569,7 +571,7 @@ static bool do_import(JSContext* context, JS::HandleObject obj, exists = g_file_query_exists(file, nullptr); if (!exists) { - GjsAutoChar full_path = g_file_get_parse_name(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; gjs_debug(GJS_DEBUG_IMPORTER, "JS import '%s' not found in %s at %s", name.get(), dirname.get(), full_path.get()); @@ -656,18 +658,18 @@ static bool importer_new_enumerate(JSContext* context, JS::HandleObject object, if (!dirname) return false; - GjsAutoUnref directory = - g_file_new_for_commandline_arg(dirname.get()); - GjsAutoUnref file = - g_file_get_child(directory, MODULE_INIT_FILENAME); + Gjs::AutoUnref directory{ + g_file_new_for_commandline_arg(dirname.get())}; + Gjs::AutoUnref file{ + g_file_get_child(directory, MODULE_INIT_FILENAME)}; if (!load_module_elements(context, object, properties, file)) return false; /* new_for_commandline_arg handles resource:/// paths */ - GjsAutoUnref direnum = g_file_enumerate_children( + Gjs::AutoUnref direnum{g_file_enumerate_children( directory, "standard::name,standard::type", G_FILE_QUERY_INFO_NONE, - nullptr, nullptr); + nullptr, nullptr)}; while (true) { GFileInfo *info; @@ -678,7 +680,7 @@ static bool importer_new_enumerate(JSContext* context, JS::HandleObject object, if (info == NULL || file == NULL) break; - GjsAutoChar filename = g_file_get_basename(file); + Gjs::AutoChar filename{g_file_get_basename(file)}; /* skip hidden files and directories (.svn, .git, ...) */ if (filename.get()[0] == '.') @@ -697,8 +699,8 @@ static bool importer_new_enumerate(JSContext* context, JS::HandleObject object, return false; } } else if (g_str_has_suffix(filename, ".js")) { - GjsAutoChar filename_noext = - g_strndup(filename, strlen(filename) - 3); + Gjs::AutoChar filename_noext{ + g_strndup(filename, strlen(filename) - 3)}; jsid id = gjs_intern_string_to_id(context, filename_noext); if (id.isVoid()) return false; @@ -800,8 +802,8 @@ JSFunctionSpec gjs_importer_proto_funcs[] = { /* $XDG_DATA_DIRS /gjs-1.0 */ system_data_dirs = g_get_system_data_dirs(); for (i = 0; system_data_dirs[i] != NULL; ++i) { - GjsAutoChar s = - g_build_filename(system_data_dirs[i], "cjs-1.0", nullptr); + Gjs::AutoChar s{ + g_build_filename(system_data_dirs[i], "gjs-1.0", nullptr)}; gjs_search_path.push_back(s.get()); } @@ -809,8 +811,8 @@ JSFunctionSpec gjs_importer_proto_funcs[] = { #ifdef G_OS_WIN32 extern HMODULE gjs_dll; char *basedir = g_win32_get_package_installation_directory_of_module (gjs_dll); - GjsAutoChar gjs_data_dir = - g_build_filename(basedir, "share", "cjs-1.0", nullptr); + Gjs::AutoChar gjs_data_dir{ + g_build_filename(basedir, "share", "gjs-1.0", nullptr)}; gjs_search_path.push_back(gjs_data_dir.get()); g_free (basedir); #else diff --git a/cjs/internal.cpp b/cjs/internal.cpp index 67ddc6dc0..395898cc5 100644 --- a/cjs/internal.cpp +++ b/cjs/internal.cpp @@ -15,10 +15,10 @@ #include // for JS_CallFunction #include #include -#include #include #include // for JSEXN_ERR #include +#include // for PropertyKey #include #include #include @@ -34,8 +34,10 @@ #include // for JS_NewPlainObject, JS_ObjectIsFunction #include // for JS_GetObjectFunction, SetFunctionNativeReserved +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/engine.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/internal.h" #include "cjs/jsapi-util-args.h" @@ -54,25 +56,24 @@ union Utf8Unit; /** * gjs_load_internal_module: + * @cx: the current JSContext + * @identifier: the identifier of the internal module * - * @brief Loads a module source from an internal resource, + * Loads a module source from an internal resource, * resource:///org/cinnamon/cjs/modules/internal/{#identifier}.js, registers it in * the internal global's module registry, and proceeds to compile, initialize, * and evaluate the module. * - * @param cx the current JSContext - * @param identifier the identifier of the internal module - * - * @returns whether an error occurred while loading or evaluating the module. + * Returns: whether an error occurred while loading or evaluating the module. */ bool gjs_load_internal_module(JSContext* cx, const char* identifier) { - GjsAutoChar full_path(g_strdup_printf( + Gjs::AutoChar full_path(g_strdup_printf( "resource:///org/cinnamon/cjs/modules/internal/%s.js", identifier)); gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)", identifier, full_path.get()); - GjsAutoChar script; + Gjs::AutoChar script; size_t script_len; if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len)) return false; @@ -87,9 +88,26 @@ bool gjs_load_internal_module(JSContext* cx, const char* identifier) { options.setSelfHostingMode(false); Gjs::AutoInternalRealm ar{cx}; + GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); + JS::RootedObject internal_global{cx, gjs->internal_global()}; + JS::RootedObject module{cx, JS::CompileModule(cx, options, buf)}; + if (!module) + return false; + + JS::RootedObject registry{cx, gjs_get_module_registry(internal_global)}; + + JS::RootedId key{cx, gjs_intern_string_to_id(cx, full_path)}; + if (key.isVoid()) + return false; - JS::RootedValue ignored(cx); - return JS::Evaluate(cx, options, buf, &ignored); + JS::RootedValue ignore{cx}; + if (!gjs_global_registry_set(cx, registry, key, module) || + !JS::ModuleLink(cx, module) || + !JS::ModuleEvaluate(cx, module, &ignore)) { + return false; + } + + return true; } static bool handle_wrong_args(JSContext* cx) { @@ -100,13 +118,16 @@ static bool handle_wrong_args(JSContext* cx) { /** * gjs_internal_set_global_module_loader: + * @global: the JS global object + * @loader: the JS module loader object to store in @global * - * @brief Sets the MODULE_LOADER slot of the passed global object. - * The second argument should be an instance of ModuleLoader or - * InternalModuleLoader. Its moduleResolveHook and moduleLoadHook properties - * will be called. + * JS function exposed as `setGlobalModuleLoader` in the internal global scope. * - * @returns guaranteed to return true or assert. + * Sets the MODULE_LOADER slot of @global. @loader should be an instance of + * ModuleLoader or InternalModuleLoader. Its `moduleResolveHook` and + * `moduleLoadHook` properties will be called. + * + * Returns: JS undefined */ bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -125,16 +146,15 @@ bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc, /** * compile_module: + * @cx: the current JSContext + * @uri: The URI of the module + * @source: The source text of the module + * @v_module_out: (out): Return location for the module as a JS value * - * @brief Compiles the a module source text into an internal #Module object - * given the module's URI as the first argument. - * - * @param cx the current JSContext - * @param uri The URI of the module - * @param source The source text of the module - * @param v_module_out Return location for the module as a JS value + * Compiles the a module source text into an internal #Module object given the + * module's URI as the first argument. * - * @returns whether an error occurred while compiling the module. + * Returns: whether an error occurred while compiling the module. */ static bool compile_module(JSContext* cx, const JS::UniqueChars& uri, JS::HandleString source, @@ -161,17 +181,17 @@ static bool compile_module(JSContext* cx, const JS::UniqueChars& uri, /** * gjs_internal_compile_internal_module: + * @uri: The URI of the module (JS string) + * @source: The source text of the module (JS string) * - * @brief Compiles a module source text within the internal global's realm. + * JS function exposed as `compileInternalModule` in the internal global scope. + * + * Compiles a module source text within the internal global's realm. * * NOTE: Modules compiled with this function can only be executed * within the internal global's realm. * - * @param cx the current JSContext - * @param argc - * @param vp - * - * @returns whether an error occurred while compiling the module. + * Returns: The compiled JS module object. */ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -190,17 +210,17 @@ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, /** * gjs_internal_compile_module: + * @uri: The URI of the module (JS string) + * @source: The source text of the module (JS string) * - * @brief Compiles a module source text within the main realm. + * JS function exposed as `compileModule` in the internal global scope. + * + * Compiles a module source text within the main realm. * * NOTE: Modules compiled with this function can only be executed * within the main realm. * - * @param cx the current JSContext - * @param argc - * @param vp - * - * @returns whether an error occurred while compiling the module. + * Returns: The compiled JS module object. */ bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -218,15 +238,14 @@ bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) { /** * gjs_internal_set_module_private: + * @module: The JS module object + * @private: The JS module private object for @module * - * @brief Sets the private object of an internal #Module object. - * The private object must be a #JSObject. + * JS function exposed as `setModulePrivate` in the internal global scope. * - * @param cx the current JSContext - * @param argc - * @param vp + * Sets the private object of an internal #Module object. * - * @returns whether an error occurred while setting the private. + * Returns: JS undefined */ bool gjs_internal_set_module_private(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -242,14 +261,13 @@ bool gjs_internal_set_module_private(JSContext* cx, unsigned argc, /** * gjs_internal_get_registry: + * @global: The JS global object * - * @brief Retrieves the module registry for the passed global object. + * JS function exposed as `getRegistry` in the internal global scope. * - * @param cx the current JSContext - * @param argc - * @param vp + * Retrieves the module registry for @global. * - * @returns whether an error occurred while retrieving the registry. + * Returns: the module registry, a JS Map object. */ bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -264,27 +282,58 @@ bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) { return true; } -bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { - using AutoHashTable = - GjsAutoPointer; - using AutoURI = GjsAutoPointer; - - JS::CallArgs args = CallArgsFromVp(argc, vp); - JS::RootedString string_arg(cx); - if (!gjs_parse_call_args(cx, "parseUri", args, "S", "uri", &string_arg)) +/** + * gjs_internal_get_source_map_registry: + * @global: The JS global object + * + * JS function exposed as `getSourceMapRegistry` in the internal global scope. + * + * Retrieves the source map registry for @global. + * + * Returns: the source map registry, a JS Map object. + */ +bool gjs_internal_get_source_map_registry(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject global{cx}; + if (!gjs_parse_call_args(cx, "getSourceMapRegistry", args, "o", "global", + &global)) return handle_wrong_args(cx); - JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg); - if (!uri) - return false; + JSAutoRealm ar{cx, global}; + + JSObject* registry = gjs_get_source_map_registry(global); + args.rval().setObject(*registry); + return true; +} + +/** + * gjs_uri_object: + * @cx: the current JSContext + * @uri: the URI to parse into a JS URI object + * @rval: (out): handle to a JSValue where the URI object will be stored + * + * Parses @uri and creates a JS object with the various parsed parts available + * as properties. See type `Uri` in modules/internal/environment.d.ts. + * + * Basically a JS wrapper for g_uri_parse() for use in the internal global scope + * where we don't have access to gobject-introspection. + * + * Returns: false if an exception is pending, otherwise true + */ +static bool gjs_uri_object(JSContext* cx, const char* uri, + JS::MutableHandleValue rval) { + using AutoHashTable = + Gjs::AutoPointer; + using AutoURI = Gjs::AutoPointer; - GjsAutoError error; - AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error); + Gjs::AutoError error; + AutoURI parsed = g_uri_parse(uri, G_URI_FLAGS_ENCODED, &error); if (!parsed) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", - "Attempted to import invalid URI: %s (%s)", uri.get(), + "Attempted to import invalid URI: %s (%s)", uri, error->message); return false; } @@ -301,8 +350,8 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", - "Attempted to import invalid URI: %s (%s)", - uri.get(), error->message); + "Attempted to import invalid URI: %s (%s)", uri, + error->message); return false; } @@ -328,6 +377,11 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { if (!return_obj) return false; + JS::RootedValue v_uri{cx}; + Gjs::AutoChar uri_string{g_uri_to_string(parsed)}; + if (!gjs_string_from_utf8(cx, uri_string, &v_uri)) + return false; + // JS_NewStringCopyZ() used here and below because the URI components are // %-encoded, meaning ASCII-only JS::RootedString scheme(cx, @@ -343,15 +397,15 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { if (!path) return false; - GjsAutoChar no_query_str = - g_uri_to_string_partial(parsed, G_URI_HIDE_QUERY); + Gjs::AutoChar no_query_str{ + g_uri_to_string_partial(parsed, G_URI_HIDE_QUERY)}; JS::RootedString uri_no_query{cx, JS_NewStringCopyZ(cx, no_query_str)}; if (!uri_no_query) return false; if (!JS_DefineProperty(cx, return_obj, "uri", uri_no_query, JSPROP_ENUMERATE) || - !JS_DefineProperty(cx, return_obj, "uriWithQuery", string_arg, + !JS_DefineProperty(cx, return_obj, "uriWithQuery", v_uri, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "scheme", scheme, JSPROP_ENUMERATE) || @@ -361,10 +415,23 @@ bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { JSPROP_ENUMERATE)) return false; - args.rval().setObject(*return_obj); + rval.setObject(*return_obj); return true; } +bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + JS::RootedString string_arg{cx}; + if (!gjs_parse_call_args(cx, "parseURI", args, "S", "uri", &string_arg)) + return handle_wrong_args(cx); + + JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg); + if (!uri) + return false; + + return gjs_uri_object(cx, uri.get(), args.rval()); +} + bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -374,23 +441,12 @@ bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx, "uri", &uri, "relativePath", &relative_path)) return handle_wrong_args(cx); - GjsAutoUnref module_file = g_file_new_for_uri(uri.get()); - - if (module_file) { - GjsAutoChar output_uri = g_uri_resolve_relative( - uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr); + Gjs::AutoUnref module_file{g_file_new_for_uri(uri.get())}; - JS::ConstUTF8CharsZ uri_chars(output_uri, strlen(output_uri)); - JS::RootedString retval(cx, JS_NewStringCopyUTF8Z(cx, uri_chars)); - if (!retval) - return false; - - args.rval().setString(retval); - return true; - } + Gjs::AutoChar output_uri{g_uri_resolve_relative( + uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr)}; - args.rval().setNull(); - return true; + return gjs_uri_object(cx, output_uri.get(), args.rval()); } bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc, @@ -400,11 +456,11 @@ bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc, if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri)) return handle_wrong_args(cx); - GjsAutoUnref file = g_file_new_for_uri(uri.get()); + Gjs::AutoUnref file{g_file_new_for_uri(uri.get())}; char* contents; size_t length; - GjsAutoError error; + Gjs::AutoError error; if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents, &length, /* etag_out = */ nullptr, &error)) { Gjs::AutoMainRealm ar{cx}; @@ -432,12 +488,32 @@ bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) { if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri)) return handle_wrong_args(cx); - GjsAutoUnref file = g_file_new_for_uri(uri.get()); + Gjs::AutoUnref file{g_file_new_for_uri(uri.get())}; args.rval().setBoolean(g_file_query_exists(file, nullptr)); return true; } +bool gjs_internal_atob(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::UniqueChars text; + size_t result_len; + if (!gjs_parse_call_args(cx, "atob", args, "!s", "text", &text)) + return handle_wrong_args(cx); + + Gjs::AutoChar decoded = + reinterpret_cast(g_base64_decode(text.get(), &result_len)); + JS::ConstUTF8CharsZ contents_chars{decoded, result_len}; + JS::RootedString contents_str{cx, + JS_NewStringCopyUTF8Z(cx, contents_chars)}; + + if (!contents_str) + return false; + + args.rval().setString(contents_str); + return true; +} + class PromiseData { public: JSContext* cx; @@ -493,10 +569,10 @@ static void load_async_callback(GObject* file, GAsyncResult* res, void* data) { char* contents; size_t length; - GjsAutoError error; + Gjs::AutoError 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::AutoChar uri{g_file_get_uri(G_FILE(file))}; gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError", "Unable to load file async from: %s (%s)", uri.get(), error->message); @@ -528,7 +604,7 @@ static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0); g_assert(!priv_value.isNull() && "Executor called twice"); - GjsAutoUnref file = G_FILE(priv_value.toPrivate()); + Gjs::AutoUnref file{G_FILE(priv_value.toPrivate())}; g_assert(file && "Executor called twice"); // We now own the GFile, and will pass the reference to the GAsyncResult, so // remove it from the executor's private slot so it doesn't become dangling @@ -553,7 +629,7 @@ bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc, &uri)) return handle_wrong_args(cx); - GjsAutoUnref file = g_file_new_for_uri(uri.get()); + Gjs::AutoUnref file{g_file_new_for_uri(uri.get())}; JS::RootedObject executor(cx, JS_GetFunctionObject(js::NewFunctionWithReserved( diff --git a/cjs/internal.h b/cjs/internal.h index f73a421dd..2966d3eb3 100644 --- a/cjs/internal.h +++ b/cjs/internal.h @@ -23,6 +23,10 @@ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_internal_get_source_map_registry(JSContext* cx, unsigned argc, + JS::Value* vp); + GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc, JS::Value* vp); @@ -50,4 +54,7 @@ bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc, GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_internal_atob(JSContext* cx, unsigned argc, JS::Value* vp); + #endif // GJS_INTERNAL_H_ diff --git a/cjs/jsapi-class.h b/cjs/jsapi-class.h index bad97a4d2..93ec7db93 100644 --- a/cjs/jsapi-class.h +++ b/cjs/jsapi-class.h @@ -14,8 +14,6 @@ #include #include -#include "cjs/global.h" -#include "cjs/jsapi-util.h" #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION diff --git a/cjs/jsapi-dynamic-class.cpp b/cjs/jsapi-dynamic-class.cpp index 6e4bed31a..e81fb542d 100644 --- a/cjs/jsapi-dynamic-class.cpp +++ b/cjs/jsapi-dynamic-class.cpp @@ -24,6 +24,7 @@ #include // for GetFunctionNativeReserved, NewFun... #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -80,8 +81,8 @@ bool gjs_init_class_dynamic(JSContext* context, JS::HandleObject in_object, if (proto_fs && !JS_DefineFunctions(context, prototype, proto_fs)) return false; - GjsAutoChar full_function_name = - g_strdup_printf("%s_%s", ns_name, class_name); + Gjs::AutoChar full_function_name{ + g_strdup_printf("%s_%s", ns_name, class_name)}; JSFunction* constructor_fun = JS_NewFunction(context, constructor_native, nargs, JSFUN_CONSTRUCTOR, full_function_name); @@ -199,8 +200,10 @@ bool gjs_define_property_dynamic(JSContext* cx, JS::HandleObject proto, const char* func_namespace, JSNative getter, JS::HandleValue getter_slot, JSNative setter, JS::HandleValue setter_slot, unsigned flags) { - GjsAutoChar getter_name = g_strconcat(func_namespace, "_get::", prop_name, nullptr); - GjsAutoChar setter_name = g_strconcat(func_namespace, "_set::", prop_name, nullptr); + Gjs::AutoChar getter_name{ + g_strconcat(func_namespace, "_get::", prop_name, nullptr)}; + Gjs::AutoChar setter_name{ + g_strconcat(func_namespace, "_set::", prop_name, nullptr)}; JS::RootedObject getter_obj( cx, define_native_accessor_wrapper(cx, getter, 0, getter_name, diff --git a/cjs/jsapi-util-args.h b/cjs/jsapi-util-args.h index 5ec05d51f..1492f410a 100644 --- a/cjs/jsapi-util-args.h +++ b/cjs/jsapi-util-args.h @@ -26,6 +26,7 @@ #include // for GenericErrorResult #include // IWYU pragma: keep +#include "cjs/auto.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -44,11 +45,11 @@ namespace detail { } class ParseArgsErr { - GjsAutoChar m_message; + Gjs::AutoChar m_message; public: explicit ParseArgsErr(const char* literal_msg) - : m_message(literal_msg, GjsAutoTakeOwnership{}) {} + : m_message(literal_msg, Gjs::TakeOwnership{}) {} template ParseArgsErr(const char* format_string, F param) : m_message(g_strdup_printf(format_string, param)) {} @@ -114,9 +115,10 @@ static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, - JS::HandleValue value, GjsAutoChar* ref) { + JS::HandleValue value, + Gjs::AutoChar* ref) { if (c != 'F') - return Err("Wrong type for %c, got GjsAutoChar*", c); + return Err("Wrong type for %c, got Gjs::AutoChar*", c); if (nullable && value.isNull()) { ref->release(); return JS::Ok(); @@ -326,7 +328,7 @@ GJS_JSAPI_RETURN_CONVENTION [[maybe_unused]] static bool gjs_parse_call_args( * b: A boolean (pass a bool *) * s: A string, converted into UTF-8 (pass a JS::UniqueChars*) * F: A string, converted into "filename encoding" (i.e. active locale) (pass - * a GjsAutoChar *) + * a Gjs::AutoChar *) * S: A string, no conversion (pass a JS::MutableHandleString) * i: A number, will be converted to a 32-bit int (pass an int32_t * or a * pointer to an enum type) @@ -393,7 +395,7 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_parse_call_args( return false; } - GjsAutoStrv parts = g_strsplit(format, "|", 2); + Gjs::AutoStrv parts{g_strsplit(format, "|", 2)}; fmt_required = parts.get()[0]; fmt_optional = parts.get()[1]; // may be null diff --git a/cjs/jsapi-util-error.cpp b/cjs/jsapi-util-error.cpp index 2533f6d12..209f263b0 100644 --- a/cjs/jsapi-util-error.cpp +++ b/cjs/jsapi-util-error.cpp @@ -28,7 +28,9 @@ #include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" @@ -93,7 +95,7 @@ static bool append_new_cause(JSContext* cx, JS::HandleValue thrown, [[gnu::format(printf, 4, 0)]] static void gjs_throw_valist( JSContext* cx, JSExnType error_kind, const char* error_name, const char* format, va_list args) { - GjsAutoChar s = g_strdup_vprintf(format, args); + Gjs::AutoChar 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 @@ -212,7 +214,7 @@ gjs_throw_literal(JSContext *context, * and domain don't matter. So, for example, don't use it to throw errors * around calling from JS into C code. */ -bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const& error) { +bool gjs_throw_gerror_message(JSContext* cx, Gjs::AutoError const& error) { g_return_val_if_fail(error, false); gjs_throw_literal(cx, error->message); return false; @@ -254,8 +256,8 @@ void gjs_warning_reporter(JSContext*, JSErrorReport* report) { if (gjs_environment_variable_is_set("GJS_ABORT_ON_OOM") && !report->isWarning() && report->errorNumber == 137) { /* 137, JSMSG_OUT_OF_MEMORY */ - g_error("GJS ran out of memory at %s: %i.", report->filename.c_str(), - report->lineno); + g_error("GJS ran out of memory at %s:%u:%u.", report->filename.c_str(), + report->lineno, report->column.oneOriginValue()); } if (report->isWarning()) { @@ -276,6 +278,7 @@ void gjs_warning_reporter(JSContext*, JSErrorReport* report) { level = G_LOG_LEVEL_WARNING; } - g_log(G_LOG_DOMAIN, level, "JS %s: [%s %d]: %s", warning, - report->filename.c_str(), report->lineno, report->message().c_str()); + g_log(G_LOG_DOMAIN, level, "JS %s: %s:%u:%u: %s", warning, + report->filename.c_str(), report->lineno, + report->column.oneOriginValue(), report->message().c_str()); } diff --git a/cjs/jsapi-util-root.h b/cjs/jsapi-util-root.h index e2fdda12e..72186dd4c 100644 --- a/cjs/jsapi-util-root.h +++ b/cjs/jsapi-util-root.h @@ -8,9 +8,10 @@ #include -#include // for nullptr_t +// https://github.com/include-what-you-use/include-what-you-use/issues/1791 +#include // IWYU pragma: keep #include -#include +#include // IWYU pragma: keep (actually for clangd) #include diff --git a/cjs/jsapi-util-string.cpp b/cjs/jsapi-util-string.cpp index c0d5b7138..9483d7de9 100644 --- a/cjs/jsapi-util-string.cpp +++ b/cjs/jsapi-util-string.cpp @@ -34,13 +34,14 @@ #include #include +#include "cjs/auto.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" -#include "util/misc.h" // for _gjs_memdup2 class JSLinearString; -GjsAutoChar gjs_hyphen_to_underscore(const char* str) { +Gjs::AutoChar gjs_hyphen_to_underscore(const char* str) { char *s = g_strdup(str); char *retval = s; while (*(s++) != '\0') { @@ -50,8 +51,8 @@ GjsAutoChar gjs_hyphen_to_underscore(const char* str) { return retval; } -GjsAutoChar gjs_hyphen_to_camel(const char* str) { - GjsAutoChar retval = static_cast(g_malloc(strlen(str) + 1)); +Gjs::AutoChar gjs_hyphen_to_camel(const char* str) { + Gjs::AutoChar retval{static_cast(g_malloc(strlen(str) + 1))}; const char* input_iter = str; char* output_iter = retval.get(); bool uppercase_next = false; @@ -96,18 +97,20 @@ JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) { /** * gjs_string_to_utf8_n: - * @param cx: the current #JSContext - * @param str: a handle to a JSString - * @param output a pointer to a JS::UniqueChars - * @param output_len a pointer for the length of output + * @cx: the current #JSContext + * @str: string to encode in UTF-8 + * @output: (out): return location for the UTF-8-encoded string + * @output_len: (out): return location for the length of @output * - * @brief Converts a JSString to UTF-8 and puts the char array in #output and - * its length in #output_len. + * Converts a JSString to UTF-8 and puts the char array in @output and its + * length in @output_len. * - * This function handles the boilerblate for unpacking a JSString, determining its - * length, and returning the appropriate JS::UniqueChars. This function should generally - * be preferred over using JS::DeflateStringToUTF8Buffer directly as it correctly - * handles allocation in a JS_Free compatible manner. + * This function handles the boilerplate for unpacking @str, determining its + * length, and returning the appropriate JS::UniqueChars. This function should + * generally be preferred over using JS::DeflateStringToUTF8Buffer() directly as + * it correctly handles allocation in a JS_free compatible manner. + * + * Returns: false if an exception is pending, otherwise true. */ bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output, size_t* output_len) { @@ -134,17 +137,16 @@ bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* /** * gjs_lossy_string_from_utf8: + * @cx: the current #JSContext + * @utf8_string: a zero-terminated array of UTF-8 characters to decode * - * @brief Converts an array of UTF-8 characters to a JS string. - * Instead of throwing, any invalid characters will be converted - * to the UTF-8 invalid character fallback. + * Converts @utf8_string to a JS string. Instead of throwing, any invalid + * characters will be converted to the UTF-8 invalid character fallback. * - * @param cx the current #JSContext - * @param utf8_string an array of UTF-8 characters - * @param value_p a value to store the resulting string in + * Returns: The decoded string. */ JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string) { - JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string)); + JS::UTF8Chars chars{utf8_string, strlen(utf8_string)}; size_t outlen; JS::UniqueTwoByteChars twobyte_chars( JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen, @@ -158,9 +160,14 @@ JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string) { /** * gjs_lossy_string_from_utf8_n: + * @cx: the current #JSContext + * @utf8_string: an array of UTF-8 characters to decode + * @len: length of @utf8_string + * + * Provides the same conversion behavior as gjs_lossy_string_from_utf8 + * with a fixed length. See gjs_lossy_string_from_utf8(). * - * @brief Provides the same conversion behavior as gjs_lossy_string_from_utf8 - * with a fixed length. See gjs_lossy_string_from_utf8() + * Returns: The decoded string. */ JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string, size_t len) { @@ -204,12 +211,9 @@ gjs_string_from_utf8_n(JSContext *cx, return !!str; } -bool -gjs_string_to_filename(JSContext *context, - const JS::Value filename_val, - GjsAutoChar *filename_string) -{ - GjsAutoError error; +bool gjs_string_to_filename(JSContext* context, const JS::Value filename_val, + Gjs::AutoChar* filename_string) { + Gjs::AutoError error; /* gjs_string_to_filename verifies that filename_val is a string */ @@ -233,15 +237,19 @@ gjs_string_from_filename(JSContext *context, JS::MutableHandleValue value_p) { gsize written; - GjsAutoError error; + Gjs::AutoError error; error = NULL; - GjsAutoChar utf8_string = g_filename_to_utf8(filename_string, n_bytes, - nullptr, &written, &error); + Gjs::AutoChar utf8_string{g_filename_to_utf8(filename_string, n_bytes, + nullptr, &written, &error)}; if (error) { + Gjs::AutoChar escaped_char{g_strescape(filename_string, nullptr)}; gjs_throw(context, - "Could not convert UTF-8 string '%s' to a filename: '%s'", - filename_string, error->message); + "Could not convert filename string to UTF-8 for string: %s. " + "If string is " + "invalid UTF-8 and used for display purposes, try GLib " + "attribute standard::display-name. The reason is: %s. ", + escaped_char.get(), error->message); return false; } @@ -311,7 +319,7 @@ gjs_string_get_char16_data(JSContext *context, return false; } - *data_p = static_cast(_gjs_memdup2(js_data, len_bytes.value())); + *data_p = static_cast(g_memdup2(js_data, len_bytes.value())); return true; } @@ -335,7 +343,7 @@ gjs_string_to_ucs4(JSContext *cx, return true; size_t len; - GjsAutoError error; + Gjs::AutoError error; if (JS::StringHasLatin1Chars(str)) return from_latin1(cx, str, ucs4_string_p, len_p); @@ -390,7 +398,7 @@ gjs_string_from_ucs4(JSContext *cx, } long u16_string_length; - GjsAutoError error; + Gjs::AutoError error; gunichar2* u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, nullptr, &u16_string_length, &error); diff --git a/cjs/jsapi-util.cpp b/cjs/jsapi-util.cpp index e544ddde2..d4ec98f7f 100644 --- a/cjs/jsapi-util.cpp +++ b/cjs/jsapi-util.cpp @@ -19,19 +19,23 @@ #include #include +#include // for Call #include #include #include +#include // for TaggedColumnNumberOneOrigin #include #include #include -#include // for JS_MaybeGC, NonIncrementalGC, GCRe... +#include // for JS_MaybeGC, NonIncrementalGC, GCRe... #include // for GCHashSet -#include // for RootedVector -#include // for DefaultHasher -#include // for GetClass +#include // for RootedVector +#include // for DefaultHasher +#include // for GetClass #include +#include // for JSPROP_ENUMERATE #include +#include #include #include #include @@ -42,9 +46,12 @@ #include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" +#include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" +#include "cjs/module.h" static void throw_property_lookup_error(JSContext *cx, @@ -262,8 +269,37 @@ static JSString* exception_to_string(JSContext* cx, JS::HandleValue exc) { return JS::ToString(cx, exc); } +// Helper function: log and clear the pending exception, without calling into +// any JS APIs that might cause more exceptions to be thrown. +static void log_exception_brief(JSContext* cx) { + JS::RootedValue exc{cx}; + if (!JS_GetPendingException(cx, &exc)) + return; + + JS_ClearPendingException(cx); + + if (!exc.isObject()) { + g_warning("Value thrown while printing exception: %s", + gjs_debug_value(exc).c_str()); + return; + } + + JS::RootedObject exc_obj{cx, &exc.toObject()}; + JSErrorReport* report = JS_ErrorFromException(cx, exc_obj); + if (!report) { + g_warning("Non-Error Object thrown while printing exception: %s", + gjs_debug_object(exc_obj).c_str()); + return; + } + + g_warning("Exception thrown while printing exception: %s:%u:%u: %s", + report->filename.c_str(), report->lineno, + report->column.oneOriginValue(), report->message().c_str()); +} + // Helper function: format the error's stack property. static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) { + auto Ok = JS::SavedFrameResult::Ok; JS::AutoSaveExceptionState saved_exc(cx); auto restore = mozilla::MakeScopeExit([&saved_exc]() { saved_exc.restore(); }); @@ -276,25 +312,131 @@ static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) { // have the latter. JS::RootedObject saved_frame{cx, JS::ExceptionStackOrNull(exc)}; if (saved_frame) { + GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); + Gjs::AutoMainRealm ar{gjs}; + JS::RootedObject global{cx, gjs->global()}; + JS::RootedObject registry{cx, gjs_get_source_map_registry(global)}; + JS::UniqueChars utf8_stack{format_saved_frame(cx, saved_frame)}; if (!utf8_stack) return {}; - out << '\n' << utf8_stack.get(); + + char* utf8_stack_str = utf8_stack.get(); + std::stringstream ss{utf8_stack_str}; + // append source map info when available to each line + while (saved_frame) { + JS::RootedObject consumer{cx}; + JS::RootedString source_string{cx}; + uint32_t line; + JS::TaggedColumnNumberOneOrigin column; + std::string stack_line; + + // print the original stack trace + std::getline(ss, stack_line, '\n'); + out << '\n' << stack_line; + + bool success = + JS::GetSavedFrameSource(cx, nullptr, saved_frame, + &source_string) == Ok && + JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line) == Ok && + JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &column) == + Ok; + if (JS::GetSavedFrameParent(cx, nullptr, saved_frame, + &saved_frame) != Ok) { + // If we can't iterate, bail out and don't print source map info + break; + } + + if (!success) { + continue; + } + + if (!gjs_global_source_map_get(cx, registry, source_string, + &consumer) || + !consumer) { + log_exception_brief(cx); + continue; // no source map for this file + } + + // build query obj for consumer + JS::RootedObject input_obj{cx, JS_NewPlainObject(cx)}; + if (!input_obj || + !JS_DefineProperty(cx, input_obj, "line", line, + JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, input_obj, "column", + column.oneOriginValue() - 1, + JSPROP_ENUMERATE)) { + log_exception_brief(cx); + continue; + } + + JS::RootedValue val{cx, JS::ObjectValue(*input_obj)}; + if (!JS::Call(cx, consumer, "originalPositionFor", + JS::HandleValueArray(val), &val)) { + log_exception_brief(cx); + continue; + } + JS::RootedObject rvalObj{cx, &val.toObject()}; + + out << " -> "; + + if (!JS_GetProperty(cx, rvalObj, "name", &val)) { + log_exception_brief(cx); + continue; + } + if (val.isString()) { + JS::UniqueChars name{gjs_string_to_utf8(cx, val)}; + if (name) + out << name.get() << "@"; + log_exception_brief(cx); + } + if (!JS_GetProperty(cx, rvalObj, "source", &val)) { + log_exception_brief(cx); + continue; + } + if (val.isString()) { + JS::UniqueChars source{gjs_string_to_utf8(cx, val)}; + if (source) + out << source.get(); + log_exception_brief(cx); + } + if (!JS_GetProperty(cx, rvalObj, "line", &val)) { + log_exception_brief(cx); + continue; + } + if (val.isInt32()) { + out << ":" << val.toInt32(); + } + if (!JS_GetProperty(cx, rvalObj, "column", &val)) { + log_exception_brief(cx); + continue; + } + if (val.isInt32()) { + out << ":" << val.toInt32() + 1; + } + } return out.str(); } JS::RootedValue stack{cx}; - if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) || !stack.isString()) + if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) || + !stack.isString()) { + log_exception_brief(cx); return {}; + } JS::RootedString str{cx, stack.toString()}; bool is_empty; - if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty) + if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty) { + log_exception_brief(cx); return {}; + } JS::UniqueChars utf8_stack{JS_EncodeStringToUTF8(cx, str)}; - if (!utf8_stack) + if (!utf8_stack) { + log_exception_brief(cx); return {}; + } out << '\n' << utf8_stack.get(); return out.str(); @@ -312,14 +454,14 @@ static std::string format_syntax_error_location(JSContext* cx, if (property.isInt32()) line = property.toInt32(); } - JS_ClearPendingException(cx); + log_exception_brief(cx); int32_t column = 0; if (JS_GetPropertyById(cx, exc, atoms.column_number(), &property)) { if (property.isInt32()) column = property.toInt32(); } - JS_ClearPendingException(cx); + log_exception_brief(cx); JS::UniqueChars utf8_filename; if (JS_GetPropertyById(cx, exc, atoms.file_name(), &property)) { @@ -328,7 +470,7 @@ static std::string format_syntax_error_location(JSContext* cx, utf8_filename = JS_EncodeStringToUTF8(cx, str); } } - JS_ClearPendingException(cx); + log_exception_brief(cx); std::ostringstream out; out << " @ "; @@ -353,7 +495,7 @@ static std::string format_exception_with_cause( JS::RootedValue v_cause(cx); if (!JS_GetPropertyById(cx, exc_obj, atoms.cause(), &v_cause)) - JS_ClearPendingException(cx); + log_exception_brief(cx); if (v_cause.isUndefined()) return out.str(); @@ -374,7 +516,7 @@ static std::string format_exception_with_cause( if (utf8_exception) out << utf8_exception.get(); } - JS_ClearPendingException(cx); + log_exception_brief(cx); if (v_cause.isObject()) out << format_exception_with_cause(cx, cause, seen_causes); @@ -389,7 +531,7 @@ static std::string format_exception_log_message(JSContext* cx, if (message) { JS::UniqueChars utf8_message = JS_EncodeStringToUTF8(cx, message); - JS_ClearPendingException(cx); + log_exception_brief(cx); if (utf8_message) out << utf8_message.get() << ": "; } @@ -400,7 +542,7 @@ static std::string format_exception_log_message(JSContext* cx, if (utf8_exception) out << utf8_exception.get(); } - JS_ClearPendingException(cx); + log_exception_brief(cx); if (!exc.isObject()) return out.str(); @@ -499,7 +641,7 @@ static void _linux_get_self_process_size(long* rss_size) // NOLINT(runtime/int) *rss_size = 0; - GjsAutoChar contents; + Gjs::AutoChar contents; if (!g_file_get_contents("/proc/self/stat", contents.out(), &len, nullptr)) return; diff --git a/cjs/jsapi-util.h b/cjs/jsapi-util.h index eb51b01c5..7fc0c0785 100644 --- a/cjs/jsapi-util.h +++ b/cjs/jsapi-util.h @@ -15,10 +15,8 @@ #include #include // for string, u16string #include // for enable_if_t, add_pointer_t, add_const_t -#include // IWYU pragma: keep #include -#include #include #include @@ -30,6 +28,8 @@ #include #include // for UniqueChars +#include "cjs/auto.h" +#include "cjs/gerror-result.h" #include "cjs/macros.h" #include "util/log.h" @@ -56,375 +56,6 @@ template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS -struct GjsAutoTakeOwnership {}; - -template -using GjsAutoPointerRefFunction = F* (*)(F*); - -template -using GjsAutoPointerFreeFunction = void (*)(F*); - -template free_func = free, - GjsAutoPointerRefFunction ref_func = nullptr> -struct GjsAutoPointer { - using Tp = - 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 - static constexpr bool has_function() { - using NullType = std::integral_constant; - using ActualType = std::integral_constant; - - return !std::is_same_v; - } - - public: - static constexpr bool has_free_function() { - return has_function, free_func>(); - } - - static constexpr bool has_ref_function() { - return has_function, ref_func>(); - } - - constexpr GjsAutoPointer(Ptr ptr = nullptr) // NOLINT(runtime/explicit) - : m_ptr(ptr) {} - template && - std::is_array_v>> - explicit constexpr GjsAutoPointer(U ptr[]) : m_ptr(ptr) {} - - constexpr GjsAutoPointer(Ptr ptr, const GjsAutoTakeOwnership&) - : GjsAutoPointer(ptr) { - m_ptr = copy(); - } - constexpr GjsAutoPointer(ConstPtr ptr, const GjsAutoTakeOwnership& o) - : GjsAutoPointer(const_cast(ptr), o) {} - constexpr GjsAutoPointer(GjsAutoPointer&& other) : GjsAutoPointer() { - this->swap(other); - } - constexpr GjsAutoPointer(GjsAutoPointer const& other) : GjsAutoPointer() { - *this = other; - } - - constexpr GjsAutoPointer& operator=(Ptr ptr) { - reset(ptr); - return *this; - } - - constexpr GjsAutoPointer& operator=(GjsAutoPointer&& other) { - this->swap(other); - return *this; - } - - constexpr GjsAutoPointer& operator=(GjsAutoPointer const& other) { - GjsAutoPointer dup(other.get(), GjsAutoTakeOwnership()); - this->swap(dup); - return *this; - } - - template - constexpr std::enable_if_t, Ptr> operator->() { - return m_ptr; - } - - template - constexpr std::enable_if_t, ConstPtr> operator->() - const { - 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; } - constexpr operator ConstPtr() const { return m_ptr; } - constexpr operator bool() const { return m_ptr != nullptr; } - - constexpr Ptr get() const { return m_ptr; } - constexpr Ptr* out() { return &m_ptr; } - constexpr ConstPtr* out() const { return const_cast(&m_ptr); } - - constexpr Ptr release() { - auto* ptr = m_ptr; - m_ptr = nullptr; - return ptr; - } - - constexpr void reset(Ptr ptr = nullptr) { - Ptr old_ptr = m_ptr; - m_ptr = ptr; - - if constexpr (has_free_function()) { - if (old_ptr) - free_func(reinterpret_cast(old_ptr)); - } - } - - constexpr void swap(GjsAutoPointer& other) { - std::swap(this->m_ptr, other.m_ptr); - } - - /* constexpr */ ~GjsAutoPointer() { // one day, with -std=c++2a - reset(); - } - - template - [[nodiscard]] constexpr std::enable_if_t, Ptr> copy() - const { - static_assert(has_ref_function(), "No ref function provided"); - return m_ptr ? reinterpret_cast( - ref_func(reinterpret_cast(m_ptr))) - : nullptr; - } - - template - [[nodiscard]] constexpr C* as() const { - return const_cast(reinterpret_cast(m_ptr)); - } - - private: - Ptr m_ptr; -}; - -template free_func = free, - GjsAutoPointerRefFunction ref_func = nullptr> -struct GjsAutoPointerSimple : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; -}; - -template free_func, - GjsAutoPointerRefFunction ref_func> -constexpr bool operator==( - GjsAutoPointer const& lhs, - GjsAutoPointer const& rhs) { - return lhs.get() == rhs.get(); -} - -template -using GjsAutoFree = GjsAutoPointer; - -struct GjsAutoCharFuncs { - static char* dup(char* str) { return g_strdup(str); } - static void free(char* str) { g_free(str); } -}; -using GjsAutoChar = - GjsAutoPointer; - -using GjsAutoChar16 = GjsAutoPointer; - -struct GjsAutoErrorFuncs { - static GError* error_copy(GError* error) { return g_error_copy(error); } -}; - -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; - -template -using GjsAutoUnref = GjsAutoPointer; - -using GjsAutoGVariant = - GjsAutoPointer; - -template -constexpr void GjsAutoPointerDeleter(T v) { - if constexpr (std::is_array_v) - delete[] reinterpret_cast*>(v); - else - delete v; -} - -template -using GjsAutoCppPointer = GjsAutoPointer>; - -template -struct GjsAutoTypeClass : GjsAutoPointer { - GjsAutoTypeClass(gpointer ptr = nullptr) // NOLINT(runtime/explicit) - : GjsAutoPointer(static_cast(ptr)) {} - explicit GjsAutoTypeClass(GType gtype) - : GjsAutoTypeClass(g_type_class_ref(gtype)) {} -}; - -// Use this class for owning a GIBaseInfo* of indeterminate type. Any type (e.g. -// GIFunctionInfo*, GIObjectInfo*) will fit. If you know that the info is of a -// certain type (e.g. you are storing the return value of a function that -// returns GIFunctionInfo*,) use one of the derived classes below. -struct GjsAutoBaseInfo : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; - - [[nodiscard]] const char* name() const { - return g_base_info_get_name(*this); - } - [[nodiscard]] const char* ns() const { - return g_base_info_get_namespace(*this); - } - [[nodiscard]] GIInfoType type() const { - return g_base_info_get_type(*this); - } -}; - -// Use GjsAutoInfo, preferably its typedefs below, when you know for sure that -// the info is either of a certain type or null. -template -struct GjsAutoInfo : GjsAutoBaseInfo { - using GjsAutoBaseInfo::GjsAutoBaseInfo; - - // Normally one-argument constructors should be explicit, but we are trying - // 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. - GIInfoType type() = delete; - - private: - void validate() const { - if (GIBaseInfo* base = *this) - g_assert(g_base_info_get_type(base) == TAG); - } -}; - -using GjsAutoArgInfo = GjsAutoInfo; -using GjsAutoEnumInfo = GjsAutoInfo; -using GjsAutoFieldInfo = GjsAutoInfo; -using GjsAutoFunctionInfo = GjsAutoInfo; -using GjsAutoInterfaceInfo = GjsAutoInfo; -using GjsAutoObjectInfo = GjsAutoInfo; -using GjsAutoPropertyInfo = GjsAutoInfo; -using GjsAutoStructInfo = GjsAutoInfo; -using GjsAutoSignalInfo = GjsAutoInfo; -using GjsAutoTypeInfo = GjsAutoInfo; -using GjsAutoValueInfo = GjsAutoInfo; -using GjsAutoVFuncInfo = GjsAutoInfo; - -// GICallableInfo can be one of several tags, so we have to have a separate -// class, and use GI_IS_CALLABLE_INFO() to validate. -struct GjsAutoCallableInfo : GjsAutoBaseInfo { - using GjsAutoBaseInfo::GjsAutoBaseInfo; - - GjsAutoCallableInfo(GIBaseInfo* ptr = nullptr) // NOLINT(runtime/explicit) - : GjsAutoBaseInfo(ptr) { - validate(); - } - - void reset(GIBaseInfo* other = nullptr) { - GjsAutoBaseInfo::reset(other); - validate(); - } - - private: - void validate() const { - if (*this) - g_assert(GI_IS_CALLABLE_INFO(get())); - } -}; - -template -struct GjsSmartPointer : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; -}; - -template <> -struct GjsSmartPointer : GjsAutoStrv { - using GjsAutoStrv::GjsAutoPointer; -}; - -template <> -struct GjsSmartPointer : GjsAutoStrv { - using GjsAutoStrv::GjsAutoPointer; -}; - -template <> -struct GjsSmartPointer : GjsAutoUnref { - using GjsAutoUnref::GjsAutoUnref; -}; - -template <> -struct GjsSmartPointer : GjsAutoBaseInfo { - using GjsAutoBaseInfo::GjsAutoBaseInfo; -}; - -template <> -struct GjsSmartPointer : GjsAutoError { - using GjsAutoError::GjsAutoError; - using GjsAutoError::operator=; - using GjsAutoError::operator&; -}; - -template <> -struct GjsSmartPointer : GjsAutoGVariant { - using GjsAutoGVariant::GjsAutoPointer; -}; - -template <> -struct GjsSmartPointer : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; -}; - -template <> -struct GjsSmartPointer : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; -}; - -/* For use of GjsAutoInfo in GC hash maps */ -namespace JS { -template -struct GCPolicy> : public IgnoreGCPolicy> {}; -} // namespace JS - -using GjsAutoParam = GjsAutoPointer; - -/* For use of GjsAutoParam in GC hash maps */ -namespace JS { -template <> -struct GCPolicy : public IgnoreGCPolicy {}; -} // namespace JS - /* Flags that should be set on properties exported from native code modules. * Basically set these on API, but do NOT set them on data. * @@ -473,7 +104,7 @@ JSObject* gjs_define_string_array(JSContext* cx, JS::HandleObject obj, const char* format, ...); void gjs_throw_literal (JSContext *context, const char *string); -bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const&); +bool gjs_throw_gerror_message(JSContext* cx, Gjs::AutoError const&); bool gjs_log_exception (JSContext *context); @@ -505,9 +136,8 @@ bool gjs_string_from_utf8_n(JSContext *cx, JS::MutableHandleValue out); GJS_JSAPI_RETURN_CONVENTION -bool gjs_string_to_filename(JSContext *cx, - const JS::Value string_val, - GjsAutoChar *filename_string); +bool gjs_string_to_filename(JSContext*, const JS::Value, + Gjs::AutoChar* filename_string); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_filename(JSContext *context, @@ -604,8 +234,8 @@ bool gjs_object_require_converted_property(JSContext *context, [[nodiscard]] std::string gjs_debug_value(JS::Value v); [[nodiscard]] std::string gjs_debug_id(jsid id); -[[nodiscard]] GjsAutoChar gjs_hyphen_to_underscore(const char* str); -[[nodiscard]] GjsAutoChar gjs_hyphen_to_camel(const char* str); +[[nodiscard]] Gjs::AutoChar gjs_hyphen_to_underscore(const char* str); +[[nodiscard]] Gjs::AutoChar gjs_hyphen_to_camel(const char* str); #if defined(G_OS_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1900)) [[nodiscard]] std::wstring gjs_win32_vc140_utf8_to_utf16(const char* str); diff --git a/cjs/mainloop.cpp b/cjs/mainloop.cpp index 7a1dd0f5e..16f9dda86 100644 --- a/cjs/mainloop.cpp +++ b/cjs/mainloop.cpp @@ -6,8 +6,8 @@ #include +#include "cjs/auto.h" #include "cjs/context-private.h" -#include "cjs/jsapi-util.h" #include "cjs/mainloop.h" namespace Gjs { @@ -25,8 +25,8 @@ bool MainLoop::spin(GjsContextPrivate* gjs) { return false; } - GjsAutoPointer - main_context(g_main_context_ref_thread_default()); + Gjs::AutoPointer + main_context{g_main_context_ref_thread_default()}; debug("Spinning loop until released or hook cleared"); do { diff --git a/cjs/module.cpp b/cjs/module.cpp index 7fecd743a..ffa01d1c5 100644 --- a/cjs/module.cpp +++ b/cjs/module.cpp @@ -7,6 +7,7 @@ #include // for size_t #include +#include #include // for vector #include @@ -20,6 +21,7 @@ #include #include #include +#include #include // for JS_ReportOutOfMemory #include #include // for RootedVector @@ -43,8 +45,10 @@ #include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" @@ -60,7 +64,7 @@ union Utf8Unit; } class GjsScriptModule { - GjsAutoChar m_name; + Gjs::AutoChar m_name; // Reserved slots static const size_t POINTER = 0; @@ -116,7 +120,7 @@ class GjsScriptModule { if (!buf.init(cx, source, source_len, JS::SourceOwnership::Borrowed)) return false; - JS::RootedObjectVector scope_chain(cx); + JS::EnvironmentChain scope_chain{cx, JS::SupportUnscopables::No}; if (!scope_chain.append(module)) { JS_ReportOutOfMemory(cx); return false; @@ -154,8 +158,8 @@ class GjsScriptModule { JS::HandleObject module, GFile *file) { - GjsAutoError error; - GjsAutoChar script; + Gjs::AutoError error; + Gjs::AutoChar script; size_t script_len = 0; if (!(g_file_load_contents(file, nullptr, script.out(), &script_len, @@ -163,8 +167,8 @@ class GjsScriptModule { return gjs_throw_gerror_message(cx, error); g_assert(script); - GjsAutoChar full_path = g_file_get_parse_name(file); - GjsAutoChar uri = g_file_get_uri(file); + Gjs::AutoChar full_path{g_file_get_parse_name(file)}; + Gjs::AutoChar uri{g_file_get_uri(file)}; return evaluate_import(cx, module, script, script_len, full_path, uri); } @@ -197,7 +201,7 @@ class GjsScriptModule { _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId::ModuleExportedLetOrConst, - {gjs_debug_id(id).c_str(), m_name}); + {gjs_debug_id(id), m_name.get()}); JS::Rooted desc(cx, maybe_desc.value()); return JS_DefinePropertyById(cx, module, id, desc); @@ -272,14 +276,14 @@ class GjsScriptModule { /** * gjs_script_module_build_private: - * @param cx the #JSContext - * @param uri the URI this script module is loaded from + * @cx: the #JSContext + * @uri: the URI this script module is loaded from * - * @brief To support dynamic imports from scripts, we need to provide private - * data when we compile scripts which is compatible with our module resolution - * hooks in modules/internal/loader.js + * To support dynamic imports from scripts, we need to provide private data when + * we compile scripts which is compatible with our module resolution hooks in + * modules/internal/loader.js * - * @returns a JSObject which can be used for a JSScript's private data. + * Returns: a JSObject which can be used for a JSScript's private data. */ JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri) { return GjsScriptModule::build_private(cx, uri); @@ -316,15 +320,14 @@ decltype(GjsScriptModule::class_ops) constexpr GjsScriptModule::class_ops; /** * gjs_get_native_registry: + * @global: The JS global object * - * @brief Retrieves a global's native registry from the NATIVE_REGISTRY slot. - * Registries are JS Map objects created with JS::NewMapObject instead - * of GCHashMaps (used elsewhere in GJS) because the objects need to be - * exposed to internal JS code and accessed from native C++ code. + * Retrieves a global's native registry from the NATIVE_REGISTRY slot. + * Registries are JS Map objects created with JS::NewMapObject instead of + * GCHashMaps (used elsewhere in GJS) because the objects need to be exposed to + * internal JS code and accessed from native C++ code. * - * @param global a global #JSObject - * - * @returns the registry map as a #JSObject + * Returns: the native module registry, a JS Map object. */ JSObject* gjs_get_native_registry(JSObject* global) { JS::Value native_registry = @@ -336,14 +339,12 @@ JSObject* gjs_get_native_registry(JSObject* global) { /** * gjs_get_module_registry: + * @global: the JS global object * - * @brief Retrieves a global's module registry from the MODULE_REGISTRY slot. - * Registries are JS Maps. See gjs_get_native_registry for more detail. - * - * @param cx the current #JSContext - * @param global a global #JSObject + * Retrieves a global's module registry from the MODULE_REGISTRY slot. + * Registries are JS Maps. See gjs_get_native_registry() for more detail. * - * @returns the registry map as a #JSObject + * Returns: the module registry, a JS Map object */ JSObject* gjs_get_module_registry(JSObject* global) { JS::Value esm_registry = @@ -353,13 +354,32 @@ JSObject* gjs_get_module_registry(JSObject* global) { return &esm_registry.toObject(); } +/** + * gjs_get_source_map_registry: + * @global: The JS global object + * + * Retrieves a global's source map registry from the SOURCE_MAP_REGISTRY slot. + * Registries are JS Maps. + * + * Returns: the source map registry, a JS Map object + */ +JSObject* gjs_get_source_map_registry(JSObject* global) { + JS::Value source_map_registry = + gjs_get_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY); + + g_assert(source_map_registry.isObject()); + return &source_map_registry.toObject(); +} + /** * gjs_module_load: + * @cx: the current JSContext + * @identifier: specifier of the module to load + * @file_uri: URI to load the module from * - * Loads and registers a module given a specifier and - * URI. + * Loads and registers a module given a specifier and URI. * - * @returns whether an error occurred while resolving the specifier. + * Returns: whether an error occurred while resolving the specifier. */ JSObject* gjs_module_load(JSContext* cx, const char* identifier, const char* file_uri) { @@ -388,7 +408,7 @@ JSObject* gjs_module_load(JSContext* cx, const char* identifier, args[1].setString(uri); gjs_debug(GJS_DEBUG_IMPORTER, - "Module resolve hook for module '%s' (%s), global %p", identifier, + "Module load hook for module '%s' (%s), global %p", identifier, file_uri, global.get()); JS::RootedValue result(cx); @@ -401,8 +421,11 @@ JSObject* gjs_module_load(JSContext* cx, const char* identifier, /** * import_native_module_sync: + * @identifier: the specifier for the module to import * - * @brief Synchronously imports native "modules" from the import global's + * JS function exposed as `import.meta.importSync` in internal modules only. + * + * Synchronously imports native "modules" from the import global's * native registry. This function does not do blocking I/O so it is * safe to call it synchronously for accessing native "modules" within * modules. This function is always called within the import global's @@ -410,11 +433,7 @@ JSObject* gjs_module_load(JSContext* cx, const char* identifier, * * Compare gjs_import_native_module() for the legacy importer. * - * @param cx the current JSContext - * @param argc - * @param vp - * - * @returns whether an error occurred while importing the native module. + * Returns: the imported JS module object. */ static bool import_native_module_sync(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -456,15 +475,15 @@ static bool import_native_module_sync(JSContext* cx, unsigned argc, /** * gjs_populate_module_meta: + * @cx: the current JSContext + * @private_ref: the JS private value for the #Module object, as a JS Object + * @meta: the JS `import.meta` object * - * Hook SpiderMonkey calls to populate the import.meta object. - * Defines a property "import.meta.url", and additionally a method - * "import.meta.importSync" if this is an internal module. + * Hook SpiderMonkey calls to populate the `import.meta` object. + * Defines a property `import.meta.url`, and additionally a method + * `import.meta.importSync` if this is an internal module. * - * @param private_ref the private value for the #Module object - * @param meta the import.meta object - * - * @returns whether an error occurred while populating the module meta. + * Returns: whether an error occurred while populating the module meta. */ bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref, JS::HandleObject meta) { @@ -504,7 +523,7 @@ static bool canonicalize_specifier(JSContext* cx, if (!specifier_utf8) return false; - GjsAutoChar scheme, host, path, query; + Gjs::AutoChar scheme, host, path, query; if (!g_uri_split(specifier_utf8.get(), G_URI_FLAGS_NONE, scheme.out(), nullptr, host.out(), nullptr, path.out(), query.out(), nullptr, nullptr)) @@ -512,10 +531,10 @@ static bool canonicalize_specifier(JSContext* cx, if (g_strcmp0(scheme, "gi")) { // canonicalize without the query portion to avoid it being encoded - GjsAutoChar for_file_uri = - g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1, - path.get(), nullptr, nullptr); - GjsAutoUnref file = g_file_new_for_uri(for_file_uri.get()); + Gjs::AutoChar for_file_uri{g_uri_join(G_URI_FLAGS_NONE, scheme.get(), + nullptr, host.get(), -1, + path.get(), nullptr, nullptr)}; + Gjs::AutoUnref file{g_file_new_for_uri(for_file_uri.get())}; for_file_uri = g_file_get_uri(file); host.reset(); path.reset(); @@ -525,9 +544,9 @@ static bool canonicalize_specifier(JSContext* cx, return false; } - GjsAutoChar canonical_specifier = + Gjs::AutoChar canonical_specifier{ g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1, - path.get(), query.get(), nullptr); + path.get(), query.get(), nullptr)}; JS::ConstUTF8CharsZ chars{canonical_specifier, strlen(canonical_specifier)}; JS::RootedString new_specifier{cx, JS_NewStringCopyUTF8Z(cx, chars)}; if (!new_specifier) @@ -539,16 +558,18 @@ static bool canonicalize_specifier(JSContext* cx, /** * gjs_module_resolve: + * @cx: the current JSContext + * @importing_module_priv: the private JS Object of the #Module object + * initiating the import, or a JS null value + * @module_request: the module request object * - * Hook SpiderMonkey calls to resolve import specifiers. - * - * @param importingModulePriv the private value of the #Module object initiating - * the import, or a JS null value - * @param specifier the import specifier to resolve + * This function resolves import specifiers. It is called internally by + * SpiderMonkey as a hook. * - * @returns whether an error occurred while resolving the specifier. + * Returns: whether an error occurred while resolving the specifier. */ -JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, +JSObject* gjs_module_resolve(JSContext* cx, + JS::HandleValue importing_module_priv, JS::HandleObject module_request) { g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) || gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) && @@ -567,13 +588,13 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, return nullptr; JS::RootedValueArray<2> args(cx); - args[0].set(importingModulePriv); + args[0].set(importing_module_priv); args[1].setString(specifier); gjs_debug(GJS_DEBUG_IMPORTER, "Module resolve hook for module %s (relative to %s), global %p", gjs_debug_string(specifier).c_str(), - gjs_debug_value(importingModulePriv).c_str(), global.get()); + gjs_debug_value(importing_module_priv).c_str(), global.get()); JS::RootedValue result(cx); if (!JS::Call(cx, loader, "moduleResolveHook", args, &result)) diff --git a/cjs/module.h b/cjs/module.h index 3465a6985..2526dd110 100644 --- a/cjs/module.h +++ b/cjs/module.h @@ -30,6 +30,9 @@ JSObject* gjs_get_native_registry(JSObject* global); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_get_module_registry(JSObject* global); +GJS_JSAPI_RETURN_CONVENTION +JSObject* gjs_get_source_map_registry(JSObject* global); + GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_load(JSContext* cx, const char* identifier, const char* uri); diff --git a/cjs/native.cpp b/cjs/native.cpp index b474ef7cd..d11c42862 100644 --- a/cjs/native.cpp +++ b/cjs/native.cpp @@ -4,10 +4,8 @@ #include -#include #include // for tie #include -#include // for pair #include diff --git a/cjs/objectbox.cpp b/cjs/objectbox.cpp index a5f6e80d7..d5f5f878c 100644 --- a/cjs/objectbox.cpp +++ b/cjs/objectbox.cpp @@ -17,6 +17,7 @@ #include #include +#include "cjs/auto.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/objectbox.h" diff --git a/cjs/objectbox.h b/cjs/objectbox.h index bb17b3bfa..390e65df0 100644 --- a/cjs/objectbox.h +++ b/cjs/objectbox.h @@ -10,7 +10,7 @@ #include -#include "cjs/jsapi-util.h" +#include "cjs/auto.h" #include "cjs/macros.h" class JSTracer; @@ -19,7 +19,7 @@ class ObjectBox { static void destroy(ObjectBox*); public: - using Ptr = GjsAutoPointer; + using Ptr = Gjs::AutoPointer; [[nodiscard]] static GType gtype(); @@ -39,5 +39,5 @@ class ObjectBox { struct impl; static void destroy_impl(impl*); - GjsAutoPointer m_impl; + Gjs::AutoPointer m_impl; }; diff --git a/cjs/profiler-private.h b/cjs/profiler-private.h index 60f328739..1e7818e28 100644 --- a/cjs/profiler-private.h +++ b/cjs/profiler-private.h @@ -8,6 +8,7 @@ #include #include +#include #include // for JSFinalizeStatus, JSGCStatus, GCReason #include @@ -18,17 +19,20 @@ #include "cjs/context.h" #include "cjs/profiler.h" +#define GJS_PROFILER_DYNAMIC_STRING(cx, str) \ + js::GetContextProfilingStackIfEnabled(cx) ? str : "" + class AutoProfilerLabel { public: explicit inline AutoProfilerLabel(JSContext* cx, const char* label, - const char* dynamicString, + const std::string& dynamicString, JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::OTHER, uint32_t flags = 0) : m_stack(js::GetContextProfilingStackIfEnabled(cx)) { if (m_stack) - m_stack->pushLabelFrame(label, dynamicString, this, categoryPair, - flags); + m_stack->pushLabelFrame(label, dynamicString.c_str(), this, + categoryPair, flags); } inline ~AutoProfilerLabel() { diff --git a/cjs/profiler.cpp b/cjs/profiler.cpp index 9247af060..b1fcce9dc 100644 --- a/cjs/profiler.cpp +++ b/cjs/profiler.cpp @@ -8,9 +8,7 @@ #endif #ifdef ENABLE_PROFILER -// IWYU has a weird loop where if this is present, it asks for it to be removed, -// and if absent, asks for it to be added -# include // IWYU pragma: keep +# include # include # include # include // for sscanf @@ -39,6 +37,7 @@ #include #include // for ProfilingStack operators +#include "cjs/auto.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" // for gjs_explain_gc_reason #include "cjs/mem-private.h" @@ -149,15 +148,14 @@ static GjsContext *profiling_context; g_assert(((void) "Profiler must be set up before extracting maps", self)); - GjsAutoChar path = g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid)); + Gjs::AutoChar path{g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid))}; - char *content_tmp; + Gjs::AutoChar content; size_t len; - if (!g_file_get_contents(path, &content_tmp, &len, nullptr)) - return false; - GjsAutoChar content = content_tmp; + if (!g_file_get_contents(path, content.out(), &len, nullptr)) + return false; - GjsAutoStrv lines = g_strsplit(content, "\n", 0); + Gjs::AutoStrv lines{g_strsplit(content, "\n", 0)}; for (size_t ix = 0; lines[ix]; ix++) { char file[256]; @@ -523,7 +521,7 @@ gjs_profiler_start(GjsProfiler *self) self->capture = sysprof_capture_writer_new_from_fd(self->fd, 0); self->fd = -1; } else { - GjsAutoChar path = g_strdup(self->filename); + Gjs::AutoChar path{g_strdup(self->filename)}; if (!path) path = g_strdup_printf("gjs-%jd.syscap", intmax_t(self->pid)); @@ -878,7 +876,7 @@ void gjs_profiler_set_fd(GjsProfiler* self, int fd) { void _gjs_profiler_set_finalize_status(GjsProfiler* self, JSFinalizeStatus status) { #ifdef ENABLE_PROFILER - // Implementation note for mozjs-128: + // Implementation note for mozjs-140: // // Sweeping happens in three phases: // 1st phase (JSFINALIZE_GROUP_PREPARE): the collector prepares to sweep a @@ -886,9 +884,9 @@ void _gjs_profiler_set_finalize_status(GjsProfiler* self, // unmarked things have been removed, but no GC thing has been swept. 3rd // Phase (JSFINALIZE_GROUP_END): all dead GC things for a group of zones // have been swept. The above repeats for each sweep group. - // JSFINALIZE_COLLECTION_END occurs at the end of all GC. (see jsgc.cpp, - // BeginSweepPhase/BeginSweepingZoneGroup and SweepPhase, all called from - // IncrementalCollectSlice). + // JSFINALIZE_COLLECTION_END occurs at the end of all GC. (see + // js/src/gc/GC.cpp, GCRuntime::beginSweepPhase, beginSweepingSweepGroup, + // and endSweepPhase, all called from incrementalSlice). // // Incremental GC muddies the waters, because BeginSweepPhase is always run // to entirety, but SweepPhase can be run incrementally and mixed with JS diff --git a/cjs/promise.cpp b/cjs/promise.cpp index 095d20bb6..97e0cdcca 100644 --- a/cjs/promise.cpp +++ b/cjs/promise.cpp @@ -18,6 +18,7 @@ #include // for JS_NewPlainObject #include // for RunJobs +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" @@ -47,16 +48,18 @@ namespace Gjs { /** - * @brief a custom GSource which handles draining our job queue. + * PromiseJobDispatcher::Source: + * + * A custom GSource which handles draining our job queue. */ class PromiseJobDispatcher::Source : public GSource { // The private GJS context this source runs within. GjsContextPrivate* m_gjs; // The main context this source attaches to. - GjsAutoMainContext m_main_context; + AutoMainContext m_main_context; // The cancellable that stops this source. - GjsAutoUnref m_cancellable; - GjsAutoPointer m_cancellable_source; + AutoUnref m_cancellable; + AutoPointer m_cancellable_source; // G_PRIORITY_HIGH is normally -100, we set 10 times that to ensure our // source always has the greatest priority. This means our prepare will @@ -89,23 +92,20 @@ class PromiseJobDispatcher::Source : public GSource { public: /** - * @brief Constructs a new GjsPromiseJobQueueSource GSource and adds a - * reference to the associated main context. + * Source::Source: + * @gjs: the GJS object + * @main_context: GLib main context to associate with the source * - * @param cx the current JSContext - * @param cancellable an optional cancellable + * Constructs a new GSource for the PromiseJobDispatcher and adds a + * reference to the associated main context. */ Source(GjsContextPrivate* gjs, GMainContext* main_context) : m_gjs(gjs), - m_main_context(main_context, GjsAutoTakeOwnership()), + m_main_context(main_context, TakeOwnership{}), m_cancellable(g_cancellable_new()), m_cancellable_source(g_cancellable_source_new(m_cancellable)) { g_source_set_priority(this, PRIORITY); -#if GLIB_CHECK_VERSION(2, 70, 0) g_source_set_static_name(this, "GjsPromiseJobQueueSource"); -#else - g_source_set_name(this, "GjsPromiseJobQueueSource"); -#endif // Add our cancellable source to our main source, // this will trigger the main source if our cancellable @@ -121,13 +121,17 @@ class PromiseJobDispatcher::Source : public GSource { bool is_running() { return !!g_source_get_context(this); } /** - * @brief Trigger the cancellable, detaching our source. + * Source::cancel: + * + * Trigger the cancellable, detaching our source. */ void cancel() { g_cancellable_cancel(m_cancellable); } /** - * @brief Reset the cancellable and prevent the source from stopping, - * overriding a previous cancel() call. Called by start() in - * PromiseJobDispatcher to ensure the custom source will start. + * Source::reset: + * + * Reset the cancellable and prevent the source from stopping, overriding a + * previous cancel() call. Called by PromiseJobDispatcher::start() to ensure + * the custom source will start. */ void reset() { if (!g_cancellable_is_cancelled(m_cancellable)) diff --git a/cjs/promise.h b/cjs/promise.h index 4e1363507..86473a4db 100644 --- a/cjs/promise.h +++ b/cjs/promise.h @@ -11,25 +11,26 @@ #include -#include "cjs/jsapi-util.h" +#include "cjs/auto.h" class GjsContextPrivate; -using GjsAutoMainContext = - GjsAutoPointer; - namespace Gjs { +using AutoMainContext = AutoPointer; + /** - * @brief A class which wraps a custom GSource and handles associating it with a + * PromiseJobDispatcher: + * + * A class which wraps a custom GSource and handles associating it with a * GMainContext. While it is running, it will attach the source to the main * context so that promise jobs are run at the appropriate time. */ class PromiseJobDispatcher { class Source; // The thread-default GMainContext - GjsAutoMainContext m_main_context; + AutoMainContext m_main_context; // The custom source. std::unique_ptr m_source; @@ -38,17 +39,23 @@ class PromiseJobDispatcher { ~PromiseJobDispatcher(); /** - * @brief Start (or resume) dispatching jobs from the promise job queue + * PromiseJobDispatcher::start: + * + * Starts (or resumes) dispatching jobs from the promise job queue. */ void start(); /** - * @brief Stop dispatching + * PromiseJobDispatcher::stop: + * + * Stops dispatching jobs from the promise job queue. */ void stop(); /** - * @brief Whether the dispatcher is currently running + * PromiseJobDispatcher::is_running: + * + * Returns: Whether the dispatcher is currently running. */ bool is_running(); }; diff --git a/cjs/stack.cpp b/cjs/stack.cpp index ba584d819..8cac7a9d3 100644 --- a/cjs/stack.cpp +++ b/cjs/stack.cpp @@ -4,8 +4,6 @@ #include -#include // for stderr - #include #include @@ -17,9 +15,9 @@ #include // for UniqueChars #include +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" -#include "cjs/jsapi-util.h" void gjs_context_print_stack_stderr(GjsContext *context) @@ -33,11 +31,11 @@ gjs_context_print_stack_stderr(GjsContext *context) void gjs_dumpstack(void) { - GjsSmartPointer contexts = gjs_context_get_all(); + Gjs::SmartPointer contexts{gjs_context_get_all()}; GList *iter; for (iter = contexts; iter; iter = iter->next) { - GjsAutoUnref context(GJS_CONTEXT(iter->data)); + Gjs::AutoUnref context{GJS_CONTEXT(iter->data)}; gjs_context_print_stack_stderr(context); } } @@ -47,12 +45,12 @@ gjs_dumpstack_string() { std::string out; std::ostringstream all_traces; - GjsSmartPointer contexts = gjs_context_get_all(); + Gjs::SmartPointer contexts{gjs_context_get_all()}; js::Sprinter printer; GList *iter; for (iter = contexts; iter; iter = iter->next) { - GjsAutoUnref context(GJS_CONTEXT(iter->data)); + Gjs::AutoUnref context{GJS_CONTEXT(iter->data)}; if (!printer.init()) { all_traces << "No stack trace for context " << context.get() << ": out of memory\n\n"; diff --git a/cjs/text-encoding.cpp b/cjs/text-encoding.cpp index 656185965..076741701 100644 --- a/cjs/text-encoding.cpp +++ b/cjs/text-encoding.cpp @@ -11,7 +11,6 @@ #include // for strcmp, memchr, strlen #include -#include // for nullptr_t #include // for distance #include // for unique_ptr #include // for u16string @@ -43,6 +42,8 @@ #include #include +#include "cjs/auto.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -55,7 +56,7 @@ static void gfree_arraybuffer_contents(void* contents, void*) { } static std::nullptr_t gjs_throw_type_error_from_gerror( - JSContext* cx, GjsAutoError const& error) { + JSContext* cx, Gjs::AutoError const& error) { g_return_val_if_fail(error, nullptr); gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "%s", error->message); return nullptr; @@ -77,9 +78,9 @@ 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) { - GjsAutoError error; - GjsAutoUnref converter( - g_charset_converter_new(UTF16_CODESET, from_codeset, &error)); + Gjs::AutoError error; + Gjs::AutoUnref converter{ + g_charset_converter_new(UTF16_CODESET, from_codeset, &error)}; // This should only throw if an encoding is not available. if (error) @@ -122,7 +123,7 @@ static JSString* gjs_lossy_decode_from_uint8array_slow( std::u16string output_str = u""; do { - GjsAutoError local_error; + Gjs::AutoError local_error; // Create a buffer to convert into. std::unique_ptr buffer = std::make_unique(buffer_size); @@ -210,11 +211,11 @@ static JSString* gjs_decode_from_uint8array_slow(JSContext* cx, } size_t bytes_written, bytes_read; - GjsAutoError error; + Gjs::AutoError error; - GjsAutoChar bytes = - g_convert(reinterpret_cast(input), input_len, - UTF16_CODESET, encoding, &bytes_read, &bytes_written, &error); + Gjs::AutoChar bytes{g_convert(reinterpret_cast(input), + input_len, UTF16_CODESET, encoding, + &bytes_read, &bytes_written, &error)}; if (error) return gjs_throw_type_error_from_gerror(cx, error); @@ -239,7 +240,7 @@ static JSString* gjs_decode_from_uint8array_slow(JSContext* cx, g_ascii_strcasecmp(encoding, "utf8") == 0) return true; - GjsAutoChar stripped(g_strdup(encoding)); + Gjs::AutoChar stripped{g_strdup(encoding)}; g_strstrip(stripped); // modifies in place return g_ascii_strcasecmp(stripped, "utf-8") == 0 || g_ascii_strcasecmp(stripped, "utf8") == 0; @@ -402,8 +403,8 @@ JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str, array_buffer = JS::NewArrayBufferWithContents(cx, utf8_len, std::move(utf8)); } else { - GjsAutoError error; - GjsAutoChar encoded = nullptr; + Gjs::AutoError error; + Gjs::AutoChar encoded; size_t bytes_written; /* Scope for AutoCheckCannotGC, will crash if a GC is triggered diff --git a/debian/changelog b/debian/changelog index 23351edfc..a66acfc99 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +cjs (139.9) unstable; urgency=medium + + * mozjs 140, gjs 1.86.0 + + -- Michael Webster Thu, 22 Jan 2026 12:00:00 +0000 + cjs (128.1) zena; urgency=medium [ Michael Webster ] diff --git a/debian/control b/debian/control index 200817de2..b8fa3a5f0 100644 --- a/debian/control +++ b/debian/control @@ -7,28 +7,29 @@ Build-Depends: at-spi2-core , debhelper-compat (= 13), dh-sequence-gir, gir1.2-cairo-1.0-dev, - gir1.2-gio-2.0-dev, - gir1.2-girepository-2.0-dev, - gir1.2-gobject-2.0-dev, - gir1.2-gtk-3.0, - gobject-introspection (>= 1.71), + gir1.2-gio-2.0-dev (>= 2.86.0-7~), + gir1.2-girepository-3.0-dev (>= 2.86.0-7~), + gir1.2-gobject-2.0-dev (>= 2.86.0-7~), libcairo2-dev, libffi-dev, - libgirepository-1.0-dev (>= 1.71), - libglib2.0-dev, - libmozjs-128-dev, + libgirepository-2.0-dev (>= 2.86.0-7~), + libglib2.0-dev (>= 2.86.0-7~), + libgtk-3-dev (>= 3.24.49-3~) , + libgtk-4-dev, + libmozjs-140-dev, + libmozjs-140-dev (>= 140.6.0-1.1~) [arm64], libreadline-dev, - meson, - libsysprof-capture-4-dev (>= 3.38.0) [amd64 arm64 armel armhf i386 mips64el ppc64el riscv64 s390x hppa loong64 powerpc ppc64 sh4], + meson (>= 1.4), + libsysprof-capture-4-dev [linux-any], xauth , xvfb Rules-Requires-Root: no -Standards-Version: 4.7.0 +Standards-Version: 4.7.3 Vcs-Git: https://github.com/linuxmint/cjs.git Package: cjs Architecture: any -Depends: gir1.2-gtk-3.0, +Depends: libcjs0 (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends} Description: Mozilla-based javascript bindings for the Cinnamon platform (cli tool) @@ -43,7 +44,7 @@ Architecture: any Multi-Arch: same Section: libs Depends: gir1.2-gio-2.0, - gir1.2-girepository-2.0, + gir1.2-girepository-3.0 (>= 2.86.0-7~), gir1.2-glib-2.0, gir1.2-gobject-2.0, ${gir:Depends}, @@ -79,9 +80,9 @@ Architecture: any Multi-Arch: same Section: libdevel Depends: libcairo2-dev, - libgirepository-1.0-dev, + libgirepository-2.0-dev (>= 2.86.0-7~), libcjs0 (= ${binary:Version}), - libmozjs-128-dev, + libmozjs-140-dev, ${misc:Depends} Description: Mozilla-based javascript bindings for Cinnamon - development files Makes it possible for applications to use all of Cinnamon's platform diff --git a/debian/copyright b/debian/copyright index 4f6c7a9af..724cd553a 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,7 +1,14 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: cjs -Upstream-Contact: Clement Lefebvre -Source: https://github.com/linuxmint/cjs +Source: https://download.gnome.org/sources/gjs/ +Comment: + This work was packaged for Debian by: + . + Gustavo Noronha Silva on Mon, 12 Oct 2009 18:38:36 -0300 + . + Upstream Author: + . + litl, LLC +Upstream-Name: gjs Files: * Copyright: diff --git a/debian/gbp.conf b/debian/gbp.conf index 7db90fe6f..b37f0bdca 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -1,5 +1,17 @@ +[DEFAULT] +pristine-tar = True +debian-branch = debian/latest +upstream-branch = upstream/latest +upstream-vcs-tag = %(version)s + [buildpackage] sign-tags = True [dch] multimaint-merge = True + +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit + +[pq] +patch-numbers = False diff --git a/debian/libcjs0.symbols b/debian/libcjs0.symbols index a84d59777..e7b572cc6 100644 --- a/debian/libcjs0.symbols +++ b/debian/libcjs0.symbols @@ -1,92 +1,93 @@ libcjs.so.0 libcjs0 #MINVER# -* Build-Depends-Package: libcjs-dev - gjs_bindtextdomain@Base 2.4.1 - gjs_cairo_pdf_surface_proto_props@Base 5.4.0 - gjs_clear_terminal@Base 5.4.0 - gjs_console_clear@Base 5.4.0 - gjs_console_is_tty@Base 5.4.0 - gjs_context_define_string_array@Base 2.0.0 - gjs_context_eval@Base 2.0.0 - gjs_context_eval_file@Base 2.0.0 - gjs_context_eval_module@Base 5.4.0 - gjs_context_eval_module_file@Base 5.4.0 - gjs_context_gc@Base 2.0.0 - gjs_context_get_all@Base 2.0.0 - gjs_context_get_current@Base 2.4.1 - gjs_context_get_native_context@Base 2.0.0 - gjs_context_get_profiler@Base 4.0.0 - gjs_context_get_type@Base 2.0.0 - gjs_context_make_current@Base 2.4.1 - gjs_context_maybe_gc@Base 2.0.0 - gjs_context_new@Base 2.0.0 - 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 - gjs_coverage_get_type@Base 5.4.0 - gjs_coverage_new@Base 2.4.1 - gjs_coverage_write_statistics@Base 2.4.1 - gjs_dbus_implementation_emit_property_changed@Base 2.0.0 - gjs_dbus_implementation_emit_signal@Base 2.0.0 - gjs_dbus_implementation_get_type@Base 2.0.0 - gjs_dbus_implementation_unexport@Base 5.7.0 - gjs_dbus_implementation_unexport_from_connection@Base 5.7.0 - gjs_dumpstack@Base 2.0.0 - gjs_error_quark@Base 2.0.0 - gjs_format_int_alternative_output@Base 2.4.1 - gjs_g_binding_group_bind_full@Base 5.7.0 - gjs_g_object_bind_property_full@Base 5.4.0 - gjs_get_js_version@Base 4.0.0 - gjs_gobject_class_info@Base 5.4.0 - gjs_gobject_interface_info@Base 5.4.0 - gjs_gtk_container_child_set_property@Base 2.4.1 - gjs_gtk_custom_sorter_new@Base 5.7.0 - gjs_gtk_custom_sorter_set_sort_func@Base 5.7.0 - gjs_importer_class@Base 5.4.0 - gjs_importer_proto_funcs@Base 5.4.0 - gjs_js_error_get_type@Base 4.0.0 - gjs_js_error_quark@Base 4.0.0 - gjs_list_store_insert_sorted@Base 5.4.0 - gjs_list_store_sort@Base 5.4.0 - gjs_locale_category_get_type@Base 3.4.4 - gjs_log_set_writer_default@Base 5.4.0 - gjs_log_set_writer_func@Base 5.4.0 - gjs_match_info_expand_references@Base 6.5.0 - gjs_match_info_fetch@Base 6.5.0 - gjs_match_info_fetch_all@Base 6.5.0 - gjs_match_info_fetch_named@Base 6.5.0 - gjs_match_info_fetch_named_pos@Base 6.5.0 - gjs_match_info_fetch_pos@Base 6.5.0 - gjs_match_info_free@Base 6.5.0 - gjs_match_info_get_match_count@Base 6.5.0 - gjs_match_info_get_regex@Base 6.5.0 - gjs_match_info_get_string@Base 6.5.0 - gjs_match_info_get_type@Base 6.5.0 - gjs_match_info_is_partial_match@Base 6.5.0 - gjs_match_info_matches@Base 6.5.0 - gjs_match_info_next@Base 6.5.0 - gjs_match_info_ref@Base 6.5.0 - gjs_match_info_unref@Base 6.5.0 - gjs_memory_report@Base 2.0.0 - gjs_native_promise_module_funcs@Base 5.7.0 - gjs_param_class@Base 5.4.0 - gjs_param_spec_get_flags@Base 3.4.4 - gjs_param_spec_get_owner_type@Base 3.4.4 - gjs_param_spec_get_value_type@Base 3.4.4 - gjs_profiler_chain_signal@Base 4.0.0 - gjs_profiler_get_type@Base 4.0.0 - gjs_profiler_set_capture_writer@Base 5.4.0 - gjs_profiler_set_fd@Base 4.6.0 - gjs_profiler_set_filename@Base 4.0.0 - gjs_profiler_start@Base 4.0.0 - gjs_profiler_stop@Base 4.0.0 - gjs_regex_match@Base 6.5.0 - gjs_regex_match_all@Base 6.5.0 - gjs_regex_match_all_full@Base 6.5.0 - gjs_regex_match_full@Base 6.5.0 - gjs_repo_class@Base 5.4.0 - gjs_setlocale@Base 3.4.4 - gjs_textdomain@Base 2.4.1 + gjs_bindtextdomain@Base 140.0 + gjs_cairo_pdf_surface_proto_props@Base 140.0 + gjs_clear_terminal@Base 140.0 + gjs_console_clear@Base 140.0 + gjs_console_get_repl_history_path@Base 140.0 + gjs_console_is_tty@Base 140.0 + gjs_console_write_repl_history@Base 140.0 + gjs_context_define_string_array@Base 140.0 + gjs_context_eval@Base 140.0 + gjs_context_eval_file@Base 140.0 + gjs_context_eval_module@Base 140.0 + gjs_context_eval_module_file@Base 140.0 + gjs_context_gc@Base 140.0 + gjs_context_get_all@Base 140.0 + gjs_context_get_current@Base 140.0 + gjs_context_get_native_context@Base 140.0 + gjs_context_get_profiler@Base 140.0 + gjs_context_get_repl_history_path@Base 140.0 + gjs_context_get_type@Base 140.0 + gjs_context_make_current@Base 140.0 + gjs_context_maybe_gc@Base 140.0 + gjs_context_new@Base 140.0 + gjs_context_new_with_search_path@Base 140.0 + gjs_context_print_stack_stderr@Base 140.0 + gjs_context_register_module@Base 140.0 + gjs_context_run_in_realm@Base 140.0 + gjs_context_set_argv@Base 140.0 + gjs_context_setup_debugger_console@Base 140.0 + gjs_coverage_enable@Base 140.0 + gjs_coverage_get_type@Base 140.0 + gjs_coverage_new@Base 140.0 + gjs_coverage_write_statistics@Base 140.0 + gjs_dbus_implementation_emit_property_changed@Base 140.0 + gjs_dbus_implementation_emit_signal@Base 140.0 + gjs_dbus_implementation_get_type@Base 140.0 + gjs_dbus_implementation_unexport@Base 140.0 + gjs_dbus_implementation_unexport_from_connection@Base 140.0 + gjs_dumpstack@Base 140.0 + gjs_error_quark@Base 140.0 + gjs_g_binding_group_bind_full@Base 140.0 + gjs_g_object_bind_property_full@Base 140.0 + gjs_get_js_version@Base 140.0 + gjs_gobject_class_info@Base 140.0 + gjs_gobject_interface_info@Base 140.0 + gjs_gtk_container_child_set_property@Base 140.0 + gjs_gtk_custom_sorter_new@Base 140.0 + gjs_gtk_custom_sorter_set_sort_func@Base 140.0 + gjs_importer_class@Base 140.0 + gjs_importer_proto_funcs@Base 140.0 + gjs_js_error_get_type@Base 140.0 + gjs_js_error_quark@Base 140.0 + gjs_list_store_insert_sorted@Base 140.0 + gjs_list_store_sort@Base 140.0 + gjs_locale_category_get_type@Base 140.0 + gjs_log_set_writer_default@Base 140.0 + gjs_log_set_writer_func@Base 140.0 + gjs_match_info_expand_references@Base 140.0 + gjs_match_info_fetch@Base 140.0 + gjs_match_info_fetch_all@Base 140.0 + gjs_match_info_fetch_named@Base 140.0 + gjs_match_info_fetch_named_pos@Base 140.0 + gjs_match_info_fetch_pos@Base 140.0 + gjs_match_info_free@Base 140.0 + gjs_match_info_get_match_count@Base 140.0 + gjs_match_info_get_regex@Base 140.0 + gjs_match_info_get_string@Base 140.0 + gjs_match_info_get_type@Base 140.0 + gjs_match_info_is_partial_match@Base 140.0 + gjs_match_info_matches@Base 140.0 + gjs_match_info_next@Base 140.0 + gjs_match_info_ref@Base 140.0 + gjs_match_info_unref@Base 140.0 + gjs_memory_report@Base 140.0 + gjs_native_promise_module_funcs@Base 140.0 + gjs_param_class@Base 140.0 + gjs_param_spec_get_flags@Base 140.0 + gjs_param_spec_get_owner_type@Base 140.0 + gjs_param_spec_get_value_type@Base 140.0 + gjs_profiler_chain_signal@Base 140.0 + gjs_profiler_get_type@Base 140.0 + gjs_profiler_set_capture_writer@Base 140.0 + gjs_profiler_set_fd@Base 140.0 + gjs_profiler_set_filename@Base 140.0 + gjs_profiler_start@Base 140.0 + gjs_profiler_stop@Base 140.0 + gjs_regex_match@Base 140.0 + gjs_regex_match_all@Base 140.0 + gjs_regex_match_all_full@Base 140.0 + gjs_regex_match_full@Base 140.0 + gjs_repo_class@Base 140.0 + gjs_set_thread_locale@Base 140.0 + gjs_textdomain@Base 140.0 diff --git a/debian/rules b/debian/rules index 0ec4ca9c8..867b7bccd 100755 --- a/debian/rules +++ b/debian/rules @@ -4,25 +4,29 @@ built_binaries := $(shell dh_listpackages) export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_LDFLAGS_MAINT_APPEND = -Wl,-z,defs -export DPKG_GENSYMBOLS_CHECK_LEVEL = 2 include /usr/share/dpkg/default.mk export DH_VERBOSE=1 -ifeq ($(filter amd64 arm64 armel armhf i386 mips64el ppc64el riscv64 s390x hppa loong64 powerpc ppc64 sh4,$(DEB_HOST_ARCH)),) +ifneq ($(DEB_HOST_ARCH_OS),linux) SYSPROF = -Dprofiler=disabled endif +ifneq ($(filter arm64,$(DEB_HOST_ARCH)),) +SYMBOLIC_FUNCTIONS = -Dbsymbolic_functions=false +endif + %: dh $@ override_dh_auto_configure: dh_auto_configure -- \ --unity=on \ - -Dinstalled_tests=false \ -Dauto_features=enabled \ + -Dinstalled_tests=false \ $(SYSPROF) \ + $(SYMBOLIC_FUNCTIONS) \ $(NULL) override_dh_girepository: diff --git a/debian/shlibs.local b/debian/shlibs.local deleted file mode 100644 index 4972e4394..000000000 --- a/debian/shlibs.local +++ /dev/null @@ -1 +0,0 @@ -libcjs 0 libcjs0 (= ${binary:Version}) diff --git a/debian/tests/build b/debian/tests/build old mode 100644 new mode 100755 index 599fbc682..c3c439ed5 --- a/debian/tests/build +++ b/debian/tests/build @@ -16,7 +16,7 @@ else CROSS_COMPILE= fi -cat < cjstest.c +cat < gjstest.c #include int main() @@ -29,8 +29,8 @@ EOF # Deliberately word-splitting, that's how pkg-config works: # shellcheck disable=SC2046 -"${CROSS_COMPILE}gcc" -o cjstest cjstest.c $("${CROSS_COMPILE}pkg-config" --cflags --libs cjs-1.0) +"${CROSS_COMPILE}gcc" -o gjstest gjstest.c $("${CROSS_COMPILE}pkg-config" --cflags --libs gjs-1.0) echo "build: OK" -[ -x cjstest ] -./cjstest +[ -x gjstest ] +./gjstest echo "run: OK" diff --git a/debian/tests/control b/debian/tests/control index f1ec55d1e..6316b0b12 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -1,11 +1,11 @@ Tests: build Restrictions: superficial Depends: build-essential, - libcjs-dev + libgjs-dev -#Tests: installed-tests -#Depends: dbus-daemon, -# cjs-tests, -# gnome-desktop-testing, -# xauth, -# xvfb +Tests: installed-tests +Depends: dbus-daemon, + gjs-tests, + gnome-desktop-testing, + xauth, + xvfb diff --git a/debian/tests/installed-tests b/debian/tests/installed-tests old mode 100644 new mode 100755 index d4a7dd551..df28cae67 --- a/debian/tests/installed-tests +++ b/debian/tests/installed-tests @@ -9,4 +9,4 @@ export XDG_RUNTIME_DIR=$AUTOPKGTEST_TMP export LC_ALL=C.UTF-8 # dbus outputs activation messages to stderr which fails the test -dbus-run-session -- xvfb-run -a gnome-desktop-testing-runner cjs 2> >(grep -vE '^(Activating|Successfully activated)')>&2 +dbus-run-session -- xvfb-run -a gnome-desktop-testing-runner gjs 2> >(grep -vE '^(Activating|Successfully activated)')>&2 diff --git a/doc/CPP_Style_Guide.md b/doc/CPP_Style_Guide.md index 312838f6f..61dbb892d 100644 --- a/doc/CPP_Style_Guide.md +++ b/doc/CPP_Style_Guide.md @@ -419,7 +419,7 @@ Note that the header `` must be included before any SpiderMonkey headers. GJS headers should use quotes, _except_ in public header files (any -header file included from ``.) +header file included from ``.) If you need to include headers conditionally, add the conditional after the group that it belongs to, separated by a blank line. diff --git a/doc/Environment.md b/doc/Environment.md index 143db1973..30e4a59b0 100644 --- a/doc/Environment.md +++ b/doc/Environment.md @@ -17,6 +17,10 @@ GJS allows runtime configuration with a number of environment variables. Setting this variable to any value causes GJS to exit when an out-of-memory condition is encountered, instead of just printing a warning. +* `GJS_REPL_HISTORY` + + When not set, GJS persists REPL history in `gjs_repl_history` under the XDG user cache folder which is usually `~/.cache/`. Set this variable to a writable path to save REPL command history in an alternate location. If set to an empty string, then command history is not persisted. + ## JavaScript Engine * `JS_GC_ZEAL` diff --git a/doc/Hacking.md b/doc/Hacking.md index e939cb149..3268d4ecf 100644 --- a/doc/Hacking.md +++ b/doc/Hacking.md @@ -37,24 +37,26 @@ 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 "mozjs128" on some +gobject-introspection, SpiderMonkey (also called "mozjs140" 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, -libffi, gobject-introspection, Meson and readline. +We recommend installing your system's development packages for libffi, +gobject-introspection, Meson and readline.
Ubuntu - sudo apt-get install libglib2.0-dev libffi-dev libreadline-dev libgirepository1.0-dev meson + sudo apt-get install libffi-dev libreadline-dev libgirepository1.0-dev meson
Fedora - sudo dnf install glib2-devel libffi readline-devel gobject-introspection-devel meson + sudo dnf install libffi readline-devel gobject-introspection-devel meson
But, if your system's versions of these packages aren't new enough, then the build process will download and build sufficient versions. +(Temporarily, until GNOME 49 is released, GJS requires a development +version of GLib, so the build process will always download GLib.) SpiderMonkey cannot be auto-installed, so you will need to install it either through your system's package manager, or building it yourself. @@ -70,15 +72,14 @@ example, Fedora 41 or Ubuntu 24.10 and later versions), then you don't need to build it yourself. Install SpiderMonkey using your system's package manager instead: - - + sudo apt-get install libmozjs-140-dev +
Fedora - sudo dnf install mozjs128-devel + sudo dnf install mozjs140-devel
If you _are_ writing C++ code, then please build SpiderMonkey yourself @@ -86,12 +87,19 @@ 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/esr128/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/esr140/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`. ## First build +Temporarily, until GNOME 49 is released, you will need to first run: +```sh +export GI_TYPELIB_PATH=$(pkg-config --variable=typelibdir girepository-2.0) +``` +You need to do this once per terminal session, and you can put it in +your shell profile file if you want to do it automatically. + To build GJS, change to your `gjs` directory, and run: ```sh meson setup _build @@ -104,7 +112,7 @@ 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 -meson devenv -C _build cjs-console ../script.js +meson devenv -C _build gjs-console ../script.js ``` (the path `../script.js` is relative to `_build`, not the root folder) @@ -167,7 +175,7 @@ more likely to show up. To see which GC zeal options are available: ```sh -JS_GC_ZEAL=-1 js128 +JS_GC_ZEAL=-1 js140 ``` We include three test setups, `extra_gc`, `pre_verify`, and @@ -231,7 +239,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/esr128/docs/Building%20SpiderMonkey.md) +[embedder](https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr140/docs/Building%20SpiderMonkey.md) ## Troubleshooting diff --git a/doc/Overrides.md b/doc/Overrides.md index 08b079150..50cc276a9 100644 --- a/doc/Overrides.md +++ b/doc/Overrides.md @@ -210,7 +210,7 @@ Gio._promisify(Gio.File.prototype, 'load_contents_async', try { const file = Gio.File.new_for_path('file.txt'); - const [contents, len, etag] = await file.load_contents_async(null); + const [contents, etag] = await file.load_contents_async(null); } catch (e) { logError(e, 'Failed to load file contents'); } diff --git a/doc/README.md b/doc/README.md index aca225624..a59d7fce6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -53,47 +53,23 @@ new project and select `JavaScript` language. Here is a non-exhaustive list of applications written in GJS: -GNOME Apps +GNOME Core Apps * [Characters](https://gitlab.gnome.org/GNOME/gnome-characters) +* [Extensions](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/HEAD/subprojects/extensions-app) * [Maps](https://gitlab.gnome.org/GNOME/gnome-maps) * [Weather](https://gitlab.gnome.org/GNOME/gnome-weather) -* [Extensions](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/HEAD/subprojects/extensions-app) -* [Polari](https://gitlab.gnome.org/GNOME/polari) -* [Tangram](https://github.com/sonnyp/Tangram) -* [Flatseal](https://github.com/tchx84/Flatseal) + +GNOME Circle Apps + +* [Biblioteca](https://github.com/workbenchdev/Biblioteca) * [Commit](https://github.com/sonnyp/commit/) +* [Decibels](https://gitlab.gnome.org/GNOME/Incubator/decibels) (TypeScript) +* [Forge Sparks](https://github.com/rafaelmardojai/forge-sparks) * [Junction](https://github.com/sonnyp/Junction) -* [Oh My SVG](https://github.com/sonnyp/OhMySVG) +* [Polari](https://gitlab.gnome.org/GNOME/polari) +* [Tangram](https://github.com/sonnyp/Tangram) * [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/) -* [Almond](https://github.com/stanford-oval/almond-gnome/) -* [Lobjur](https://github.com/ranfdev/Lobjur) (Clojure) -* [Touché](https://github.com/JoseExposito/touche) -* [Annex](https://github.com/andyholmes/annex) -* [Bolso](https://github.com/felipeborges/bolso) -* [Design](https://github.com/dubstar-04/Design) -* [Capsule](https://gitlab.gnome.org/verdre/Capsule) -* [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 - -* [GNOME Books](https://gitlab.gnome.org/GNOME/gnome-books) -* [GNOME Documents](https://gitlab.gnome.org/GNOME/gnome-documents) ### GNOME Shell Extensions diff --git a/doc/Understanding-SpiderMonkey-code.md b/doc/Understanding-SpiderMonkey-code.md index 6c150d95b..b5243d713 100644 --- a/doc/Understanding-SpiderMonkey-code.md +++ b/doc/Understanding-SpiderMonkey-code.md @@ -16,6 +16,6 @@ - Many API functions return a `bool`. As in many other APIs, these should return `true` for success and `false` for failure. - Specific to SpiderMonkey is the convention that if an API function returns `false`, an exception should have been thrown (a JS exception, not a C++ exception, which would terminate the program!) This is also described as "an exception should be _pending_ on `cx`". Likewise, if the function returns `true`, an exception should not be pending. - There are two ways to violate that condition: - - Returning `false` with no exception pending. This is interpreted as an "uncatchable" exception, and it's used for out-of-memory and killing scripts within Firefox, for example. In GJS we use it to implement `System.exit()`. + - Returning `false` with no exception pending. This will fail assertions in debug builds. - Returning `true` while an exception is pending. This can easily happen by forgetting to check the return value of a SpiderMonkey function, and is a programmer error but not too serious. It will probably cause some warnings. - Likewise if an API function returns a pointer such as `JSObject*` (this is less common), the convention is that it should return `nullptr` on failure, in which case an exception should be pending. diff --git a/eslint.config.js b/eslint.config.js new file mode 120000 index 000000000..8e6e2c0df --- /dev/null +++ b/eslint.config.js @@ -0,0 +1 @@ +tools/eslint.config.js \ No newline at end of file diff --git a/examples/.eslintrc.yml b/examples/.eslintrc.yml deleted file mode 100644 index 922f3cdcc..000000000 --- a/examples/.eslintrc.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -parserOptions: - sourceType: 'module' -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - jsdoc/require-param: 'off' - jsdoc/require-param-type: 'off' - jsdoc/require-returns: 'off' - jsdoc/require-jsdoc: 'off' - jsdoc/check-tag-names: 'off' - jsdoc/check-param-names: 'off' diff --git a/examples/dbus-client.js b/examples/dbus-client.js index 07d090a81..bdbeaab5d 100644 --- a/examples/dbus-client.js +++ b/examples/dbus-client.js @@ -9,7 +9,7 @@ import Gio from 'gi://Gio'; */ const ifaceXml = ` - + @@ -45,7 +45,7 @@ function onNameAppeared(connection, name, _owner) { try { proxy = new TestProxy( Gio.DBus.session, - 'org.cinnamon.cjs.Test', + 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test' ); } catch (err) { @@ -119,7 +119,7 @@ function onNameVanished(connection, name) { let busWatchId = Gio.bus_watch_name( Gio.BusType.SESSION, - 'org.cinnamon.cjs.Test', + 'org.gnome.gjs.Test', Gio.BusNameWatcherFlags.NONE, onNameAppeared, onNameVanished @@ -143,7 +143,7 @@ proxy = null; new TestProxy( Gio.DBus.session, - 'org.cinnamon.cjs.Test', + 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', (sourceObj, error) => { // If @error is not `null` it will be an Error object indicating the diff --git a/examples/dbus-service.js b/examples/dbus-service.js index 31ebf6a20..c00d149ee 100644 --- a/examples/dbus-service.js +++ b/examples/dbus-service.js @@ -9,7 +9,7 @@ import Gio from 'gi://Gio'; */ const ifaceXml = ` - + @@ -113,7 +113,7 @@ function onNameLost(_connection, _name) { let ownerId = Gio.bus_own_name( Gio.BusType.SESSION, - 'org.cinnamon.cjs.Test', + 'org.gnome.gjs.Test', Gio.BusNameOwnerFlags.NONE, onBusAcquired, onNameAcquired, diff --git a/examples/gtk-application.js b/examples/gtk-application.js index 3f68db6b5..4aa4f26d2 100644 --- a/examples/gtk-application.js +++ b/examples/gtk-application.js @@ -26,7 +26,7 @@ let ExampleApplication = GObject.registerClass({ }, class ExampleApplication extends Gtk.Application { constructor() { super({ - application_id: 'org.cinnamon.cjs.ExampleApplication', + application_id: 'org.gnome.gjs.ExampleApplication', flags: Gio.ApplicationFlags.FLAGS_NONE, }); } @@ -82,7 +82,7 @@ let ExampleApplication = GObject.registerClass({ notif.set_title('Example Notification'); notif.set_body('Example Body'); notif.set_icon( - new Gio.ThemedIcon({name: 'xsi-dialog-information-symbolic'}) + new Gio.ThemedIcon({name: 'dialog-information-symbolic'}) ); // A default action for when the body of the notification is clicked diff --git a/examples/gtk4-template.js b/examples/gtk4-template.js index 890a85550..821287dc3 100644 --- a/examples/gtk4-template.js +++ b/examples/gtk4-template.js @@ -16,8 +16,9 @@ Gtk.init(); * - an absolute file URI, such as `file:///home/user/window.ui` * - a GResource URI, such as `resource:///org/gnome/AppName/window.ui` */ -const file = Gio.File.new_for_path('gtk4-template.ui'); -const [, template] = file.load_contents(null); +const file = Gio.File.new_for_uri(import.meta.url); +const templateFile = file.get_parent().resolve_relative_path('gtk4-template.ui'); +const [, template] = templateFile.load_contents(null); const ExampleWindow = GObject.registerClass({ diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp index f319b8bfd..3fd5c227e 100644 --- a/gi/arg-cache.cpp +++ b/gi/arg-cache.cpp @@ -10,14 +10,14 @@ #include #include +#include // for mem_fn #include -#include #include #include // for unordered_set::erase(), insert() #include #include -#include +#include #include #include @@ -28,6 +28,7 @@ #include #include // for InformalValueTypeName, JS_TypeOfValue #include // for JSTYPE_FUNCTION +#include #include "gi/arg-cache.h" #include "gi/arg-inl.h" @@ -40,18 +41,22 @@ #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" +#include "gi/info.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" // for GjsTypecheckNoThrow +#include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/enum-utils.h" // for operator&, operator|=, operator| #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" +using mozilla::Maybe, mozilla::Nothing, mozilla::Some; + enum ExpectedType { OBJECT, FUNCTION, @@ -138,20 +143,22 @@ namespace Arg { // implementation, taking advantage of the C++ multiple inheritance. struct BasicType { - constexpr BasicType() : m_tag(GI_TYPE_TAG_VOID) {} - constexpr explicit BasicType(GITypeTag tag) : m_tag(tag) {} - constexpr operator GITypeTag() const { return m_tag; } + constexpr explicit BasicType(GITypeTag tag) : m_tag(tag) { + g_assert(GI_TYPE_TAG_IS_BASIC(tag)); + } GITypeTag m_tag : 5; }; struct HasTypeInfo { - constexpr GITypeInfo* type_info() const { - // Should be const GITypeInfo*, but G-I APIs won't accept that - return const_cast(&m_type_info); + explicit HasTypeInfo(GI::TypeInfo info) { + GI::detail::Pointer::to_stack(GI::detail::Pointer::get_from(info), + &m_type_info); } - GITypeInfo m_type_info{}; + Maybe return_tag() const { return Some(ReturnTag{m_type_info}); } + + GI::StackTypeInfo m_type_info; }; struct Transferable { @@ -182,10 +189,7 @@ struct Positioned { constexpr bool set_out_parameter(GjsFunctionCallState* state, GIArgument* arg) { - // Clear all bits of the out C value. No one member is guaranteed to - // span the whole union on all architectures, so use memset() instead of - // gjs_arg_unset(). - memset(&state->out_cvalue(m_arg_pos), 0, sizeof(GIArgument)); + gjs_arg_unset(&state->out_cvalue(m_arg_pos)); // The value passed to the function is actually the address of the out // C value gjs_arg_set(arg, &state->out_cvalue(m_arg_pos)); @@ -203,28 +207,228 @@ struct Positioned { uint8_t m_arg_pos = 0; }; -struct Array : BasicType { - uint8_t m_length_pos = 0; +struct ExplicitArray { + uint8_t m_length_pos; GIDirection m_length_direction : 2; - Array() : BasicType(), m_length_direction(GI_DIRECTION_IN) {} - - void set_array_length(int pos, GITypeTag tag, GIDirection direction) { + ExplicitArray(int pos, GIDirection direction) + : m_length_pos(pos), m_length_direction(direction) { g_assert(pos >= 0 && pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); - m_length_pos = pos; - m_length_direction = direction; - m_tag = tag; } }; +struct BasicCArray { + constexpr explicit BasicCArray(GITypeTag element_tag) + : m_element_tag(element_tag) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); + } + + GITypeTag m_element_tag; +}; + +struct ZeroTerminatedArray { + constexpr explicit ZeroTerminatedArray(const GI::TypeInfo) {} + + bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + const char* arg_name, GjsArgumentFlags flags, + JS::HandleValue value) { + return gjs_value_to_basic_array_gi_argument( + cx, value, element_tag, GI_ARRAY_TYPE_C, arg, arg_name, + GJS_ARGUMENT_ARGUMENT, flags); + } + + bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + JS::MutableHandleValue value) { + return gjs_array_from_basic_zero_terminated_array( + cx, value, element_tag, gjs_arg_get(arg)); + } + + void release_container(GIArgument* arg) { + g_clear_pointer(&gjs_arg_member(arg), g_free); + } + + void release_contents(GIArgument* arg) { + char** array = gjs_arg_get(arg); + for (size_t ix = 0; array[ix]; ix++) + g_free(array[ix]); + } + + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct GArrayContainer { + constexpr explicit GArrayContainer(const GI::TypeInfo) {} + + bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + const char* arg_name, GjsArgumentFlags flags, + JS::HandleValue value) { + return gjs_value_to_basic_array_gi_argument( + cx, value, element_tag, GI_ARRAY_TYPE_ARRAY, arg, arg_name, + GJS_ARGUMENT_ARGUMENT, flags); + } + + bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + JS::MutableHandleValue value_out) { + return gjs_value_from_basic_garray_gi_argument(cx, value_out, + element_tag, arg); + } + + void release_container(GIArgument* arg) { + g_clear_pointer(&gjs_arg_member(arg), g_array_unref); + } + + void release_contents(GIArgument* arg) { + GArray* array = gjs_arg_get(arg); + for (size_t ix = 0; ix < array->len; ix++) + g_free(g_array_index(array, char*, ix)); + } + + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct GPtrArrayContainer { + constexpr explicit GPtrArrayContainer(const GI::TypeInfo) {} + + bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + const char* arg_name, GjsArgumentFlags flags, + JS::HandleValue value) { + return gjs_value_to_basic_array_gi_argument( + cx, value, element_tag, GI_ARRAY_TYPE_PTR_ARRAY, arg, arg_name, + GJS_ARGUMENT_ARGUMENT, flags); + } + + bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + JS::MutableHandleValue value_out) { + return gjs_value_from_basic_gptrarray_gi_argument(cx, value_out, + element_tag, arg); + } + + void release_container(GIArgument* arg) { + g_clear_pointer(&gjs_arg_member(arg), g_ptr_array_unref); + } + + void release_contents(GIArgument* arg) { + GPtrArray* array = gjs_arg_get(arg); + g_ptr_array_foreach( + array, [](void* ptr, void*) { g_free(ptr); }, nullptr); + } + + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct FixedSizeArray { + explicit FixedSizeArray(const GI::TypeInfo type_info) { + size_t fixed_size = type_info.array_fixed_size().value(); + g_assert( + fixed_size <= UINT32_MAX && + "4294967295 fixed array elements ought to be enough for anybody"); + m_fixed_size = fixed_size; + } + + uint32_t m_fixed_size = -1; + + bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + const char* arg_name, GjsArgumentFlags flags, + JS::HandleValue value) { + return gjs_value_to_basic_array_gi_argument( + cx, value, element_tag, GI_ARRAY_TYPE_C, arg, arg_name, + GJS_ARGUMENT_ARGUMENT, flags); + } + + bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + JS::MutableHandleValue value) { + return gjs_value_from_basic_fixed_size_array_gi_argument( + cx, value, element_tag, m_fixed_size, arg); + } + + void release_container(GIArgument* arg) { + g_clear_pointer(&gjs_arg_member(arg), g_free); + } + + void release_contents(GIArgument* arg) { + char** array = gjs_arg_get(arg); + for (size_t ix = 0; ix < m_fixed_size; ix++) + g_free(array[ix]); + } + + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct GListContainer { + explicit GListContainer(const GI::TypeInfo type_info) + : m_double_link(type_info.tag() == GI_TYPE_TAG_GLIST) {} + bool m_double_link : 1; + + bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + const char* arg_name, GjsArgumentFlags, JS::HandleValue value) { + if (m_double_link) { + return gjs_value_to_basic_glist_gi_argument( + cx, value, element_tag, arg, arg_name, GJS_ARGUMENT_ARGUMENT); + } + return gjs_value_to_basic_gslist_gi_argument( + cx, value, element_tag, arg, arg_name, GJS_ARGUMENT_ARGUMENT); + } + + bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, + JS::MutableHandleValue value) { + if (m_double_link) + return gjs_array_from_basic_glist_gi_argument(cx, value, + element_tag, arg); + return gjs_array_from_basic_gslist_gi_argument(cx, value, element_tag, + arg); + } + + void release_container(GIArgument* arg) { + if (m_double_link) + g_clear_pointer(&gjs_arg_member(arg), g_list_free); + else + g_clear_pointer(&gjs_arg_member(arg), g_slist_free); + } + + void release_contents(GIArgument* arg) { + GFunc free_gfunc = [](void* data, void*) { g_free(data); }; + + if (m_double_link) { + GList* list = gjs_arg_get(arg); + g_list_foreach(list, free_gfunc, nullptr); + } else { + GSList* list = gjs_arg_get(arg); + g_slist_foreach(list, free_gfunc, nullptr); + } + } + + Maybe return_tag() const { + return Some(ReturnTag{container_tag()}); + } + constexpr GITypeTag container_tag() const { + return m_double_link ? GI_TYPE_TAG_GLIST : GI_TYPE_TAG_GSLIST; + } +}; + +struct GHashContainer { + explicit GHashContainer(const GI::TypeInfo type_info) + : m_value_tag(type_info.value_type().tag()) {} + constexpr GITypeTag value_tag() const { return m_value_tag; } + + // Key type is managed by the basic container + GITypeTag m_value_tag; +}; + +template struct HasIntrospectionInfo { - constexpr explicit HasIntrospectionInfo(GIBaseInfo* info, - const GjsAutoTakeOwnership& add_ref) - : m_info(info, add_ref) {} - constexpr explicit HasIntrospectionInfo(GIBaseInfo* info) : m_info(info) {} + constexpr explicit HasIntrospectionInfo(const GI::UnownedInfo& info) + : m_info(info) {} - GjsAutoBaseInfo m_info; + GI::OwnedInfo m_info; }; // boxed / union / GObject @@ -237,68 +441,74 @@ struct GTypedType { }; struct RegisteredType : GTypedType { - RegisteredType(GType gtype, GIInfoType info_type) - : GTypedType(gtype), m_info_type(info_type) {} - explicit RegisteredType(GIRegisteredTypeInfo* info) - : GTypedType(g_registered_type_info_get_g_type(info)), - m_info_type(g_base_info_get_type(info)) { + RegisteredType(GType gtype, bool is_enum_or_flags) + : GTypedType(gtype), m_is_enum_or_flags(is_enum_or_flags) {} + explicit RegisteredType(const GI::RegisteredTypeInfo info) + : GTypedType(info.gtype()), + m_is_enum_or_flags(info.is_enum_or_flags()) { g_assert(m_gtype != G_TYPE_NONE && "Use RegisteredInterface for this type"); } - GIInfoType m_info_type : 5; -}; + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_INTERFACE, m_is_enum_or_flags, true}); + } -struct RegisteredInterface : HasIntrospectionInfo, GTypedType { - explicit RegisteredInterface(GIRegisteredTypeInfo* info) - : HasIntrospectionInfo(info, GjsAutoTakeOwnership{}), - GTypedType(g_registered_type_info_get_g_type(m_info)) {} + bool m_is_enum_or_flags : 1; }; -struct Callback : Nullable, HasIntrospectionInfo { - explicit Callback(GICallbackInfo* info) - : HasIntrospectionInfo(info, GjsAutoTakeOwnership{}), - m_scope(GI_SCOPE_TYPE_INVALID) {} +template +struct RegisteredInterface : HasIntrospectionInfo, GTypedType { + explicit RegisteredInterface(const GI::UnownedInfo info) + : HasIntrospectionInfo(info), GTypedType(info.gtype()) {} - inline void set_callback_destroy_pos(int pos) { - g_assert(pos <= Argument::MAX_ARGS && + Maybe return_tag() const { + return Some(ReturnTag{GI_TYPE_TAG_INTERFACE, + HasIntrospectionInfo::m_info.type(), true}); + } +}; + +struct Callback : Nullable, HasIntrospectionInfo { + explicit Callback(const GI::CallbackInfo info, Maybe closure_pos, + Maybe destroy_pos, GIScopeType scope) + : HasIntrospectionInfo(info), + m_closure_pos(closure_pos.valueOr(Argument::ABSENT)), + m_destroy_pos(destroy_pos.valueOr(Argument::ABSENT)), + m_scope(scope) { + g_assert(destroy_pos.valueOr(0) <= Argument::MAX_ARGS && + "No more than 253 arguments allowed"); + g_assert(closure_pos.valueOr(0) <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); - m_destroy_pos = pos < 0 ? Argument::ABSENT : pos; } [[nodiscard]] constexpr bool has_callback_destroy() { return m_destroy_pos != Argument::ABSENT; } - inline void set_callback_closure_pos(int pos) { - g_assert(pos <= Argument::MAX_ARGS && - "No more than 253 arguments allowed"); - m_closure_pos = pos < 0 ? Argument::ABSENT : pos; - } - [[nodiscard]] constexpr bool has_callback_closure() { return m_closure_pos != Argument::ABSENT; } - uint8_t m_closure_pos = Argument::ABSENT; - uint8_t m_destroy_pos = Argument::ABSENT; + uint8_t m_closure_pos; + uint8_t m_destroy_pos; GIScopeType m_scope : 3; }; struct Enum { - explicit Enum(GIEnumInfo*); + explicit Enum(const GI::EnumInfo); bool m_unsigned : 1; uint32_t m_min = 0; uint32_t m_max = 0; }; struct Flags { - explicit Flags(GIEnumInfo*); + explicit Flags(const GI::FlagsInfo); unsigned m_mask = 0; }; struct CallerAllocates { - size_t m_allocates_size = 0; + explicit CallerAllocates(size_t size) : m_allocates_size(size) {} + size_t m_allocates_size; }; // Gjs::Arguments: @@ -331,20 +541,26 @@ struct SkipAll : Argument { constexpr bool skip() { return true; } }; -struct Generic : SkipAll, Transferable, HasTypeInfo {}; +struct Fallback : Transferable, HasTypeInfo { + using HasTypeInfo::HasTypeInfo; +}; + +struct FallbackIn : SkipAll, Fallback, Nullable { + using Fallback::Fallback; -struct GenericIn : Generic { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; - bool out(JSContext* cx, GjsFunctionCallState*, GIArgument*, - JS::MutableHandleValue) override { - return invalid(cx, G_STRFUNC); - } bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; + + GjsArgumentFlags flags() const override { + return Argument::flags() | Nullable::flags(); + } }; -struct GenericInOut : GenericIn, Positioned { +struct FallbackInOut : SkipAll, Positioned, Fallback { + using Fallback::Fallback; + bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, @@ -353,58 +569,62 @@ struct GenericInOut : GenericIn, Positioned { GIArgument*) override; }; -struct GenericOut : GenericInOut { +struct FallbackOut : FallbackInOut { + using FallbackInOut::FallbackInOut; + bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; - - GITypeTag return_tag() const override { - return g_type_info_get_tag(&const_cast(this)->m_type_info); - } - const GITypeInfo* return_type() const override { return &m_type_info; } }; -struct GenericReturn : ReturnValue { +struct FallbackReturn : FallbackOut { + using FallbackOut::FallbackOut; // No in! bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } + + Maybe return_tag() const override { + return HasTypeInfo::return_tag(); + } }; -template +template struct NumericOut : SkipAll, Positioned { - static_assert(std::is_arithmetic_v, "Not arithmetic type"); + static_assert(std::is_arithmetic_v>, "Not arithmetic type"); bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { - return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), - value); + return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), + value); } }; -using BooleanOut = NumericOut; +using BooleanOut = NumericOut; -template +template struct NumericReturn : SkipAll { - static_assert(std::is_arithmetic_v, "Not arithmetic type"); + static_assert(std::is_arithmetic_v>, "Not arithmetic type"); bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { - return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), - value); + return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), + value); + } + Maybe return_tag() const override { + return Some(ReturnTag{MarshallingInfo::gi_tag}); } - GITypeTag return_tag() const override { return TAG; } }; -using BooleanReturn = NumericReturn; +using BooleanReturn = NumericReturn; struct SimpleOut : SkipAll, Positioned { bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, @@ -413,13 +633,462 @@ struct SimpleOut : SkipAll, Positioned { }; }; -struct ExplicitArray : GenericOut, Array, Nullable { +struct BasicTypeReturn : SkipAll, BasicType { + using BasicType::BasicType; + + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, + JS::HandleValue) override { + return invalid(cx, G_STRFUNC); + } + bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::MutableHandleValue value) override { + return gjs_value_from_basic_gi_argument(cx, value, m_tag, arg); + } + bool release(JSContext*, GjsFunctionCallState*, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + gjs_gi_argument_release_basic(GI_TRANSFER_NOTHING, m_tag, + Argument::flags(), out_arg); + return true; + } + + Maybe return_tag() const override { + return Some(ReturnTag{m_tag}); + } +}; + +struct BasicTypeOut : BasicTypeReturn, Positioned { + using BasicTypeReturn::BasicTypeReturn; + + bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) override { + return set_out_parameter(state, arg); + } +}; + +struct BasicTypeInOut : BasicTypeOut { + using BasicTypeOut::BasicTypeOut; + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + if (!gjs_value_to_basic_gi_argument(cx, value, m_tag, arg, arg_name(), + GJS_ARGUMENT_ARGUMENT, flags())) + return false; + + return set_inout_parameter(state, arg); + } + bool release(JSContext*, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg [[maybe_unused]]) override { + GIArgument* original_out_arg = + &(state->inout_original_cvalue(m_arg_pos)); + gjs_gi_argument_release_basic(GI_TRANSFER_NOTHING, m_tag, flags(), + original_out_arg); + return true; + } +}; + +struct BasicTypeTransferableReturn : BasicTypeReturn, Transferable { + using BasicTypeReturn::BasicTypeReturn; + bool release(JSContext*, GjsFunctionCallState*, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + gjs_gi_argument_release_basic(m_transfer, m_tag, Argument::flags(), + out_arg); + return true; + } +}; + +struct BasicTypeTransferableOut : BasicTypeTransferableReturn, Positioned { + using BasicTypeTransferableReturn::BasicTypeTransferableReturn; + + bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) override { + return set_out_parameter(state, arg); + } +}; + +struct BasicTypeTransferableInOut : BasicTypeInOut, Transferable { + using BasicTypeInOut::BasicTypeInOut; + + bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument* out_arg) override { + if (!BasicTypeInOut::release(cx, state, in_arg, out_arg)) + return false; + + if (m_transfer != GI_TRANSFER_NOTHING) + gjs_gi_argument_release_basic(m_transfer, m_tag, flags(), out_arg); + + return true; + } +}; + +struct ErrorIn : SkipAll, Transferable, Nullable { + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::HandleValue value) override { + return gjs_value_to_gerror_gi_argument(cx, value, m_transfer, arg, + m_arg_name, + GJS_ARGUMENT_ARGUMENT, flags()); + } + + GjsArgumentFlags flags() const override { + return Argument::flags() | Nullable::flags(); + } +}; + +// The tag is normally used in containers for the element type, but for explicit +// arrays we use it for the length argument type. Specific implementations can +// override this +struct BasicTypeContainerReturn : BasicTypeTransferableReturn, Nullable { + explicit BasicTypeContainerReturn(GITypeTag element_tag) + : BasicTypeTransferableReturn(element_tag) {} + + explicit BasicTypeContainerReturn(const GI::TypeInfo type_info) + : BasicTypeContainerReturn(type_info.element_type().tag()) {} + + GjsArgumentFlags flags() const override { + return Argument::flags() | Nullable::flags(); + } + Maybe return_tag() const override { + // in Return subclasses, this must be overridden with the container tag + g_return_val_if_reached(Nothing{}); + } + constexpr GITypeTag element_tag() { return m_tag; } +}; + +struct BasicTypeContainerOut : BasicTypeContainerReturn, Positioned { + using BasicTypeContainerReturn::BasicTypeContainerReturn; +}; + +struct BasicTypeContainerIn : BasicTypeContainerReturn { + using BasicTypeContainerReturn::BasicTypeContainerReturn; + + bool out(JSContext*, GjsFunctionCallState*, GIArgument*, + JS::MutableHandleValue) override { + return skip(); + } +}; + +struct BasicTypeContainerInOut : BasicTypeContainerOut { + using BasicTypeContainerOut::BasicTypeContainerOut; +}; + +template +struct BasicTypeContainer : Marshaller, Container { + explicit BasicTypeContainer(const GI::TypeInfo type_info) + : Marshaller(type_info), Container(type_info) {} + + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + if constexpr (std::is_same_v) { + return Container::in(cx, Marshaller::element_tag(), arg, + Marshaller::arg_name(), Marshaller::flags(), + value); + } + if constexpr (std::is_same_v) + return Marshaller::invalid(cx, G_STRFUNC); + if constexpr (std::is_same_v) + return Marshaller::set_out_parameter(state, arg); + if constexpr (std::is_same_v) { + return Container::in(cx, Marshaller::element_tag(), arg, + Marshaller::arg_name(), Marshaller::flags(), + value) && + Marshaller::set_inout_parameter(state, arg); + } + g_return_val_if_reached(false); + } + + bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::MutableHandleValue value) override { + if constexpr (std::is_same_v) + return Marshaller::skip(); + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return Container::out(cx, Marshaller::element_tag(), arg, value); + } + g_return_val_if_reached(false); + } + bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument* out_arg) override { + GITransfer transfer = Marshaller::m_transfer; + GITypeTag element_tag = Marshaller::element_tag(); + + if constexpr (std::is_same_v) { + if (!state->call_completed()) + transfer = GI_TRANSFER_NOTHING; + + if (!gjs_arg_get(in_arg) || + transfer == GI_TRANSFER_EVERYTHING) + return true; + + if (Gjs::basic_type_needs_release(element_tag)) + Container::release_contents(in_arg); + if (transfer != GI_TRANSFER_CONTAINER) + Container::release_container(in_arg); + + return true; + } + if constexpr (std::is_same_v) { + if (!state->call_completed()) + transfer = GI_TRANSFER_NOTHING; + + GIArgument* original_out_arg = + &(state->inout_original_cvalue(Marshaller::m_arg_pos)); + void* original_out_ptr = gjs_arg_get(original_out_arg); + if (original_out_ptr && + original_out_ptr != gjs_arg_get(out_arg) && + transfer != GI_TRANSFER_EVERYTHING) { + if (Gjs::basic_type_needs_release(element_tag)) + Container::release_contents(original_out_arg); + if (transfer != GI_TRANSFER_CONTAINER) + Container::release_container(original_out_arg); + } + } + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + if (!gjs_arg_get(out_arg) || transfer == GI_TRANSFER_NOTHING) + return true; + + if (Gjs::basic_type_needs_release(element_tag) && + transfer != GI_TRANSFER_CONTAINER) + Container::release_contents(out_arg); + Container::release_container(out_arg); + + return true; + } + g_return_val_if_reached(false); + } + + Maybe return_tag() const override { + return Container::return_tag(); + } +}; + +using BasicGListReturn = + BasicTypeContainer; +using BasicGListIn = BasicTypeContainer; +using BasicGListOut = BasicTypeContainer; +using BasicGListInOut = + BasicTypeContainer; + +using BasicCZeroTerminatedArrayReturn = + BasicTypeContainer; +using BasicCZeroTerminatedArrayIn = + BasicTypeContainer; +using BasicCZeroTerminatedArrayOut = + BasicTypeContainer; +using BasicCZeroTerminatedArrayInOut = + BasicTypeContainer; + +using BasicCFixedSizeArrayReturn = + BasicTypeContainer; +using BasicCFixedSizeArrayIn = + BasicTypeContainer; +using BasicCFixedSizeArrayOut = + BasicTypeContainer; +using BasicCFixedSizeArrayInOut = + BasicTypeContainer; + +using BasicGArrayReturn = + BasicTypeContainer; +using BasicGArrayIn = BasicTypeContainer; +using BasicGArrayOut = + BasicTypeContainer; +using BasicGArrayInOut = + BasicTypeContainer; + +using BasicGPtrArrayReturn = + BasicTypeContainer; +using BasicGPtrArrayIn = + BasicTypeContainer; +using BasicGPtrArrayOut = + BasicTypeContainer; +using BasicGPtrArrayInOut = + BasicTypeContainer; + +struct BasicGHashReturn : BasicTypeTransferableReturn, + GHashContainer, + Nullable { + explicit BasicGHashReturn(const GI::TypeInfo type_info) + : BasicTypeTransferableReturn(type_info.key_type().tag()), + GHashContainer(type_info) { + g_assert(GI_TYPE_TAG_IS_BASIC(m_value_tag)); + } + + bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::MutableHandleValue value) override { + return gjs_value_from_basic_ghash(cx, value, m_tag, m_value_tag, + gjs_arg_get(arg)); + } + bool release(JSContext*, GjsFunctionCallState*, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + if (m_transfer == GI_TRANSFER_NOTHING) + return true; + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Releasing GIArgument ghash out param or return value"); + + gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, + out_arg); + return true; + } + GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } + Maybe return_tag() const override { + return Some(ReturnTag{GI_TYPE_TAG_GHASH}); + } }; -struct ExplicitArrayIn : ExplicitArray { +struct BasicGHashIn : BasicGHashReturn { + using BasicGHashReturn::BasicGHashReturn; + + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::HandleValue value) override { + return gjs_value_to_basic_ghash_gi_argument( + cx, value, m_tag, m_value_tag, m_transfer, arg, m_arg_name, + GJS_ARGUMENT_ARGUMENT, flags()); + } + bool out(JSContext*, GjsFunctionCallState*, GIArgument*, + JS::MutableHandleValue) override { + return skip(); + } + bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument* out_arg [[maybe_unused]]) override { + // GI_TRANSFER_EVERYTHING: we don't own the argument anymore. + // GI_TRANSFER_CONTAINER: See FIXME in gjs_array_to_g_list(); currently + // an error and we won't get here. + if (!state->call_completed() || m_transfer != GI_TRANSFER_NOTHING) + return true; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument ghash in param"); + + gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, + in_arg); + return true; + } +}; + +struct BasicGHashOut : BasicGHashReturn, Positioned { + using BasicGHashReturn::BasicGHashReturn; + + bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) override { + return set_out_parameter(state, arg); + } +}; + +struct BasicGHashInOut : BasicGHashOut { + using BasicGHashOut::BasicGHashOut; + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + if (!gjs_value_to_basic_ghash_gi_argument( + cx, value, m_tag, m_value_tag, m_transfer, arg, m_arg_name, + GJS_ARGUMENT_ARGUMENT, flags())) + return false; + + return set_inout_parameter(state, arg); + } + bool release(JSContext*, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + GIArgument* original_out_arg = + &(state->inout_original_cvalue(m_arg_pos)); + gjs_gi_argument_release_basic_ghash(GI_TRANSFER_NOTHING, m_tag, + m_value_tag, original_out_arg); + if (m_transfer != GI_TRANSFER_NOTHING) + gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, + out_arg); + return true; + } +}; + +struct ByteArrayReturn : SkipAll, Transferable { + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, + JS::HandleValue) override { + return invalid(cx, G_STRFUNC); + } + bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::MutableHandleValue value) override { + return gjs_value_from_byte_array_gi_argument(cx, value, arg); + } + bool release(JSContext*, GjsFunctionCallState*, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + if (m_transfer != GI_TRANSFER_NOTHING) + gjs_gi_argument_release_byte_array(out_arg); + return true; + } + + Maybe return_tag() const override { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct ByteArrayIn : SkipAll, Transferable { + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::HandleValue value) override { + return gjs_value_to_byte_array_gi_argument(cx, value, arg, arg_name(), + flags()); + } + bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, + GIArgument* out_arg [[maybe_unused]]) override { + if (!state->call_completed() || m_transfer != GI_TRANSFER_NOTHING) + return true; + + gjs_gi_argument_release_byte_array(in_arg); + return true; + } +}; + +struct ByteArrayOut : ByteArrayReturn, Positioned { + using ByteArrayReturn::ByteArrayReturn; + + bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) override { + return set_out_parameter(state, arg); + } +}; + +struct ByteArrayInOut : ByteArrayOut { + using ByteArrayOut::ByteArrayOut; + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + return gjs_value_to_byte_array_gi_argument(cx, value, arg, m_arg_name, + flags()) && + set_inout_parameter(state, arg); + } + bool release(JSContext*, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + GIArgument* original_out_arg = + &(state->inout_original_cvalue(m_arg_pos)); + if (m_transfer != GI_TRANSFER_EVERYTHING) + gjs_gi_argument_release_byte_array(original_out_arg); + if (m_transfer != GI_TRANSFER_NOTHING) + gjs_gi_argument_release_byte_array(out_arg); + return true; + } +}; + +struct ExplicitArrayBase : BasicTypeContainerReturn, ExplicitArray { + ExplicitArrayBase(unsigned length_pos, GITypeTag length_tag, + GIDirection length_direction) + : BasicTypeContainerReturn(length_tag), + ExplicitArray(length_pos, length_direction) {} +}; + +struct CArrayIn : ExplicitArrayBase, HasTypeInfo { + CArrayIn(const GI::TypeInfo type_info, unsigned length_pos, + GITypeTag length_tag, GIDirection length_direction) + : ExplicitArrayBase(length_pos, length_tag, length_direction), + HasTypeInfo(type_info) {} + bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, @@ -430,7 +1099,13 @@ struct ExplicitArrayIn : ExplicitArray { GIArgument*) override; }; -struct ExplicitArrayInOut : ExplicitArrayIn { +// Positioned must come before HasTypeInfo for struct packing reasons, otherwise +// this could inherit from CArrayIn +struct CArrayInOut : ExplicitArrayBase, Positioned, HasTypeInfo { + CArrayInOut(const GI::TypeInfo type_info, unsigned length_pos, + GITypeTag length_tag, GIDirection length_direction) + : ExplicitArrayBase(length_pos, length_tag, length_direction), + HasTypeInfo(type_info) {} bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, @@ -439,18 +1114,11 @@ struct ExplicitArrayInOut : ExplicitArrayIn { GIArgument*) override; }; -struct ExplicitArrayOut : ExplicitArrayInOut { - bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, - JS::HandleValue) override { - return invalid(cx, G_STRFUNC); - } - bool release(JSContext*, GjsFunctionCallState*, GIArgument*, - GIArgument*) override; -}; +struct CArrayOut : CArrayInOut { + using CArrayInOut::CArrayInOut; -struct ReturnArray : ExplicitArrayOut { bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, - JS::HandleValue value) override { + JS::HandleValue) override { if (m_length_direction != GI_DIRECTION_OUT) { gjs_throw(cx, "Using different length argument direction for array %s" @@ -458,27 +1126,19 @@ struct ReturnArray : ExplicitArrayOut { m_arg_name); return false; } - return GenericOut::in(cx, state, arg, value); + return set_out_parameter(state, arg); }; -}; - -using ArrayLengthOut = SimpleOut; - -struct FallbackIn : GenericIn, Nullable { - bool out(JSContext*, GjsFunctionCallState*, GIArgument*, - JS::MutableHandleValue) override { - return skip(); - } + bool release(JSContext*, GjsFunctionCallState*, GIArgument*, + GIArgument*) override; - GjsArgumentFlags flags() const override { - return Argument::flags() | Nullable::flags(); + Maybe return_tag() const override { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; -using FallbackInOut = GenericInOut; -using FallbackOut = GenericOut; +using ArrayLengthOut = SimpleOut; -struct NotIntrospectable : GenericIn { +struct NotIntrospectable : SkipAll { explicit NotIntrospectable(NotIntrospectableReason reason) : m_reason(reason) {} NotIntrospectableReason m_reason; @@ -508,16 +1168,8 @@ struct Instance : NullableIn { JS::HandleValue) override { return skip(); } - bool out(JSContext*, GjsFunctionCallState*, GIArgument*, - JS::MutableHandleValue) override { - return skip(); - } - bool release(JSContext*, GjsFunctionCallState*, GIArgument*, - GIArgument*) override { - return skip(); - } - const Instance* as_instance() const override { return this; } + Maybe as_instance() const override { return Some(this); } // The instance GType can be useful only in few cases such as GObjects and // GInterfaces, so we don't store it by default, unless needed. @@ -543,14 +1195,17 @@ struct RegisteredIn : Instance, RegisteredType, Transferable { GType gtype() const override { return RegisteredType::gtype(); } }; -struct RegisteredInterfaceIn : Instance, RegisteredInterface, Transferable { - using RegisteredInterface::RegisteredInterface; +template +struct RegisteredInterfaceIn : Instance, + RegisteredInterface, + Transferable { + using RegisteredInterface::RegisteredInterface; - GType gtype() const override { return RegisteredInterface::gtype(); } + GType gtype() const override { return RegisteredInterface::gtype(); } }; -struct ForeignStructInstanceIn : RegisteredInterfaceIn { - using RegisteredInterfaceIn::RegisteredInterfaceIn; +struct ForeignStructInstanceIn : RegisteredInterfaceIn { + using RegisteredInterfaceIn::RegisteredInterfaceIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; @@ -561,14 +1216,23 @@ struct ForeignStructIn : ForeignStructInstanceIn { GIArgument*) override; }; -struct FallbackInterfaceIn : RegisteredInterfaceIn, HasTypeInfo { +struct FallbackInterfaceIn + : RegisteredInterfaceIn { using RegisteredInterfaceIn::RegisteredInterfaceIn; bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { - return gjs_value_to_gi_argument(cx, value, &m_type_info, m_arg_name, - GJS_ARGUMENT_ARGUMENT, m_transfer, - flags(), arg); + return gjs_value_to_interface_gi_argument( + cx, value, m_info, m_transfer, arg, m_arg_name, + GJS_ARGUMENT_ARGUMENT, flags()); + } +}; + +struct GdkAtomIn : NullableIn { + bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::HandleValue value) override { + return gjs_value_to_gdk_atom_gi_argument(cx, value, arg, m_arg_name, + GJS_ARGUMENT_ARGUMENT); } }; @@ -578,8 +1242,7 @@ struct BoxedInTransferNone : RegisteredIn { JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; - - virtual GIBaseInfo* info() const { return nullptr; } + virtual Maybe info() const { return {}; } }; struct BoxedIn : BoxedInTransferNone { @@ -591,13 +1254,12 @@ struct BoxedIn : BoxedInTransferNone { } }; -struct UnregisteredBoxedIn : BoxedIn, HasIntrospectionInfo { - explicit UnregisteredBoxedIn(GIStructInfo* info) - : BoxedIn(g_registered_type_info_get_g_type(info), - g_base_info_get_type(info)), - HasIntrospectionInfo(info, GjsAutoTakeOwnership{}) {} +struct UnregisteredBoxedIn : BoxedIn, + HasIntrospectionInfo { + explicit UnregisteredBoxedIn(const GI::StructInfo info) + : BoxedIn(info.gtype(), /* is_enum_or_flags = */ false), + HasIntrospectionInfo(info) {} // This is a smart argument, no release needed - GIBaseInfo* info() const override { return m_info; } }; struct GValueIn : BoxedIn { @@ -680,31 +1342,31 @@ struct BooleanIn : SkipAll { JS::HandleValue) override; }; -template +template struct NumericIn : SkipAll { - static_assert(std::is_arithmetic_v, "Not arithmetic type"); + static_assert(std::is_arithmetic_v>, "Not arithmetic type"); bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; -template -struct NumericInOut : NumericIn, Positioned { - static_assert(std::is_arithmetic_v, "Not arithmetic type"); +template +struct NumericInOut : NumericIn, Positioned { + static_assert(std::is_arithmetic_v>, "Not arithmetic type"); bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { - if (!NumericIn::in(cx, state, arg, value)) + if (!NumericIn::in(cx, state, arg, value)) return false; return set_inout_parameter(state, arg); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { - return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), - value); + return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), + value); } }; -using BooleanInOut = NumericInOut; +using BooleanInOut = NumericInOut; struct UnicharIn : SkipAll { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, @@ -735,7 +1397,15 @@ template struct StringOutBase : SkipAll { bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { - return Gjs::c_value_to_js(cx, gjs_arg_get(arg), value); + bool success = + Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), value); + // do not leak since release is only for in args + if constexpr (TRANSFER == GI_TRANSFER_EVERYTHING) { + if (!success) { + g_clear_pointer(&gjs_arg_member(arg), g_free); + } + } + return success; } bool release(JSContext* cx, GjsFunctionCallState*, GIArgument*, GIArgument* out_arg [[maybe_unused]]) override { @@ -757,7 +1427,9 @@ struct StringReturn : StringOutBase { return Argument::invalid(cx, G_STRFUNC); } - GITypeTag return_tag() const override { return GI_TYPE_TAG_UTF8; } + Maybe return_tag() const override { + return Some(ReturnTag{GI_TYPE_TAG_UTF8}); + } }; template @@ -809,47 +1481,175 @@ struct CallbackIn : SkipAll, Callback { ffi_closure *m_ffi_closure; }; -using CArrayIn = ExplicitArrayIn; +struct BasicExplicitCArrayOut : ExplicitArrayBase, BasicCArray, Positioned { + explicit BasicExplicitCArrayOut(GITypeTag element_tag, unsigned length_pos, + GITypeTag length_tag, + GIDirection length_direction) + : ExplicitArrayBase(length_pos, length_tag, length_direction), + BasicCArray(element_tag) {} + + bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) override { + return set_out_parameter(state, arg); + }; + bool out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::MutableHandleValue value) override { + GIArgument* length_arg = &(state->out_cvalue(m_length_pos)); + size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); + + return gjs_value_from_basic_explicit_array(cx, value, m_element_tag, + arg, length); + } + bool release(JSContext*, GjsFunctionCallState* state, + [[maybe_unused]] GIArgument* in_arg, + GIArgument* out_arg) override { + GIArgument* length_arg = &state->out_cvalue(m_length_pos); + size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); + + gjs_gi_argument_release_basic_out_array(m_transfer, m_element_tag, + length, out_arg); + return true; + } + + Maybe return_tag() const override { + return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); + } +}; + +struct BasicExplicitCArrayIn : BasicExplicitCArrayOut { + using BasicExplicitCArrayOut::BasicExplicitCArrayOut; + + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + void* data; + size_t length; + + if (!gjs_array_to_basic_explicit_array( + cx, value, m_element_tag, m_arg_name, GJS_ARGUMENT_ARGUMENT, + flags(), &data, &length)) + return false; + + gjs_gi_argument_set_array_length(m_tag, &state->in_cvalue(m_length_pos), + length); + gjs_arg_set(arg, data); + return true; + } + bool out(JSContext*, GjsFunctionCallState*, GIArgument*, + JS::MutableHandleValue) override { + return skip(); + } + bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, + [[maybe_unused]] GIArgument* out_arg) override { + GIArgument* length_arg = &state->in_cvalue(m_length_pos); + size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); + + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + + gjs_gi_argument_release_basic_in_array(transfer, m_element_tag, length, + in_arg); + return true; + } +}; + +struct BasicExplicitCArrayInOut : BasicExplicitCArrayIn { + using BasicExplicitCArrayIn::BasicExplicitCArrayIn; + + bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) override { + if (!BasicExplicitCArrayIn::in(cx, state, arg, value)) + return false; -using CArrayInOut = ExplicitArrayInOut; + if (!gjs_arg_get(arg)) { + // Special case where we were given JS null to also pass null for + // length, and not a pointer to an integer that derefs to 0. + gjs_arg_unset(&state->in_cvalue(m_length_pos)); + gjs_arg_unset(&state->out_cvalue(m_length_pos)); + gjs_arg_unset(&state->inout_original_cvalue(m_length_pos)); -using CArrayOut = ReturnArray; + gjs_arg_unset(&state->out_cvalue(m_arg_pos)); + gjs_arg_unset(&state->inout_original_cvalue(m_arg_pos)); -struct CallerAllocatesOut : GenericOut, CallerAllocates { + return true; + } + + state->out_cvalue(m_length_pos) = + state->inout_original_cvalue(m_length_pos) = + state->in_cvalue(m_length_pos); + gjs_arg_set(&state->in_cvalue(m_length_pos), + &state->out_cvalue(m_length_pos)); + return set_inout_parameter(state, arg); + } + bool out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::MutableHandleValue value) override { + GIArgument* length_arg = &(state->out_cvalue(m_length_pos)); + size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); + + return gjs_value_from_basic_explicit_array(cx, value, m_element_tag, + arg, length); + } + bool release(JSContext*, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) override { + GIArgument* length_arg = &state->out_cvalue(m_length_pos); + size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); + + GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); + if (gjs_arg_get(original_out_arg) != + gjs_arg_get(out_arg)) { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; + gjs_gi_argument_release_basic_in_array(transfer, m_element_tag, + length, original_out_arg); + } + + gjs_gi_argument_release_basic_out_array(m_transfer, m_element_tag, + length, out_arg); + return true; + } +}; + +struct CallerAllocatesOut : FallbackOut, CallerAllocates { + CallerAllocatesOut(const GI::TypeInfo type_info, size_t size) + : FallbackOut(type_info), CallerAllocates(size) {} bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; GjsArgumentFlags flags() const override { - return GenericOut::flags() | GjsArgumentFlags::CALLER_ALLOCATES; + return FallbackOut::flags() | GjsArgumentFlags::CALLER_ALLOCATES; } }; struct BoxedCallerAllocatesOut : CallerAllocatesOut, GTypedType { - using GTypedType::GTypedType; + BoxedCallerAllocatesOut(const GI::TypeInfo type_info, size_t size, + GType gtype) + : CallerAllocatesOut(type_info, size), GTypedType(gtype) {} bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; -struct ZeroTerminatedArrayInOut : GenericInOut { +struct ZeroTerminatedArrayInOut : FallbackInOut { + using FallbackInOut::FallbackInOut; bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); - if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, + if (!gjs_gi_argument_release_in_array(cx, transfer, m_type_info, original_out_arg)) return false; transfer = state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; - return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, + return gjs_gi_argument_release_out_array(cx, transfer, m_type_info, out_arg); } }; -struct ZeroTerminatedArrayIn : GenericIn, Nullable { +struct ZeroTerminatedArrayIn : FallbackIn { + using FallbackIn::FallbackIn; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); @@ -860,7 +1660,7 @@ struct ZeroTerminatedArrayIn : GenericIn, Nullable { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, + return gjs_gi_argument_release_in_array(cx, transfer, m_type_info, in_arg); } @@ -869,7 +1669,8 @@ struct ZeroTerminatedArrayIn : GenericIn, Nullable { } }; -struct FixedSizeArrayIn : GenericIn { +struct FixedSizeArrayIn : FallbackIn { + using FallbackIn::FallbackIn; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); @@ -880,26 +1681,27 @@ struct FixedSizeArrayIn : GenericIn { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - int size = g_type_info_get_array_fixed_size(&m_type_info); - return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, - size, in_arg); + size_t size = m_type_info.array_fixed_size().value(); + return gjs_gi_argument_release_in_array(cx, transfer, m_type_info, size, + in_arg); } }; -struct FixedSizeArrayInOut : GenericInOut { +struct FixedSizeArrayInOut : FallbackInOut { + using FallbackInOut::FallbackInOut; bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); - int size = g_type_info_get_array_fixed_size(&m_type_info); - if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, size, + size_t size = m_type_info.array_fixed_size().value(); + if (!gjs_gi_argument_release_in_array(cx, transfer, m_type_info, size, original_out_arg)) return false; transfer = state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; - return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, + return gjs_gi_argument_release_out_array(cx, transfer, m_type_info, size, out_arg); } }; @@ -940,45 +1742,44 @@ bool NotIntrospectable::in(JSContext* cx, GjsFunctionCallState* state, } gjs_throw(cx, - "Function %s() cannot be called: argument '%s' with type %s is " - "not introspectable because it has a %s", - state->display_name().get(), m_arg_name, - g_type_tag_to_string(g_type_info_get_tag(&m_type_info)), - reason_string); + "Function %s() cannot be called: argument '%s' is not " + "introspectable because it has a %s", + state->display_name().get(), m_arg_name, reason_string); return false; } GJS_JSAPI_RETURN_CONVENTION -bool GenericIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, - JS::HandleValue value) { - return gjs_value_to_gi_argument(cx, value, &m_type_info, m_arg_name, +bool FallbackIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::HandleValue value) { + return gjs_value_to_gi_argument(cx, value, m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), arg); } GJS_JSAPI_RETURN_CONVENTION -bool GenericInOut::in(JSContext* cx, GjsFunctionCallState* state, - GIArgument* arg, JS::HandleValue value) { - if (!GenericIn::in(cx, state, arg, value)) - return false; - - return set_inout_parameter(state, arg); +bool FallbackInOut::in(JSContext* cx, GjsFunctionCallState* state, + GIArgument* arg, JS::HandleValue value) { + return gjs_value_to_gi_argument(cx, value, m_type_info, m_arg_name, + GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), + arg) && + set_inout_parameter(state, arg); } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayIn::in(JSContext* cx, GjsFunctionCallState* state, - GIArgument* arg, JS::HandleValue value) { +bool CArrayIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue value) { void* data; size_t length; - if (m_length_direction != GI_DIRECTION_INOUT && - m_length_direction != GI_DIRECTION_IN) { - gjs_throw(cx, "Using different length argument direction for array %s" - "is not supported for in arrays", m_arg_name); + if (m_length_direction != GI_DIRECTION_IN) { + gjs_throw(cx, + "Using different length argument direction for array %s is " + "not supported for in arrays", + m_arg_name); return false; } - if (!gjs_array_to_explicit_array(cx, value, &m_type_info, m_arg_name, + if (!gjs_array_to_explicit_array(cx, value, m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), &data, &length)) return false; @@ -990,10 +1791,26 @@ bool ExplicitArrayIn::in(JSContext* cx, GjsFunctionCallState* state, } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayInOut::in(JSContext* cx, GjsFunctionCallState* state, - GIArgument* arg, JS::HandleValue value) { - if (!ExplicitArrayIn::in(cx, state, arg, value)) +bool CArrayInOut::in(JSContext* cx, GjsFunctionCallState* state, + GIArgument* arg, JS::HandleValue value) { + if (m_length_direction != GI_DIRECTION_INOUT) { + gjs_throw(cx, + "Using different length argument direction for array %s is " + "not supported for inout arrays", + m_arg_name); return false; + } + + void* data; + size_t length; + if (!gjs_array_to_explicit_array(cx, value, m_type_info, m_arg_name, + GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), + &data, &length)) + return false; + + gjs_gi_argument_set_array_length(m_tag, &state->in_cvalue(m_length_pos), + length); + gjs_arg_set(arg, data); uint8_t length_pos = m_length_pos; uint8_t ix = m_arg_pos; @@ -1001,20 +1818,18 @@ bool ExplicitArrayInOut::in(JSContext* cx, GjsFunctionCallState* state, if (!gjs_arg_get(arg)) { // Special case where we were given JS null to also pass null for // length, and not a pointer to an integer that derefs to 0. - gjs_arg_unset(&state->in_cvalue(length_pos)); - gjs_arg_unset(&state->out_cvalue(length_pos)); - gjs_arg_unset(&state->inout_original_cvalue(length_pos)); + gjs_arg_unset(&state->in_cvalue(length_pos)); + gjs_arg_unset(&state->out_cvalue(length_pos)); + gjs_arg_unset(&state->inout_original_cvalue(length_pos)); - gjs_arg_unset(&state->out_cvalue(ix)); - gjs_arg_unset(&state->inout_original_cvalue(ix)); + gjs_arg_unset(&state->out_cvalue(ix)); + gjs_arg_unset(&state->inout_original_cvalue(ix)); } else { - if G_LIKELY (m_length_direction == GI_DIRECTION_INOUT) { - state->out_cvalue(length_pos) = - state->inout_original_cvalue(length_pos) = - state->in_cvalue(length_pos); - gjs_arg_set(&state->in_cvalue(length_pos), - &state->out_cvalue(length_pos)); - } + state->out_cvalue(length_pos) = + state->inout_original_cvalue(length_pos) = + state->in_cvalue(length_pos); + gjs_arg_set(&state->in_cvalue(length_pos), + &state->out_cvalue(length_pos)); state->out_cvalue(ix) = state->inout_original_cvalue(ix) = *arg; gjs_arg_set(arg, &state->out_cvalue(ix)); @@ -1082,11 +1897,8 @@ bool CallbackIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, } bool keep_forever = - !has_callback_destroy() && ( -#if GI_CHECK_VERSION(1, 72, 0) - m_scope == GI_SCOPE_TYPE_FOREVER || -#endif - m_scope == GI_SCOPE_TYPE_NOTIFIED); + !has_callback_destroy() && + (m_scope == GI_SCOPE_TYPE_FOREVER || m_scope == GI_SCOPE_TYPE_NOTIFIED); if (trampoline && keep_forever) { trampoline->mark_forever(); @@ -1097,8 +1909,8 @@ bool CallbackIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, } GJS_JSAPI_RETURN_CONVENTION -bool GenericOut::in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, - JS::HandleValue) { +bool FallbackOut::in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, + JS::HandleValue) { // Default value in case a broken C function doesn't fill in the pointer return set_out_parameter(state, arg); } @@ -1125,28 +1937,28 @@ bool BooleanIn::in(JSContext*, GjsFunctionCallState*, GIArgument* arg, return true; } -template -GJS_JSAPI_RETURN_CONVENTION bool NumericIn::in(JSContext* cx, - GjsFunctionCallState*, - GIArgument* arg, - JS::HandleValue value) { +template +GJS_JSAPI_RETURN_CONVENTION bool NumericIn::in(JSContext* cx, + GjsFunctionCallState*, + GIArgument* arg, + JS::HandleValue value) { bool out_of_range = false; - if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { + if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { if (out_of_range) { gjs_throw(cx, "Argument %s: value is out of range for %s", - arg_name(), Gjs::static_type_name()); + arg_name(), Gjs::static_type_name()); } return false; } gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "%s set to value %s (type %s)", - GjsAutoChar{gjs_argument_display_name( - arg_name(), GJS_ARGUMENT_ARGUMENT)} + Gjs::AutoChar{gjs_argument_display_name( + arg_name(), GJS_ARGUMENT_ARGUMENT)} .get(), - std::to_string(gjs_arg_get(arg)).c_str(), - Gjs::static_type_name()); + std::to_string(gjs_arg_get(arg)).c_str(), + Gjs::static_type_name()); return true; } @@ -1171,8 +1983,8 @@ bool GTypeIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, ExpectedType::OBJECT); JS::RootedObject gtype_obj(cx, &value.toObject()); - return gjs_gtype_get_actual_gtype( - cx, gtype_obj, &gjs_arg_member(arg)); + return gjs_gtype_get_actual_gtype(cx, gtype_obj, + &gjs_arg_member(arg)); } // Common code for most types that are pointers on the C side @@ -1180,7 +1992,7 @@ bool Nullable::handle_nullable(JSContext* cx, GIArgument* arg, const char* arg_name) { if (!m_nullable) return report_invalid_null(cx, arg_name); - gjs_arg_unset(arg); + gjs_arg_unset(arg); return true; } @@ -1196,7 +2008,7 @@ GJS_JSAPI_RETURN_CONVENTION bool StringInTransferNone::in( ExpectedType::STRING); if constexpr (TAG == GI_TYPE_TAG_FILENAME) { - GjsAutoChar str; + AutoChar str; if (!gjs_string_to_filename(cx, value, &str)) return false; gjs_arg_set(arg, str.release()); @@ -1205,7 +2017,7 @@ GJS_JSAPI_RETURN_CONVENTION bool StringInTransferNone::in( JS::UniqueChars str = gjs_string_to_utf8(cx, value); if (!str) return false; - gjs_arg_set(arg, g_strdup(str.get())); + gjs_arg_set(arg, js_chars_to_glib(std::move(str)).release()); return true; } else { return invalid(cx, G_STRFUNC); @@ -1216,7 +2028,7 @@ GJS_JSAPI_RETURN_CONVENTION bool EnumIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; - if (!Gjs::js_value_to_c(cx, value, &number)) + if (!Gjs::js_value_to_c(cx, value, &number)) return false; // Unpack the values from their uint32_t bitfield. See note in @@ -1237,9 +2049,9 @@ bool EnumIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, } if (m_unsigned) - gjs_arg_set(arg, number); + gjs_arg_set(arg, number); else - gjs_arg_set(arg, number); + gjs_arg_set(arg, number); return true; } @@ -1248,7 +2060,7 @@ GJS_JSAPI_RETURN_CONVENTION bool FlagsIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; - if (!Gjs::js_value_to_c(cx, value, &number)) + if (!Gjs::js_value_to_c(cx, value, &number)) return false; if ((uint64_t(number) & m_mask) != uint64_t(number)) { @@ -1261,7 +2073,7 @@ bool FlagsIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, // We cast to unsigned because that's what makes sense, but then we // put it in the v_int slot because that's what we use to unmarshal // flags types at the moment. - gjs_arg_set(arg, static_cast(number)); + gjs_arg_set(arg, static_cast(number)); return true; } @@ -1317,8 +2129,12 @@ bool BoxedInTransferNone::in(JSContext* cx, GjsFunctionCallState* state, GI_DIRECTION_IN, m_transfer); } + if (info() && !BoxedBase::typecheck(cx, object, info().ref())) { + gjs_arg_unset(arg); + return false; + } return BoxedBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, - m_transfer, gtype, info()); + m_transfer, gtype); } // Unions include ClutterEvent and GdkEvent, which occur fairly often in an @@ -1405,8 +2221,7 @@ bool InterfaceIn::in(JSContext* cx, GjsFunctionCallState* state, // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental - if (ObjectBase::typecheck(cx, object, nullptr, gtype, - GjsTypecheckNoThrow())) { + if (ObjectBase::typecheck(cx, object, gtype, GjsTypecheckNoThrow{})) { return ObjectBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } @@ -1499,14 +2314,14 @@ bool ParamInstanceIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, } GJS_JSAPI_RETURN_CONVENTION -bool GenericInOut::out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, - JS::MutableHandleValue value) { - return gjs_value_from_gi_argument(cx, value, &m_type_info, arg, true); +bool FallbackInOut::out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, + JS::MutableHandleValue value) { + return gjs_value_from_gi_argument(cx, value, m_type_info, arg, true); } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, - GIArgument* arg, JS::MutableHandleValue value) { +bool CArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, + GIArgument* arg, JS::MutableHandleValue value) { GIArgument* length_arg; if (m_length_direction != GI_DIRECTION_IN) @@ -1516,91 +2331,88 @@ bool ExplicitArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); - return gjs_value_from_explicit_array(cx, value, &m_type_info, m_transfer, + return gjs_value_from_explicit_array(cx, value, m_type_info, m_transfer, arg, length); } GJS_JSAPI_RETURN_CONVENTION -bool GenericIn::release(JSContext* cx, GjsFunctionCallState* state, - GIArgument* in_arg, GIArgument*) { +bool FallbackIn::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument* in_arg, GIArgument*) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - return gjs_gi_argument_release_in_arg(cx, transfer, &m_type_info, in_arg); + return gjs_gi_argument_release_in_arg(cx, transfer, m_type_info, in_arg); } GJS_JSAPI_RETURN_CONVENTION -bool GenericOut::release(JSContext* cx, GjsFunctionCallState*, - GIArgument* in_arg [[maybe_unused]], - GIArgument* out_arg) { - return gjs_gi_argument_release(cx, m_transfer, &m_type_info, out_arg); +bool FallbackOut::release(JSContext* cx, GjsFunctionCallState*, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) { + return gjs_gi_argument_release(cx, m_transfer, m_type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION -bool GenericInOut::release(JSContext* cx, GjsFunctionCallState* state, - GIArgument*, GIArgument* out_arg) { - // For inout, transfer refers to what we get back from the function; for - // the temporary C value we allocated, clearly we're responsible for - // freeing it. - +bool FallbackInOut::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument*, GIArgument* out_arg) { + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); - if (!gjs_gi_argument_release_in_arg(cx, GI_TRANSFER_NOTHING, &m_type_info, + + // Assume that inout transfer means that in and out transfer are the same. + // See https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/192 + + if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg) && + !gjs_gi_argument_release_in_arg(cx, transfer, m_type_info, original_out_arg)) return false; - return gjs_gi_argument_release(cx, m_transfer, &m_type_info, out_arg); + return gjs_gi_argument_release(cx, transfer, m_type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayOut::release(JSContext* cx, GjsFunctionCallState* state, - GIArgument* in_arg [[maybe_unused]], - GIArgument* out_arg) { +bool CArrayOut::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); - return gjs_gi_argument_release_out_array(cx, m_transfer, &m_type_info, + return gjs_gi_argument_release_out_array(cx, m_transfer, m_type_info, length, out_arg); } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayIn::release(JSContext* cx, GjsFunctionCallState* state, - GIArgument* in_arg, - GIArgument* out_arg [[maybe_unused]]) { +bool CArrayIn::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument* in_arg, + GIArgument* out_arg [[maybe_unused]]) { GIArgument* length_arg = &state->in_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, length, + return gjs_gi_argument_release_in_array(cx, transfer, m_type_info, length, in_arg); } GJS_JSAPI_RETURN_CONVENTION -bool ExplicitArrayInOut::release(JSContext* cx, GjsFunctionCallState* state, - GIArgument* in_arg [[maybe_unused]], - GIArgument* out_arg) { +bool CArrayInOut::release(JSContext* cx, GjsFunctionCallState* state, + GIArgument* in_arg [[maybe_unused]], + GIArgument* out_arg) { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); - - // For inout, transfer refers to what we get back from the function; for - // the temporary C value we allocated, clearly we're responsible for - // freeing it. + GITransfer transfer = + state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); // Due to https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/192 // Here we've to guess what to do, but in general is "better" to leak than // crash, so let's assume that in/out transfer is matching. if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg)) { - GITransfer transfer = - state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, - length, original_out_arg)) + if (!gjs_gi_argument_release_in_array(cx, transfer, m_type_info, length, + original_out_arg)) return false; } - GITransfer transfer = - state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; - return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, length, + return gjs_gi_argument_release_out_array(cx, transfer, m_type_info, length, out_arg); } @@ -1630,7 +2442,7 @@ bool CallbackIn::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, // CallbackTrampolines are refcounted because for notified/async closures // it is possible to destroy it while in call, and therefore we cannot // check its scope at this point - gjs_arg_unset(in_arg); + gjs_arg_unset(in_arg); return true; } @@ -1709,81 +2521,120 @@ bool Argument::release(JSContext*, GjsFunctionCallState*, GIArgument*, #ifdef GJS_DO_ARGUMENTS_SIZE_CHECK template constexpr size_t argument_maximum_size() { - if constexpr (std::is_same_v>) + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v || + std::is_same_v) return 24; - if constexpr (std::is_same_v || - std::is_same_v) + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) return 40; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) + return 48; + if constexpr (std::is_same_v) + return 56; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) + return 176; // FIXME + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) + return 184; // FIXME + if constexpr (std::is_same_v) + return 192; // FIXME else - return 120; + return 32; } #endif -template -GjsAutoCppPointer Argument::make(uint8_t index, const char* name, - GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args) { +template +void Argument::init_common(const Init& init, T* arg) { #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 = new T(args...); - if constexpr (ArgKind == Arg::Kind::INSTANCE) { - g_assert(index == Argument::ABSENT && + g_assert(init.index == Argument::ABSENT && "index was ignored in INSTANCE parameter"); - g_assert(name == nullptr && "name was ignored in INSTANCE parameter"); + g_assert(init.name == nullptr && + "name was ignored in INSTANCE parameter"); arg->set_instance_parameter(); } else if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) { - g_assert(index == Argument::ABSENT && + g_assert(init.index == Argument::ABSENT && "index was ignored in RETURN_VALUE parameter"); - g_assert(name == nullptr && + g_assert(init.name == nullptr && "name was ignored in RETURN_VALUE parameter"); arg->set_return_value(); } else { if constexpr (std::is_base_of_v) - arg->set_arg_pos(index); - arg->m_arg_name = name; + arg->set_arg_pos(init.index); + arg->m_arg_name = init.name; } - arg->m_skip_in = (flags & GjsArgumentFlags::SKIP_IN); - arg->m_skip_out = (flags & GjsArgumentFlags::SKIP_OUT); + arg->m_skip_in = (init.flags & GjsArgumentFlags::SKIP_IN); + arg->m_skip_out = (init.flags & GjsArgumentFlags::SKIP_OUT); if constexpr (std::is_base_of_v) - arg->m_nullable = (flags & GjsArgumentFlags::MAY_BE_NULL); + arg->m_nullable = (init.flags & GjsArgumentFlags::MAY_BE_NULL); if constexpr (std::is_base_of_v) - arg->m_transfer = transfer; - - if constexpr (std::is_base_of_v && - ArgKind != Arg::Kind::INSTANCE) { - arg->m_type_info = std::move(*type_info); - } - - return arg; + arg->m_transfer = init.transfer; } -bool ArgsCache::initialize(JSContext* cx, GICallableInfo* callable) { - if (!callable) { - gjs_throw(cx, "Invalid callable provided"); - return false; - } - +bool ArgsCache::initialize(JSContext* cx, const GI::CallableInfo callable) { if (m_args) { gjs_throw(cx, "Arguments cache already initialized!"); return false; } - GITypeInfo type_info; - g_callable_info_load_return_type(callable, &type_info); + GI::StackTypeInfo type_info; + callable.load_return_type(&type_info); - m_has_return = g_type_info_get_tag(&type_info) != GI_TYPE_TAG_VOID || - g_type_info_is_pointer(&type_info); - m_is_method = !!g_callable_info_is_method(callable); + m_has_return = + type_info.tag() != GI_TYPE_TAG_VOID || type_info.is_pointer(); + m_is_method = callable.is_method(); - int size = g_callable_info_get_n_args(callable); + int size = callable.n_args(); size += (m_is_method ? 1 : 0); size += (m_has_return ? 1 : 0); @@ -1791,137 +2642,156 @@ bool ArgsCache::initialize(JSContext* cx, GICallableInfo* callable) { gjs_throw(cx, "Too many arguments, only %u are supported, while %d are " "provided!", - Argument::MAX_ARGS, size); - return false; - } - - m_args = new ArgumentPtr[size]{}; - return true; -} - -template -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) = arg.release(); - return static_cast(arg_get(index).get()); -} - -template -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 -constexpr T* ArgsCache::set_argument_auto(Args&&... args) { - return set_argument(std::forward(args)...); -} + Argument::MAX_ARGS, size); + return false; + } -template -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( - [&](auto&&... largs) { - return set_argument(largs..., - std::forward(args)...); - }, - tuple); + m_args = new ArgumentPtr[size]{}; + return true; } -template -constexpr T* ArgsCache::set_return(GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags) { - return set_argument(Argument::ABSENT, nullptr, - type_info, transfer, flags); +template +constexpr void ArgsCache::set_argument(T* arg, const Argument::Init& init) { + Argument::init_common(init, arg); + arg_get(init.index) = arg; } template -constexpr T* ArgsCache::set_instance(GITransfer transfer, +constexpr void ArgsCache::set_return(T* arg, GITransfer transfer, GjsArgumentFlags flags) { - return set_argument(Argument::ABSENT, nullptr, - transfer, flags); -} - -GType ArgsCache::instance_type() const { - if (!m_is_method) - return G_TYPE_NONE; - - return instance()->as_instance()->gtype(); + set_argument( + arg, Argument::Init{nullptr, Argument::ABSENT, transfer, flags}); } -GITypeTag ArgsCache::return_tag() const { - Argument* rval = return_value(); - if (!rval) - return GI_TYPE_TAG_VOID; +template +constexpr void ArgsCache::set_instance(T* arg, GITransfer transfer, + GjsArgumentFlags flags) { + set_argument( + arg, Argument::Init{nullptr, Argument::ABSENT, transfer, flags}); +} + +Maybe ArgsCache::instance_type() const { + return instance() + .andThen(std::mem_fn(&Argument::as_instance)) + .map(std::mem_fn(&Arg::Instance::gtype)); +} + +Maybe ArgsCache::return_tag() const { + return return_value().andThen(std::mem_fn(&Argument::return_tag)); +} + +void ArgsCache::set_skip_all(uint8_t index, const char* name) { + set_argument(new Arg::SkipAll(), + Argument::Init{name, index, GI_TRANSFER_NOTHING, + GjsArgumentFlags::SKIP_ALL}); +} + +void ArgsCache::init_out_array_length_argument(const GI::ArgInfo length_arg, + GjsArgumentFlags flags, + unsigned length_pos) { + // Even if we skip the length argument most of time, we need to do some + // basic initialization here. + g_assert(length_pos <= Argument::MAX_ARGS && "too many arguments"); + uint8_t validated_length_pos = length_pos; + set_argument( + new Arg::ArrayLengthOut(), + Argument::Init{ + length_arg.name(), validated_length_pos, GI_TRANSFER_NOTHING, + static_cast(flags | GjsArgumentFlags::SKIP_ALL)}); +} + +void ArgsCache::set_array_argument(const GI::CallableInfo callable, + uint8_t gi_index, + const GI::TypeInfo type_info, + GIDirection direction, const GI::ArgInfo arg, + GjsArgumentFlags flags, + unsigned length_pos) { + g_assert(type_info.array_type() == GI_ARRAY_TYPE_C); + + GI::AutoTypeInfo element_type{type_info.element_type()}; + + GI::StackArgInfo length_arg; + callable.load_arg(length_pos, &length_arg); + GI::StackTypeInfo length_type; + length_arg.load_type(&length_type); + GITypeTag length_tag = length_type.tag(); + GIDirection length_direction = length_arg.direction(); + + Argument::Init common_args{arg.name(), gi_index, arg.ownership_transfer(), + flags}; + + if (direction == GI_DIRECTION_IN) { + if (element_type.is_basic()) { + set_argument( + new Arg::BasicExplicitCArrayIn(element_type.tag(), length_pos, + length_tag, length_direction), + common_args); + } else { + set_argument(new Arg::CArrayIn(type_info, length_pos, length_tag, + length_direction), + common_args); + } + set_skip_all(length_pos, length_arg.name()); + } else if (direction == GI_DIRECTION_INOUT) { + if (element_type.is_basic()) { + set_argument(new Arg::BasicExplicitCArrayInOut( + element_type.tag(), length_pos, length_tag, + length_direction), + common_args); + } else { + set_argument(new Arg::CArrayInOut(type_info, length_pos, length_tag, + length_direction), + common_args); + } + set_skip_all(length_pos, length_arg.name()); + } else { + if (element_type.is_basic()) { + set_argument( + new Arg::BasicExplicitCArrayOut(element_type.tag(), length_pos, + length_tag, length_direction), + common_args); + } else { + set_argument(new Arg::CArrayOut(type_info, length_pos, length_tag, + length_direction), + common_args); + } + } - return rval->return_tag(); + if (direction == GI_DIRECTION_OUT) + init_out_array_length_argument(length_arg, flags, length_pos); } -GITypeInfo* ArgsCache::return_type() const { - Argument* rval = return_value(); - if (!rval) - return nullptr; +void ArgsCache::set_array_return(const GI::CallableInfo callable, + const GI::TypeInfo type_info, + GjsArgumentFlags flags, unsigned length_pos) { + g_assert(type_info.array_type() == GI_ARRAY_TYPE_C); - return const_cast(rval->return_type()); -} + GI::AutoTypeInfo element_type{type_info.element_type()}; -constexpr void ArgsCache::set_skip_all(uint8_t index, const char* name) { - set_argument(index, name, GI_TRANSFER_NOTHING, - GjsArgumentFlags::SKIP_ALL); -} + GI::StackArgInfo length_arg; + callable.load_arg(length_pos, &length_arg); + GI::StackTypeInfo length_type; + length_arg.load_type(&length_type); + GITypeTag length_tag = length_type.tag(); + GIDirection length_direction = length_arg.direction(); -template -void ArgsCache::set_array_argument(GICallableInfo* callable, uint8_t gi_index, - GITypeInfo* type_info, GIDirection direction, - GIArgInfo* arg, GjsArgumentFlags flags, - int length_pos) { - Arg::ExplicitArray* array; - GIArgInfo length_arg; - g_callable_info_load_arg(callable, length_pos, &length_arg); - GITypeInfo length_type; - g_arg_info_load_type(&length_arg, &length_type); - - if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) { - GITransfer transfer = g_callable_info_get_caller_owns(callable); - array = set_return(type_info, transfer, - GjsArgumentFlags::NONE); + GITransfer transfer = callable.caller_owns(); + if (element_type.is_basic()) { + set_return( + new Arg::BasicExplicitCArrayOut(element_type.tag(), length_pos, + length_tag, length_direction), + transfer, GjsArgumentFlags::NONE); } else { - const char* arg_name = g_base_info_get_name(arg); - GITransfer transfer = g_arg_info_get_ownership_transfer(arg); - auto common_args = - std::make_tuple(gi_index, arg_name, type_info, transfer, flags); - - if (direction == GI_DIRECTION_IN) { - array = set_argument_auto(common_args); - set_skip_all(length_pos, g_base_info_get_name(&length_arg)); - } else if (direction == GI_DIRECTION_INOUT) { - array = set_argument_auto(common_args); - set_skip_all(length_pos, g_base_info_get_name(&length_arg)); - } else { - array = set_argument_auto(common_args); - } - } - - if (ArgKind == Arg::Kind::RETURN_VALUE || direction == GI_DIRECTION_OUT) { - // Even if we skip the length argument most of time, we need to - // do some basic initialization here. - set_argument( - length_pos, g_base_info_get_name(&length_arg), &length_type, - GI_TRANSFER_NOTHING, - static_cast(flags | GjsArgumentFlags::SKIP_ALL)); + set_return(new Arg::CArrayOut(type_info, length_pos, length_tag, + length_direction), + transfer, GjsArgumentFlags::NONE); } - array->set_array_length(length_pos, g_type_info_get_tag(&length_type), - g_arg_info_get_direction(&length_arg)); + init_out_array_length_argument(length_arg, flags, length_pos); } -void ArgsCache::build_return(GICallableInfo* callable, bool* inc_counter_out) { +void ArgsCache::build_return(const GI::CallableInfo callable, + bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); if (!m_has_return) { @@ -1929,92 +2799,111 @@ void ArgsCache::build_return(GICallableInfo* callable, bool* inc_counter_out) { return; } - GITypeInfo type_info; - g_callable_info_load_return_type(callable, &type_info); - GITransfer transfer = g_callable_info_get_caller_owns(callable); - GITypeTag tag = g_type_info_get_tag(&type_info); + GI::StackTypeInfo type_info; + callable.load_return_type(&type_info); + GITransfer transfer = callable.caller_owns(); + GITypeTag tag = type_info.tag(); *inc_counter_out = true; GjsArgumentFlags flags = GjsArgumentFlags::SKIP_IN; - if (g_callable_info_may_return_null(callable)) + if (callable.may_return_null()) flags |= GjsArgumentFlags::MAY_BE_NULL; switch (tag) { case GI_TYPE_TAG_BOOLEAN: - set_return(&type_info, transfer, flags); + set_return(new Arg::BooleanReturn(), transfer, flags); return; case GI_TYPE_TAG_INT8: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT16: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT32: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT8: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT16: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT32: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT64: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT64: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_FLOAT: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_DOUBLE: - set_return>( - &type_info, transfer, flags); + set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { - set_return>( - &type_info, transfer, flags); + set_return(new Arg::StringReturn(), + transfer, flags); return; } else { - set_return>( - &type_info, transfer, flags); + set_return(new Arg::StringReturn(), + transfer, flags); return; } case GI_TYPE_TAG_ARRAY: { - int length_pos = g_type_info_get_array_length(&type_info); - if (length_pos >= 0) { - set_array_argument( - callable, 0, &type_info, GI_DIRECTION_OUT, nullptr, flags, - length_pos); + Maybe length_pos = type_info.array_length_index(); + if (length_pos) { + set_array_return(callable, type_info, flags, *length_pos); + return; + } + + GIArrayType array_type = type_info.array_type(); + if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + set_return(new Arg::ByteArrayReturn(), transfer, flags); return; } + if (type_info.element_type().is_basic()) { + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + set_return( + new Arg::BasicCZeroTerminatedArrayReturn(type_info), + transfer, flags); + return; + } + if (type_info.array_fixed_size()) { + set_return( + new Arg::BasicCFixedSizeArrayReturn(type_info), + transfer, flags); + return; + } + } else if (array_type == GI_ARRAY_TYPE_ARRAY) { + set_return(new Arg::BasicGArrayReturn(type_info), transfer, + flags); + return; + } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + set_return(new Arg::BasicGPtrArrayReturn(type_info), + transfer, flags); + return; + } + } + [[fallthrough]]; } @@ -2022,20 +2911,39 @@ void ArgsCache::build_return(GICallableInfo* callable, bool* inc_counter_out) { break; } + if (type_info.is_basic()) { + if (transfer == GI_TRANSFER_NOTHING) { + set_return(new Arg::BasicTypeReturn(tag), transfer, flags); + } else { + set_return(new Arg::BasicTypeTransferableReturn(tag), transfer, + flags); + } + return; + } else if (tag == GI_TYPE_TAG_GLIST || tag == GI_TYPE_TAG_GSLIST) { + if (type_info.element_type().is_basic()) { + set_return(new Arg::BasicGListReturn(type_info), transfer, flags); + return; + } + } else if (tag == GI_TYPE_TAG_GHASH) { + if (type_info.key_type().is_basic() && + type_info.value_type().is_basic()) { + set_return(new Arg::BasicGHashReturn(type_info), transfer, flags); + return; + } + } + // in() is ignored for the return value, but skip_in is not (it is used // in the failure release path) - set_return(&type_info, transfer, flags); + set_return(new Arg::FallbackReturn(type_info), transfer, flags); } namespace Arg { -Enum::Enum(GIEnumInfo* enum_info) { +Enum::Enum(const GI::EnumInfo info) { int64_t min = std::numeric_limits::max(); int64_t max = std::numeric_limits::min(); - int n = g_enum_info_get_n_values(enum_info); - for (int i = 0; i < n; i++) { - GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); - int64_t value = g_value_info_get_value(value_info); + for (GI::AutoValueInfo value_info : info.values()) { + int64_t value = value_info.value(); if (value > max) max = value; @@ -2054,18 +2962,15 @@ Enum::Enum(GIEnumInfo* enum_info) { m_unsigned = (min >= 0 && max > std::numeric_limits::max()); } -Flags::Flags(GIEnumInfo* enum_info) { +Flags::Flags(const GI::FlagsInfo info) { uint64_t mask = 0; - int n = g_enum_info_get_n_values(enum_info); - for (int i = 0; i < n; i++) { - GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); - int64_t value = g_value_info_get_value(value_info); + for (GI::AutoValueInfo value_info : info.values()) { // From the docs for g_value_info_get_value(): "This will always be // representable as a 32-bit signed or unsigned value. The use of // gint64 as the return type is to allow both." // We stuff both into an unsigned, int-sized field, matching the // internal representation of flags in GLib (which uses guint). - mask |= static_cast(value); + mask |= static_cast(value_info.value()); } m_mask = mask; @@ -2073,184 +2978,158 @@ Flags::Flags(GIEnumInfo* enum_info) { } // namespace Arg -namespace arg_cache { -[[nodiscard]] static inline bool is_gdk_atom(GIBaseInfo* info) { - return strcmp("Atom", g_base_info_get_name(info)) == 0 && - strcmp("Gdk", g_base_info_get_namespace(info)) == 0; -} -} // namespace arg_cache - template -void ArgsCache::build_interface_in_arg(uint8_t gi_index, GITypeInfo* type_info, - GIBaseInfo* interface_info, - GITransfer transfer, const char* name, - GjsArgumentFlags flags) { - GIInfoType interface_type = g_base_info_get_type(interface_info); - - auto base_args = - std::make_tuple(gi_index, name, type_info, transfer, flags); - auto common_args = - std::tuple_cat(base_args, std::make_tuple(interface_info)); - +void ArgsCache::build_interface_in_arg(const Argument::Init& base_args, + const GI::BaseInfo interface_info) { // We do some transfer magic later, so let's ensure we don't mess up. // Should not happen in practice. - if (G_UNLIKELY(transfer == GI_TRANSFER_CONTAINER)) { - set_argument_auto(base_args, - INTERFACE_TRANSFER_CONTAINER); + if (G_UNLIKELY(base_args.transfer == GI_TRANSFER_CONTAINER)) { + set_argument( + new Arg::NotIntrospectable(INTERFACE_TRANSFER_CONTAINER), + base_args); return; } - switch (interface_type) { - case GI_INFO_TYPE_ENUM: - set_argument_auto(common_args); - return; - - case GI_INFO_TYPE_FLAGS: - set_argument_auto(common_args); - return; + if (auto flags_info = interface_info.as()) { + set_argument(new Arg::FlagsIn(*flags_info), base_args); + return; + } - case GI_INFO_TYPE_STRUCT: - if (g_struct_info_is_foreign(interface_info)) { - if constexpr (ArgKind == Arg::Kind::INSTANCE) - set_argument_auto( - common_args); - else - set_argument_auto( - common_args); - return; - } - [[fallthrough]]; - case GI_INFO_TYPE_BOXED: - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_UNION: { - GType gtype = g_registered_type_info_get_g_type(interface_info); - - if (interface_type == GI_INFO_TYPE_STRUCT && gtype == G_TYPE_NONE && - !g_struct_info_is_gtype_struct(interface_info)) { - if constexpr (ArgKind != Arg::Kind::INSTANCE) { - // This covers cases such as GTypeInstance - set_argument_auto( - common_args); - return; - } - } + if (auto enum_info = interface_info.as()) { + set_argument(new Arg::EnumIn(*enum_info), base_args); + return; + } - // Transfer handling is a bit complex here, because some of our in() - // arguments know not to copy stuff if we don't need to. + auto struct_info = interface_info.as(); + if (struct_info && struct_info->is_foreign()) { + if constexpr (ArgKind == Arg::Kind::INSTANCE) + set_argument( + new Arg::ForeignStructInstanceIn(*struct_info), base_args); + else + set_argument(new Arg::ForeignStructIn(*struct_info), + base_args); + return; + } - if (gtype == G_TYPE_VALUE) { - if constexpr (ArgKind == Arg::Kind::INSTANCE) - set_argument_auto(common_args); - else if (transfer == GI_TRANSFER_NOTHING) - set_argument_auto( - common_args); - else - set_argument_auto(common_args); + if (auto reg_info = interface_info.as()) { + if (reg_info->is_gdk_atom()) { + if constexpr (ArgKind != Arg::Kind::INSTANCE) { + set_argument(new Arg::GdkAtomIn(), base_args); return; } + } - if (arg_cache::is_gdk_atom(interface_info)) { - // Fall back to the generic marshaller - set_argument_auto( - common_args); - return; - } + GType gtype = reg_info->gtype(); - if (gtype == G_TYPE_CLOSURE) { - if (transfer == GI_TRANSFER_NOTHING && - ArgKind != Arg::Kind::INSTANCE) - set_argument_auto( - common_args); - else - set_argument_auto(common_args); + if (struct_info && gtype == G_TYPE_NONE && + !struct_info->is_gtype_struct()) { + if constexpr (ArgKind != Arg::Kind::INSTANCE) { + // This covers cases such as GTypeInstance + set_argument(new Arg::FallbackInterfaceIn(*reg_info), + base_args); return; } + } - if (gtype == G_TYPE_BYTES) { - if (transfer == GI_TRANSFER_NOTHING && - ArgKind != Arg::Kind::INSTANCE) - set_argument_auto( - common_args); - else - set_argument_auto(common_args); - return; - } + // Transfer handling is a bit complex here, because some of our in() + // arguments know not to copy stuff if we don't need to. - if (g_type_is_a(gtype, G_TYPE_OBJECT)) { - set_argument_auto(common_args); - return; - } + if (gtype == G_TYPE_VALUE) { + if constexpr (ArgKind == Arg::Kind::INSTANCE) + set_argument(new Arg::BoxedIn(*reg_info), base_args); + else if (base_args.transfer == GI_TRANSFER_NOTHING) + set_argument(new Arg::GValueInTransferNone(*reg_info), + base_args); + else + set_argument(new Arg::GValueIn(*reg_info), base_args); + return; + } - if (g_type_is_a(gtype, G_TYPE_PARAM)) { - set_argument_auto( - common_args); - return; - } + if (gtype == G_TYPE_CLOSURE) { + if (base_args.transfer == GI_TRANSFER_NOTHING && + ArgKind != Arg::Kind::INSTANCE) + set_argument( + new Arg::GClosureInTransferNone(*reg_info), base_args); + else + set_argument(new Arg::GClosureIn(*reg_info), + base_args); + return; + } - if (interface_type == GI_INFO_TYPE_UNION) { - if (gtype == G_TYPE_NONE) { - // Can't handle unions without a GType - set_argument_auto( - base_args, UNREGISTERED_UNION); - return; - } + if (gtype == G_TYPE_BYTES) { + if (base_args.transfer == GI_TRANSFER_NOTHING && + ArgKind != Arg::Kind::INSTANCE) + set_argument(new Arg::GBytesInTransferNone(*reg_info), + base_args); + else + set_argument(new Arg::GBytesIn(*reg_info), base_args); + return; + } - set_argument_auto(common_args); - return; - } + if (g_type_is_a(gtype, G_TYPE_OBJECT)) { + set_argument(new Arg::ObjectIn(*reg_info), base_args); + return; + } - if (G_TYPE_IS_INSTANTIATABLE(gtype)) { - set_argument_auto(common_args); + if (g_type_is_a(gtype, G_TYPE_PARAM)) { + if constexpr (ArgKind != Arg::Kind::INSTANCE) { + set_argument(new Arg::FallbackInterfaceIn(*reg_info), + base_args); return; } + } - if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { - set_argument_auto(common_args); + if (interface_info.is_union()) { + if (gtype == G_TYPE_NONE) { + // Can't handle unions without a GType + set_argument( + new Arg::NotIntrospectable(UNREGISTERED_UNION), base_args); return; } - // generic boxed type - if (gtype == G_TYPE_NONE) { - if (transfer != GI_TRANSFER_NOTHING) { - // Can't transfer ownership of a structure type not - // registered as a boxed - set_argument_auto( - base_args, UNREGISTERED_BOXED_WITH_TRANSFER); - return; - } + set_argument(new Arg::UnionIn(*reg_info), base_args); + return; + } - set_argument_auto( - common_args); + if (G_TYPE_IS_INSTANTIATABLE(gtype)) { + set_argument(new Arg::FundamentalIn(*reg_info), base_args); + return; + } + + if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { + set_argument(new Arg::InterfaceIn(*reg_info), base_args); + return; + } + + // generic boxed type + if (gtype == G_TYPE_NONE) { + if (base_args.transfer != GI_TRANSFER_NOTHING) { + // Can't transfer ownership of a structure type not + // registered as a boxed + set_argument(new Arg::NotIntrospectable( + UNREGISTERED_BOXED_WITH_TRANSFER), + base_args); return; } - set_argument_auto(common_args); - return; - } break; - - case GI_INFO_TYPE_INVALID: - case GI_INFO_TYPE_FUNCTION: - case GI_INFO_TYPE_CALLBACK: - case GI_INFO_TYPE_CONSTANT: - case GI_INFO_TYPE_INVALID_0: - case GI_INFO_TYPE_VALUE: - case GI_INFO_TYPE_SIGNAL: - case GI_INFO_TYPE_VFUNC: - case GI_INFO_TYPE_PROPERTY: - case GI_INFO_TYPE_FIELD: - case GI_INFO_TYPE_ARG: - case GI_INFO_TYPE_TYPE: - case GI_INFO_TYPE_UNRESOLVED: - default: - // Don't know how to handle this interface type (should not happen - // in practice, for typelibs emitted by g-ir-compiler) - set_argument_auto(base_args, - UNSUPPORTED_TYPE); + + set_argument(new Arg::UnregisteredBoxedIn(*struct_info), + base_args); + return; + } + set_argument(new Arg::BoxedIn(*reg_info), base_args); + return; } + + // Don't know how to handle this interface type (should not happen + // in practice, for typelibs emitted by g-ir-compiler) + set_argument(new Arg::NotIntrospectable(UNSUPPORTED_TYPE), + base_args); } -void ArgsCache::build_normal_in_arg(uint8_t gi_index, GITypeInfo* type_info, - GIArgInfo* arg, GjsArgumentFlags flags) { +void ArgsCache::build_normal_in_arg(uint8_t gi_index, + const GI::TypeInfo type_info, + const GI::ArgInfo arg, + GjsArgumentFlags flags) { // "Normal" in arguments are those arguments that don't require special // processing, and don't touch other arguments. // Main categories are: @@ -2263,261 +3142,487 @@ void ArgsCache::build_normal_in_arg(uint8_t gi_index, GITypeInfo* type_info, // - hashes // - sequences (null-terminated arrays, lists, etc.) - const char* name = g_base_info_get_name(arg); - GITransfer transfer = g_arg_info_get_ownership_transfer(arg); - auto common_args = - std::make_tuple(gi_index, name, type_info, transfer, flags); - GITypeTag tag = g_type_info_get_tag(type_info); + const char* name = arg.name(); + GITransfer transfer = arg.ownership_transfer(); + Argument::Init common_args{name, gi_index, transfer, flags}; - switch (tag) { + switch (type_info.tag()) { case GI_TYPE_TAG_VOID: - set_argument_auto(common_args); + set_argument(new Arg::NullIn(), common_args); break; case GI_TYPE_TAG_BOOLEAN: - set_argument_auto(common_args); + set_argument(new Arg::BooleanIn(), common_args); break; case GI_TYPE_TAG_INT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_FLOAT: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_DOUBLE: - set_argument_auto>(common_args); + set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UNICHAR: - set_argument_auto(common_args); + set_argument(new Arg::UnicharIn(), common_args); break; case GI_TYPE_TAG_GTYPE: - set_argument_auto(common_args); + set_argument(new Arg::GTypeIn(), common_args); break; case GI_TYPE_TAG_FILENAME: if (transfer == GI_TRANSFER_NOTHING) - set_argument_auto(common_args); + set_argument(new Arg::FilenameInTransferNone(), common_args); else - set_argument_auto(common_args); + set_argument(new Arg::FilenameIn(), common_args); break; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) - set_argument_auto>( - common_args); + set_argument(new Arg::StringInTransferNone(), + common_args); else - set_argument_auto(common_args); + set_argument(new Arg::StringIn(), common_args); break; case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - build_interface_in_arg(gi_index, type_info, interface_info, - transfer, name, flags); + build_interface_in_arg(common_args, type_info.interface()); return; } - case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_ERROR: + set_argument(new Arg::ErrorIn(), common_args); + return; + case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: + if (type_info.element_type().is_basic()) { + set_argument(new Arg::BasicGListIn(type_info), common_args); + return; + } + + // Fall back to the generic marshaller + set_argument(new Arg::FallbackIn(type_info), common_args); + return; + case GI_TYPE_TAG_GHASH: - case GI_TYPE_TAG_ERROR: + if (type_info.key_type().is_basic() && + type_info.value_type().is_basic()) { + set_argument(new Arg::BasicGHashIn(type_info), common_args); + return; + } + + // Fall back to the generic marshaller + set_argument(new Arg::FallbackIn(type_info), common_args); + return; + + case GI_TYPE_TAG_ARRAY: { + GIArrayType array_type = type_info.array_type(); + if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + set_argument(new Arg::ByteArrayIn(), common_args); + return; + } + + if (type_info.element_type().is_basic()) { + if (array_type == GI_ARRAY_TYPE_ARRAY) { + set_argument(new Arg::BasicGArrayIn(type_info), + common_args); + return; + } + + if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + set_argument(new Arg::BasicGPtrArrayIn(type_info), + common_args); + return; + } + + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + set_argument( + new Arg::BasicCZeroTerminatedArrayIn(type_info), + common_args); + return; + } + if (type_info.array_fixed_size()) { + set_argument(new Arg::BasicCFixedSizeArrayIn(type_info), + common_args); + return; + } + } + } + + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + set_argument(new Arg::ZeroTerminatedArrayIn(type_info), + common_args); + return; + } + if (type_info.array_fixed_size()) { + set_argument(new Arg::FixedSizeArrayIn(type_info), + common_args); + return; + } + } + [[fallthrough]]; + } + default: // FIXME: Falling back to the generic marshaller - set_argument_auto(common_args); + set_argument(new Arg::FallbackIn(type_info), common_args); } } -void ArgsCache::build_normal_out_arg(uint8_t gi_index, GITypeInfo* type_info, - GIArgInfo* arg, GjsArgumentFlags flags) { - const char* name = g_base_info_get_name(arg); - GITransfer transfer = g_arg_info_get_ownership_transfer(arg); - auto common_args = - std::make_tuple(gi_index, name, type_info, transfer, flags); - GITypeTag tag = g_type_info_get_tag(type_info); +void ArgsCache::build_normal_out_arg(uint8_t gi_index, + const GI::TypeInfo type_info, + const GI::ArgInfo arg, + GjsArgumentFlags flags) { + GITransfer transfer = arg.ownership_transfer(); + Argument::Init common_args{arg.name(), gi_index, transfer, flags}; + GITypeTag tag = type_info.tag(); switch (tag) { case GI_TYPE_TAG_BOOLEAN: - set_argument_auto(common_args); + set_argument(new Arg::BooleanOut(), common_args); break; case GI_TYPE_TAG_INT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_FLOAT: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_DOUBLE: - set_argument_auto>(common_args); + set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { - set_argument_auto>( - common_args); + set_argument(new Arg::StringOut(), + common_args); } else { - set_argument_auto>( - common_args); + set_argument(new Arg::StringOut(), + common_args); } return; - default: - set_argument_auto(common_args); + default: { + } + } + + if (type_info.is_basic()) { + if (transfer == GI_TRANSFER_NOTHING) { + set_argument(new Arg::BasicTypeOut(tag), common_args); + } else { + set_argument(new Arg::BasicTypeTransferableOut(tag), common_args); + } + return; + } else if (tag == GI_TYPE_TAG_ARRAY) { + GIArrayType array_type = type_info.array_type(); + if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + set_argument(new Arg::ByteArrayOut(), common_args); + return; + } + + if (type_info.element_type().is_basic()) { + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + set_argument( + new Arg::BasicCZeroTerminatedArrayOut(type_info), + common_args); + return; + } + + if (type_info.array_fixed_size()) { + set_argument(new Arg::BasicCFixedSizeArrayOut(type_info), + common_args); + return; + } + } else if (array_type == GI_ARRAY_TYPE_ARRAY) { + set_argument(new Arg::BasicGArrayOut(type_info), common_args); + return; + } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + set_argument(new Arg::BasicGPtrArrayOut(type_info), + common_args); + return; + } + } + } + + if (tag == GI_TYPE_TAG_GLIST || tag == GI_TYPE_TAG_GSLIST) { + if (type_info.element_type().is_basic()) { + set_argument(new Arg::BasicGListOut(type_info), common_args); + return; + } + } else if (tag == GI_TYPE_TAG_GHASH) { + if (type_info.key_type().is_basic() && + type_info.value_type().is_basic()) { + set_argument(new Arg::BasicGHashOut(type_info), common_args); + return; + } + } else if (tag == GI_TYPE_TAG_ARRAY) { + GIArrayType array_type = type_info.array_type(); + if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + set_argument(new Arg::ByteArrayOut(), common_args); + return; + } + + if (type_info.element_type().is_basic()) { + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + set_argument( + new Arg::BasicCZeroTerminatedArrayOut(type_info), + common_args); + return; + } + + if (type_info.array_fixed_size()) { + set_argument(new Arg::BasicCFixedSizeArrayOut(type_info), + common_args); + return; + } + } else if (array_type == GI_ARRAY_TYPE_ARRAY) { + set_argument(new Arg::BasicGArrayOut(type_info), common_args); + return; + } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + set_argument(new Arg::BasicGPtrArrayOut(type_info), + common_args); + return; + } + } } + + set_argument(new Arg::FallbackOut(type_info), common_args); } -void ArgsCache::build_normal_inout_arg(uint8_t gi_index, GITypeInfo* type_info, - GIArgInfo* arg, GjsArgumentFlags flags) { - const char* name = g_base_info_get_name(arg); - GITransfer transfer = g_arg_info_get_ownership_transfer(arg); - auto common_args = - std::make_tuple(gi_index, name, type_info, transfer, flags); - GITypeTag tag = g_type_info_get_tag(type_info); +void ArgsCache::build_normal_inout_arg(uint8_t gi_index, + const GI::TypeInfo type_info, + const GI::ArgInfo arg, + GjsArgumentFlags flags) { + GITransfer transfer = arg.ownership_transfer(); + Argument::Init common_args{arg.name(), gi_index, transfer, flags}; + GITypeTag tag = type_info.tag(); switch (tag) { case GI_TYPE_TAG_BOOLEAN: - set_argument_auto(common_args); + set_argument(new Arg::BooleanInOut(), common_args); break; case GI_TYPE_TAG_INT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT8: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT16: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT32: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT64: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_FLOAT: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_DOUBLE: - set_argument_auto>(common_args); + set_argument(new Arg::NumericInOut(), common_args); return; - default: - set_argument_auto(common_args); + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + if (type_info.element_type().is_basic()) { + set_argument(new Arg::BasicGListInOut(type_info), common_args); + return; + } + + set_argument(new Arg::FallbackInOut(type_info), common_args); + return; + + case GI_TYPE_TAG_GHASH: + if (type_info.key_type().is_basic() && + type_info.value_type().is_basic()) { + set_argument(new Arg::BasicGHashInOut(type_info), common_args); + return; + } + + set_argument(new Arg::FallbackInOut(type_info), common_args); + return; + + case GI_TYPE_TAG_ARRAY: { + GIArrayType array_type = type_info.array_type(); + + if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + set_argument(new Arg::ByteArrayInOut(), common_args); + return; + } + + if (array_type == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { + if (type_info.element_type().is_basic()) { + set_argument( + new Arg::BasicCZeroTerminatedArrayInOut(type_info), + common_args); + return; + } + set_argument(new Arg::ZeroTerminatedArrayInOut(type_info), + common_args); + return; + } + if (type_info.array_fixed_size()) { + if (type_info.element_type().is_basic()) { + set_argument( + new Arg::BasicCFixedSizeArrayInOut(type_info), + common_args); + return; + } + set_argument(new Arg::FixedSizeArrayInOut(type_info), + common_args); + return; + } + } else if (array_type == GI_ARRAY_TYPE_ARRAY) { + set_argument(new Arg::BasicGArrayInOut(type_info), common_args); + return; + } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + set_argument(new Arg::BasicGPtrArrayInOut(type_info), + common_args); + return; + } + + set_argument(new Arg::FallbackInOut(type_info), common_args); + return; + } + + default: { + } + } + + if (type_info.is_basic()) { + if (transfer == GI_TRANSFER_NOTHING) { + set_argument(new Arg::BasicTypeInOut(tag), common_args); + } else { + set_argument(new Arg::BasicTypeTransferableInOut(tag), common_args); + } + return; } + + set_argument(new Arg::FallbackInOut(type_info), common_args); } -void ArgsCache::build_instance(GICallableInfo* callable) { +void ArgsCache::build_instance(const GI::CallableInfo callable) { if (!m_is_method) return; - GIBaseInfo* interface_info = g_base_info_get_container(callable); // !owned + Maybe interface_info = callable.container(); + g_assert(interface_info && "callable must be a contained type"); - GITransfer transfer = - g_callable_info_get_instance_ownership_transfer(callable); + GITransfer transfer = callable.instance_ownership_transfer(); // These cases could be covered by the generic marshaller, except that // there's no way to get GITypeInfo for a method's instance parameter. // Instead, special-case the arguments here that would otherwise go through // the generic marshaller. // See: https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/334 - GIInfoType info_type = g_base_info_get_type(interface_info); - if (info_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_gtype_struct(interface_info)) { - set_instance(transfer); + auto struct_info = interface_info->as(); + if (struct_info && struct_info->is_gtype_struct()) { + set_instance(new Arg::GTypeStructInstanceIn(), transfer); return; } - if (info_type == GI_INFO_TYPE_OBJECT) { - GType gtype = g_registered_type_info_get_g_type(interface_info); + + if (auto reg_info = interface_info->as()) { + GType gtype = reg_info->gtype(); if (g_type_is_a(gtype, G_TYPE_PARAM)) { - set_instance(transfer); + set_instance(new Arg::ParamInstanceIn(), transfer); return; } } build_interface_in_arg( - Argument::ABSENT, nullptr, interface_info, transfer, nullptr, - GjsArgumentFlags::NONE); + Argument::Init{nullptr, Argument::ABSENT, transfer, + GjsArgumentFlags::NONE}, + *interface_info); } static constexpr bool type_tag_is_scalar(GITypeTag tag) { @@ -2526,19 +3631,18 @@ static constexpr bool type_tag_is_scalar(GITypeTag tag) { } void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, - GIArgInfo* arg, GICallableInfo* callable, + const GI::ArgInfo arg, + const GI::CallableInfo callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); - GITypeInfo type_info; - const char* arg_name = g_base_info_get_name(arg); - g_arg_info_load_type(arg, &type_info); - GITransfer transfer = g_arg_info_get_ownership_transfer(arg); + GI::StackTypeInfo type_info; + arg.load_type(&type_info); GjsArgumentFlags flags = GjsArgumentFlags::NONE; - if (g_arg_info_may_be_null(arg)) + if (arg.may_be_null()) flags |= GjsArgumentFlags::MAY_BE_NULL; - if (g_arg_info_is_caller_allocates(arg)) + if (arg.caller_allocates()) flags |= GjsArgumentFlags::CALLER_ALLOCATES; if (direction == GI_DIRECTION_IN) @@ -2547,75 +3651,68 @@ void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, flags |= GjsArgumentFlags::SKIP_IN; *inc_counter_out = true; - auto common_args = - std::make_tuple(gi_index, arg_name, &type_info, transfer, flags); + Argument::Init common_args{arg.name(), gi_index, arg.ownership_transfer(), + flags}; - GITypeTag type_tag = g_type_info_get_tag(&type_info); + GITypeTag type_tag = type_info.tag(); if (direction == GI_DIRECTION_OUT && (flags & GjsArgumentFlags::CALLER_ALLOCATES)) { size_t size = 0; if (type_tag == GI_TYPE_TAG_ARRAY) { - GIArrayType array_type = g_type_info_get_array_type(&type_info); - - switch (array_type) { + switch (type_info.array_type()) { case GI_ARRAY_TYPE_C: { - GjsAutoTypeInfo param_info; - int n_elements = - g_type_info_get_array_fixed_size(&type_info); - - if (n_elements <= 0) + Maybe n_elements = type_info.array_fixed_size(); + if (!n_elements || *n_elements == 0) break; - param_info = g_type_info_get_param_type(&type_info, 0); - GITypeTag param_tag = g_type_info_get_tag(param_info); - - size = gjs_type_get_element_size(param_tag, param_info); - size *= n_elements; + GI::AutoTypeInfo element_type{type_info.element_type()}; + size = gjs_type_get_element_size(element_type.tag(), + element_type); + size *= *n_elements; break; } default: break; } - } else if (!type_tag_is_scalar(type_tag) && - !g_type_info_is_pointer(&type_info)) { + } else if (!type_tag_is_scalar(type_tag) && !type_info.is_pointer()) { // Scalar out parameters should not be annotated with // caller-allocates, which is for structured types that need to be // allocated in order for the function to fill them in. - size = gjs_type_get_element_size(type_tag, &type_info); + size = gjs_type_get_element_size(type_tag, type_info); } if (!size) { - set_argument_auto( - common_args, OUT_CALLER_ALLOCATES_NON_STRUCT); + set_argument( + new Arg::NotIntrospectable(OUT_CALLER_ALLOCATES_NON_STRUCT), + common_args); 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); + GI::AutoBaseInfo interface_info{type_info.interface()}; + // FIXME: What if it is not a registered type + GType gtype = + interface_info.as()->gtype(); if (g_type_is_a(gtype, G_TYPE_BOXED)) { - auto* gjs_arg = set_argument_auto( - common_args, gtype); - gjs_arg->m_allocates_size = size; + set_argument( + new Arg::BoxedCallerAllocatesOut(type_info, size, gtype), + common_args); return; } } - auto* gjs_arg = set_argument_auto(common_args); - gjs_arg->m_allocates_size = size; - + set_argument(new Arg::CallerAllocatesOut(type_info, size), common_args); return; } if (type_tag == GI_TYPE_TAG_INTERFACE) { - GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info); - if (interface_info.type() == GI_INFO_TYPE_CALLBACK) { + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (auto callback_info = interface_info.as()) { if (direction != GI_DIRECTION_IN) { // Can't do callbacks for out or inout - set_argument_auto(common_args, - CALLBACK_OUT); + set_argument(new Arg::NotIntrospectable(CALLBACK_OUT), + common_args); return; } @@ -2627,31 +3724,34 @@ void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, // overwritten with the 'skipped' one. If no callback follows, // then this is probably an unsupported function, so the // function invocation code will check this and throw. - set_argument_auto( - common_args, DESTROY_NOTIFY_NO_CALLBACK); + set_argument( + new Arg::NotIntrospectable(DESTROY_NOTIFY_NO_CALLBACK), + common_args); *inc_counter_out = false; } else { - auto* gjs_arg = set_argument_auto( - common_args, interface_info); - - int destroy_pos = g_arg_info_get_destroy(arg); - int closure_pos = g_arg_info_get_closure(arg); - - if (destroy_pos >= 0) - set_skip_all(destroy_pos); - - if (closure_pos >= 0) - set_skip_all(closure_pos); - - if (destroy_pos >= 0 && closure_pos < 0) { - set_argument_auto( - common_args, DESTROY_NOTIFY_NO_USER_DATA); + Maybe destroy_pos = arg.destroy_index(); + Maybe closure_pos = arg.closure_index(); + g_assert(destroy_pos.valueOr(0) <= Argument::MAX_ARGS && + "No more than 253 arguments allowed"); + g_assert(closure_pos.valueOr(0) <= Argument::MAX_ARGS && + "No more than 253 arguments allowed"); + + if (destroy_pos) + set_skip_all(*destroy_pos); + + if (closure_pos) + set_skip_all(*closure_pos); + + if (destroy_pos && !closure_pos) { + set_argument( + new Arg::NotIntrospectable(DESTROY_NOTIFY_NO_USER_DATA), + common_args); return; } - gjs_arg->m_scope = g_arg_info_get_scope(arg); - gjs_arg->set_callback_destroy_pos(destroy_pos); - gjs_arg->set_callback_closure_pos(closure_pos); + set_argument(new Arg::CallbackIn(*callback_info, closure_pos, + destroy_pos, arg.scope()), + common_args); } return; @@ -2659,48 +3759,32 @@ void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, } if (type_tag == GI_TYPE_TAG_ARRAY && - g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) { - int length_pos = g_type_info_get_array_length(&type_info); + type_info.array_type() == GI_ARRAY_TYPE_C) { + Maybe length_pos = type_info.array_length_index(); - if (length_pos >= 0) { - Argument* cached_length = argument(length_pos); + if (length_pos) { + Argument* cached_length = argument(*length_pos); bool skip_length = cached_length && !(cached_length->skip_in() && cached_length->skip_out()); - set_array_argument(callable, gi_index, &type_info, direction, arg, - flags, length_pos); + set_array_argument(callable, gi_index, type_info, direction, arg, + flags, *length_pos); - if (length_pos < gi_index && skip_length) { + if (*length_pos < gi_index && skip_length) { // we already collected length_pos, remove it *inc_counter_out = false; } return; - } else if (g_type_info_is_zero_terminated(&type_info)) { - if (direction == GI_DIRECTION_IN) { - set_argument_auto(common_args); - return; - } else if (direction == GI_DIRECTION_INOUT) { - set_argument_auto(common_args); - return; - } - } else if (g_type_info_get_array_fixed_size(&type_info) >= 0) { - if (direction == GI_DIRECTION_IN) { - set_argument_auto(common_args); - return; - } else if (direction == GI_DIRECTION_INOUT) { - set_argument_auto(common_args); - return; - } } } if (direction == GI_DIRECTION_IN) - build_normal_in_arg(gi_index, &type_info, arg, flags); + build_normal_in_arg(gi_index, type_info, arg, flags); else if (direction == GI_DIRECTION_INOUT) - build_normal_inout_arg(gi_index, &type_info, arg, flags); + build_normal_inout_arg(gi_index, type_info, arg, flags); else - build_normal_out_arg(gi_index, &type_info, arg, flags); + build_normal_out_arg(gi_index, type_info, arg, flags); return; } diff --git a/gi/arg-cache.h b/gi/arg-cache.h index e16b3c1f0..d23c20095 100644 --- a/gi/arg-cache.h +++ b/gi/arg-cache.h @@ -12,14 +12,16 @@ #include -#include +#include #include #include +#include #include "gi/arg.h" +#include "gi/info.h" +#include "cjs/auto.h" #include "cjs/enum-utils.h" -#include "cjs/jsapi-util.h" #include "cjs/macros.h" class GjsFunctionCallState; @@ -39,7 +41,6 @@ enum NotIntrospectableReason : uint8_t { namespace Gjs { namespace Arg { -using ReturnValue = struct GenericOut; struct Instance; enum class Kind { @@ -48,9 +49,57 @@ enum class Kind { RETURN_VALUE, }; +class ReturnTag { + GITypeTag m_tag : 5; + bool m_is_enum_or_flags_interface : 1; + bool m_is_pointer : 1; + + public: + constexpr explicit ReturnTag(GITypeTag tag) + : m_tag(tag), + m_is_enum_or_flags_interface(false), + m_is_pointer(false) {} + constexpr explicit ReturnTag(GITypeTag tag, bool is_enum_or_flags_interface, + bool is_pointer) + : m_tag(tag), + m_is_enum_or_flags_interface(is_enum_or_flags_interface), + m_is_pointer(is_pointer) {} + explicit ReturnTag(const GI::TypeInfo type_info) + : m_tag(type_info.tag()), + m_is_pointer(type_info.is_pointer()) { + if (m_tag == GI_TYPE_TAG_INTERFACE) { + GI::AutoBaseInfo interface_info{type_info.interface()}; + m_is_enum_or_flags_interface = interface_info.is_enum_or_flags(); + } + } + + constexpr GITypeTag tag() const { return m_tag; } + [[nodiscard]] + constexpr bool is_enum_or_flags_interface() const { + return m_tag == GI_TYPE_TAG_INTERFACE && m_is_enum_or_flags_interface; + } + [[nodiscard]] + constexpr GType interface_gtype() const { + return is_enum_or_flags_interface() ? GI_TYPE_ENUM_INFO : G_TYPE_NONE; + } + constexpr bool is_pointer() const { return m_is_pointer; } +}; + } // namespace Arg +// When creating an Argument, pass it directly to ArgsCache::set_argument() or +// one of the similar methods, which will call init_common() on it and store it +// in the appropriate place in the arguments cache. struct Argument { + // Convenience struct to prevent long argument lists to make() and the + // functions that call it + struct Init { + const char* name; + uint8_t index; + GITransfer transfer : 2; + GjsArgumentFlags flags : 6; + }; + virtual ~Argument() = default; GJS_JSAPI_RETURN_CONVENTION @@ -94,9 +143,10 @@ struct Argument { protected: constexpr Argument() : m_skip_in(false), m_skip_out(false) {} - virtual GITypeTag return_tag() const { return GI_TYPE_TAG_VOID; } - virtual const GITypeInfo* return_type() const { return nullptr; } - virtual const Arg::Instance* as_instance() const { return nullptr; } + virtual mozilla::Maybe return_tag() const { return {}; } + virtual mozilla::Maybe as_instance() const { + return {}; + } constexpr void set_instance_parameter() { m_arg_name = "instance parameter"; @@ -114,13 +164,11 @@ struct Argument { private: friend struct ArgsCache; - template - static GjsAutoCppPointer make(uint8_t index, const char* name, - GITypeInfo* type_info, GITransfer transfer, - GjsArgumentFlags flags, Args&&... args); + template + static void init_common(const Init&, T* arg); }; -using ArgumentPtr = GjsAutoCppPointer; +using ArgumentPtr = AutoCppPointer; // This is a trick to print out the sizes of the structs at compile time, in // an error message: @@ -141,7 +189,7 @@ static_assert(sizeof(Argument) <= 24, struct ArgsCache { GJS_JSAPI_RETURN_CONVENTION - bool initialize(JSContext* cx, GICallableInfo* callable); + bool initialize(JSContext*, const GI::CallableInfo); // COMPAT: in C++20, use default initializers for these bitfields ArgsCache() : m_is_method(false), m_has_return(false) {} @@ -149,61 +197,52 @@ struct ArgsCache { 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); + void build_arg(uint8_t gi_index, GIDirection, const GI::ArgInfo, + const GI::CallableInfo, bool* inc_counter_out); - void build_return(GICallableInfo* callable, bool* inc_counter_out); + void build_return(const GI::CallableInfo, bool* inc_counter_out); - void build_instance(GICallableInfo* callable); + void build_instance(const GI::CallableInfo); - GType instance_type() const; - GITypeTag return_tag() const; - GITypeInfo* return_type() const; + mozilla::Maybe instance_type() const; + mozilla::Maybe return_tag() const; private: - void build_normal_in_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, - GjsArgumentFlags); - void build_normal_out_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, - GjsArgumentFlags); - void build_normal_inout_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, - GjsArgumentFlags); - + void build_normal_in_arg(uint8_t gi_index, const GI::TypeInfo, + const GI::ArgInfo, GjsArgumentFlags); + void build_normal_out_arg(uint8_t gi_index, const GI::TypeInfo, + const GI::ArgInfo, GjsArgumentFlags); + void build_normal_inout_arg(uint8_t gi_index, const GI::TypeInfo, + const GI::ArgInfo, GjsArgumentFlags); + + // GITypeInfo is not available for instance parameters (see + // https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/334) but + // for other parameters, this function additionally takes a GITypeInfo. template - void build_interface_in_arg(uint8_t gi_index, GITypeInfo*, GIBaseInfo*, - GITransfer, const char* name, GjsArgumentFlags); - - template - constexpr T* set_argument(uint8_t index, const char* name, GITypeInfo*, - GITransfer, GjsArgumentFlags flags, - Args&&... args); + void build_interface_in_arg(const Argument::Init&, + const GI::BaseInfo interface_info); - template - constexpr T* set_argument(uint8_t index, const char* name, GITransfer, - GjsArgumentFlags flags, Args&&... args); + template + constexpr void set_argument(T* arg, const Argument::Init&); - template - constexpr T* set_argument_auto(Args&&... args); + void set_array_argument(const GI::CallableInfo, uint8_t gi_index, + const GI::TypeInfo, GIDirection, const GI::ArgInfo, + GjsArgumentFlags, unsigned length_pos); - template - constexpr T* set_argument_auto(Tuple&& tuple, Args&&... args); + void set_array_return(const GI::CallableInfo, const GI::TypeInfo, + GjsArgumentFlags, unsigned length_pos); - template - void set_array_argument(GICallableInfo* callable, uint8_t gi_index, - GITypeInfo*, GIDirection, GIArgInfo*, - GjsArgumentFlags flags, int length_pos); + void init_out_array_length_argument(const GI::ArgInfo, GjsArgumentFlags, + unsigned length_pos); template - constexpr T* set_return(GITypeInfo*, GITransfer, GjsArgumentFlags); + constexpr void set_return(T* arg, GITransfer, GjsArgumentFlags); template - constexpr T* set_instance(GITransfer, - GjsArgumentFlags flags = GjsArgumentFlags::NONE); + constexpr void set_instance( + T* arg, GITransfer, GjsArgumentFlags flags = GjsArgumentFlags::NONE); - constexpr void set_skip_all(uint8_t index, const char* name = nullptr); + void set_skip_all(uint8_t index, const char* name = nullptr); template constexpr uint8_t arg_index(uint8_t index @@ -226,22 +265,22 @@ struct ArgsCache { return arg_get(index).get(); } - constexpr Argument* instance() const { + constexpr mozilla::Maybe instance() const { if (!m_is_method) - return nullptr; + return {}; - return arg_get().get(); + return mozilla::Some(arg_get().get()); } - constexpr Argument* return_value() const { + constexpr mozilla::Maybe return_value() const { if (!m_has_return) - return nullptr; + return {}; - return arg_get().get(); + return mozilla::Some(arg_get().get()); } private: - GjsAutoCppPointer m_args; + AutoCppPointer 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 f6e6f375a..cf212ba5e 100644 --- a/gi/arg-inl.h +++ b/gi/arg-inl.h @@ -7,17 +7,21 @@ #include #include +#include // for memset #include // for nullptr_t #include #include // for to_string #include -#include +#include #include // for GType #include // for gboolean + +#include // for Handle #include // for HandleValue +#include "gi/arg-types-inl.h" #include "gi/js-value-inl.h" #include "gi/utils-inl.h" #include "cjs/macros.h" @@ -45,62 +49,12 @@ template return (arg->*member); } -/* The tag is needed to disambiguate types such as gboolean and GType - * which are in fact typedef's of other generic types. - * Setting a tag for a type allows to perform proper specialization. */ -template +template [[nodiscard]] constexpr inline decltype(auto) gjs_arg_member(GIArgument* arg) { - if constexpr (TAG == GI_TYPE_TAG_VOID) { - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_boolean>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_int8>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_uint8>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_int16>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_uint16>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_int32>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_uint32>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_int64>(arg); - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_uint64>(arg); - - // gunichar is stored in v_uint32 - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_uint32>(arg); - - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_float>(arg); - - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_double>(arg); - - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_string>(arg); - - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_pointer>(arg); - - if constexpr (std::is_same_v) - return gjs_arg_member<&GIArgument::v_pointer>(arg); - - if constexpr (std::is_pointer()) { - using NonconstPtrT = std::add_pointer_t< - std::remove_const_t>>; - return reinterpret_cast( - gjs_arg_member<&GIArgument::v_pointer>(arg)); - } - } - - if constexpr (TAG == GI_TYPE_TAG_BOOLEAN && std::is_same_v) + if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_boolean>(arg); - if constexpr (TAG == GI_TYPE_TAG_GTYPE && std::is_same_v) { + if constexpr (std::is_same_v) { // GType is defined differently on 32-bit vs. 64-bit architectures. if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_size>(arg); @@ -108,46 +62,93 @@ template return gjs_arg_member<&GIArgument::v_ulong>(arg); } - if constexpr (TAG == GI_TYPE_TAG_INTERFACE && std::is_integral_v) { - if constexpr (std::is_signed_v) - return gjs_arg_member<&GIArgument::v_int>(arg); - else - return gjs_arg_member<&GIArgument::v_uint>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_long>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_ulong>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_int>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_boolean>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_int8>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint8>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_int16>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint16>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_int32>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint32>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_int64>(arg); + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint64>(arg); + + // gunichar is stored in v_uint32 + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_uint32>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_float>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_double>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_string>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_pointer>(arg); + + if constexpr (std::is_same_v) + return gjs_arg_member<&GIArgument::v_pointer>(arg); + + if constexpr (std::is_pointer()) { + using NonconstPtrT = + std::add_pointer_t>>; + return reinterpret_cast( + gjs_arg_member<&GIArgument::v_pointer>(arg)); } } -typedef enum { - GJS_TYPE_TAG_LONG = 0, -} ExtraTag; +template >>> +constexpr inline void gjs_arg_set(GIArgument* arg, Gjs::Tag::RealT v) { + if constexpr (std::is_same_v || + std::is_same_v) + v = !!v; -template -[[nodiscard]] constexpr inline decltype(auto) gjs_arg_member(GIArgument* arg) { - if constexpr (TAG == GJS_TYPE_TAG_LONG && - std::is_same_v) // NOLINT(runtime/int) - return gjs_arg_member<&GIArgument::v_long>(arg); - else if constexpr (TAG == GJS_TYPE_TAG_LONG && - std::is_same_v) // NOLINT(runtime/int) - return gjs_arg_member<&GIArgument::v_ulong>(arg); + gjs_arg_member(arg) = v; } -template +// Specialization for types where TAG and RealT are the same type, to allow +// inferring template parameter +template , T> && + std::is_arithmetic_v>> constexpr inline void gjs_arg_set(GIArgument* arg, T v) { - if constexpr (std::is_pointer_v) { - using NonconstPtrT = - std::add_pointer_t>>; - gjs_arg_member(arg) = const_cast(v); - } else { - if constexpr (std::is_same_v || (std::is_same_v && - TAG == GI_TYPE_TAG_BOOLEAN)) - v = !!v; - - gjs_arg_member(arg) = v; - } + gjs_arg_set(arg, v); } -template -constexpr inline void gjs_arg_set(GIArgument* arg, T v) { - gjs_arg_member(arg) = v; +// Specialization for non-function pointers, so that you don't have to repeat +// the pointer type explicitly for type deduction, and that takes care of +// GIArgument not having constness +template >> +constexpr inline void gjs_arg_set(GIArgument* arg, T* v) { + using NonconstPtrT = std::add_pointer_t>; + gjs_arg_member(arg) = const_cast(v); +} + +// Overload for nullptr since it's not handled by TAG* +constexpr inline void gjs_arg_set(GIArgument* arg, std::nullptr_t) { + gjs_arg_member(arg) = nullptr; } // Store function pointers as void*. It is a requirement of GLib that your @@ -157,55 +158,58 @@ constexpr inline void gjs_arg_set(GIArgument* arg, ReturnT (*v)(Args...)) { gjs_arg_member(arg) = reinterpret_cast(v); } -template -constexpr inline std::enable_if_t> gjs_arg_set( - GIArgument* arg, void* v) { - gjs_arg_set(arg, gjs_pointer_to_int(v)); +// Specifying an integer-type tag and passing a void pointer, extracts a stuffed +// integer out of the pointer; otherwise just store the pointer in v_pointer +template +constexpr inline void gjs_arg_set(GIArgument* arg, void* v) { + using T = Gjs::Tag::RealT; + if constexpr (std::is_integral_v) + gjs_arg_set(arg, gjs_pointer_to_int(v)); + else + gjs_arg_member(arg) = v; } -template -[[nodiscard]] constexpr inline T gjs_arg_get(GIArgument* arg) { - if constexpr (std::is_same_v || - (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN)) - return T(!!gjs_arg_member(arg)); - - return gjs_arg_member(arg); -} +template +[[nodiscard]] constexpr inline Gjs::Tag::RealT gjs_arg_get( + GIArgument* arg) { + if constexpr (std::is_same_v || + std::is_same_v) + return Gjs::Tag::RealT(!!gjs_arg_member(arg)); -template -[[nodiscard]] constexpr inline T gjs_arg_get(GIArgument* arg) { - return gjs_arg_member(arg); + return gjs_arg_member(arg); } -template +template [[nodiscard]] constexpr inline void* gjs_arg_get_as_pointer(GIArgument* arg) { - return gjs_int_to_pointer(gjs_arg_get(arg)); + return gjs_int_to_pointer(gjs_arg_get(arg)); } -template constexpr inline void gjs_arg_unset(GIArgument* arg) { - if constexpr (std::is_pointer_v) - gjs_arg_set(arg, nullptr); - else - gjs_arg_set(arg, static_cast(0)); + // Clear all bits of the out C value. No one member is guaranteed to span + // the whole union on all architectures, so use memset() instead of + // gjs_arg_set(arg, 0) for some type T. + memset(arg, 0, sizeof(GIArgument)); } -template -[[nodiscard]] constexpr inline T gjs_arg_steal(GIArgument* arg) { - auto val = gjs_arg_get(arg); - gjs_arg_unset(arg); +template +[[nodiscard]] constexpr inline Gjs::Tag::RealT gjs_arg_steal( + GIArgument* arg) { + auto val = gjs_arg_get(arg); + gjs_arg_unset(arg); return val; } // Implementation to store rounded (u)int64_t numbers into double -template +template [[nodiscard]] inline constexpr std::enable_if_t< - std::is_integral_v && (std::numeric_limits::max() > - std::numeric_limits::max()), + std::is_integral_v> && + (std::numeric_limits>::max() > + std::numeric_limits::max()), double> gjs_arg_get_maybe_rounded(GIArgument* arg) { - BigT val = gjs_arg_get(arg); + using BigT = Gjs::Tag::RealT; + BigT val = gjs_arg_get(arg); if (val < Gjs::min_safe_big_number() || val > Gjs::max_safe_big_number()) { @@ -218,22 +222,24 @@ gjs_arg_get_maybe_rounded(GIArgument* arg) { return static_cast(val); } -template +template GJS_JSAPI_RETURN_CONVENTION inline bool gjs_arg_set_from_js_value( - JSContext* cx, const JS::HandleValue& value, GIArgument* arg, - bool* out_of_range) { - if constexpr (Gjs::type_has_js_getter()) - return Gjs::js_value_to_c(cx, value, &gjs_arg_member(arg)); + JSContext* cx, JS::HandleValue value, GIArgument* arg, bool* out_of_range) { + if constexpr (Gjs::type_has_js_getter()) + return Gjs::js_value_to_c(cx, value, &gjs_arg_member(arg)); - Gjs::JsValueHolder::Relaxed val{}; + Gjs::Tag::JSValuePackT val{}; - if (!Gjs::js_value_to_c_checked(cx, value, &val, out_of_range)) + using T = Gjs::Tag::RealT; + using HolderTag = Gjs::Tag::JSValuePackTag; + if (!Gjs::js_value_to_c_checked(cx, value, &val, + out_of_range)) return false; if (*out_of_range) return false; - gjs_arg_set(arg, val); + gjs_arg_set(arg, val); return true; } @@ -263,3 +269,12 @@ GJS_JSAPI_RETURN_CONVENTION inline bool gjs_arg_set_from_js_value( g_assert_not_reached(); } } + +namespace Gjs { + +[[nodiscard]] static inline bool basic_type_needs_release(GITypeTag tag) { + g_assert(GI_TYPE_TAG_IS_BASIC(tag)); + return tag == GI_TYPE_TAG_FILENAME || tag == GI_TYPE_TAG_UTF8; +} + +} // namespace Gjs diff --git a/gi/arg-types-inl.h b/gi/arg-types-inl.h index c3975c41c..0bda551b8 100644 --- a/gi/arg-types-inl.h +++ b/gi/arg-types-inl.h @@ -8,108 +8,289 @@ #include -#include -#include // for GType +#include +#include + +#include +#include // for GValue #include // for gboolean namespace Gjs { -template -constexpr inline const char* static_type_name() = delete; +template +struct TypeWrapper { + constexpr TypeWrapper() : m_value(0) {} + explicit constexpr TypeWrapper(T v) : m_value(v) {} + constexpr operator T() const { return m_value; } + constexpr operator T() { return m_value; } -template <> -constexpr inline const char* static_type_name() { - return "bool"; + private: + T m_value; +}; + +template +constexpr bool comparable_types() { + return std::is_arithmetic_v == std::is_arithmetic_v && + std::is_integral_v == std::is_integral_v && + std::is_signed_v == std::is_signed_v; } -template <> -constexpr inline const char* static_type_name() { - return "int8"; +template +constexpr bool type_fits() { + if constexpr (comparable_types()) { + return (std::numeric_limits::max() <= + std::numeric_limits::max() && + std::numeric_limits::lowest() >= + std::numeric_limits::lowest()); + } + + return false; } +// These tags are used to disambiguate types such as gboolean and GType which +// are in fact typedefs of other generic types. Using the tag instead of the +// type directly allows performing proper specialization. See also arg-inl.h. +namespace Tag { +struct GBoolean {}; +struct GType {}; +struct Long {}; +struct UnsignedLong {}; +struct Enum {}; +struct UnsignedEnum {}; +} // namespace Tag + +template +struct MarshallingInfo {}; + template <> -constexpr inline const char* static_type_name() { - return "uint8"; -} +struct MarshallingInfo { + using real_type = bool; + using containing_tag = bool; + using jsvalue_pack_type = int32_t; + static constexpr const char* name = "bool"; +}; template <> -constexpr inline const char* static_type_name() { - return "int16"; -} +struct MarshallingInfo { + using real_type = int8_t; + using containing_tag = int32_t; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT8; + static constexpr const char* name = "int8"; +}; template <> -constexpr inline const char* static_type_name() { - return "uint16"; -} +struct MarshallingInfo { + using real_type = uint8_t; + using containing_tag = uint32_t; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT8; + static constexpr const char* name = "uint8"; +}; template <> -constexpr inline const char* static_type_name() { - return "int32"; -} +struct MarshallingInfo { + using real_type = int16_t; + using containing_tag = int32_t; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT16; + static constexpr const char* name = "int16"; +}; template <> -constexpr inline const char* static_type_name() { - return "uint32"; -} +struct MarshallingInfo { + using real_type = uint16_t; + using containing_tag = uint32_t; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT16; + static constexpr const char* name = "uint16"; +}; template <> -constexpr inline const char* static_type_name() { - return "char32_t"; -} +struct MarshallingInfo { + using real_type = int32_t; + using containing_tag = int32_t; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT32; + static constexpr const char* name = "int32"; +}; template <> -constexpr inline const char* static_type_name() { - return "int64"; -} +struct MarshallingInfo { + using real_type = uint32_t; + using containing_tag = uint32_t; + using jsvalue_pack_type = double; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT32; + static constexpr const char* name = "uint32"; +}; template <> -constexpr inline const char* static_type_name() { - return "uint64"; -} +struct MarshallingInfo { + using real_type = char32_t; + using containing_tag = char32_t; + using jsvalue_pack_type = double; + static constexpr const char* name = "char32"; +}; template <> -constexpr inline const char* static_type_name() { - return "float"; -} +struct MarshallingInfo { + using real_type = int64_t; + using containing_tag = int64_t; + using jsvalue_pack_type = TypeWrapper; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT64; + static constexpr const char* name = "int64"; +}; template <> -constexpr inline const char* static_type_name() { - return "double"; -} +struct MarshallingInfo { + using real_type = uint64_t; + using containing_tag = uint64_t; + using jsvalue_pack_type = TypeWrapper; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT64; + static constexpr const char* name = "uint64"; +}; template <> -constexpr inline const char* static_type_name() { - return "pointer"; -} +struct MarshallingInfo { + using real_type = float; + using containing_tag = double; + using jsvalue_pack_type = double; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_FLOAT; + static constexpr const char* name = "float"; +}; template <> -constexpr inline const char* static_type_name() { - return "GType"; -} +struct MarshallingInfo { + using real_type = double; + using containing_tag = double; + using jsvalue_pack_type = double; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_DOUBLE; + static constexpr const char* name = "double"; +}; + +template +struct MarshallingInfo { + using real_type = T*; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_VOID; + static constexpr const char* name = "pointer"; +}; template <> -constexpr inline const char* static_type_name() { - return "boolean"; -} +struct MarshallingInfo { + using real_type = GType; + using containing_tag = Tag::GType; + using jsvalue_pack_type = TypeWrapper; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_GTYPE; + static constexpr const char* name = "GType"; +}; template <> -constexpr inline const char* static_type_name() { - return "GValue"; -} +struct MarshallingInfo { + using real_type = gboolean; + using containing_tag = Tag::GBoolean; + using jsvalue_pack_type = int32_t; + static constexpr GITypeTag gi_tag = GI_TYPE_TAG_BOOLEAN; + static constexpr const char* name = "boolean"; +}; template <> -inline const char* static_type_name() { - return "string"; -} +struct MarshallingInfo { + using real_type = GValue*; + static constexpr const char* name = "GValue"; +}; template <> -inline const char* static_type_name() { - return "constant string"; -} +struct MarshallingInfo { + using real_type = GValue; + using containing_tag = GValue; + static constexpr const char* name = "flat GValue"; +}; + +template <> +struct MarshallingInfo { + using real_type = char*; + using containing_tag = char*; + using jsvalue_pack_type = char*; + static constexpr const char* name = "string"; +}; + +template <> +struct MarshallingInfo { + using real_type = const char*; + static constexpr const char* name = "constant string"; +}; + +template <> +struct MarshallingInfo { + static constexpr bool is_32 = + type_fits(); // NOLINT(runtime/int) + using real_type = long; // NOLINT(runtime/int) + using containing_tag = std::conditional_t; + using jsvalue_pack_type = + std::conditional_t>; + static constexpr const char* name = "long"; +}; + +template <> +struct MarshallingInfo { + static constexpr bool is_32 = + type_fits(); // NOLINT(runtime/int) + using real_type = unsigned long; // NOLINT(runtime/int) + using containing_tag = std::conditional_t; + using jsvalue_pack_type = + std::conditional_t>; + static constexpr const char* name = "unsigned long"; +}; template <> -inline const char* static_type_name() { - return "void"; +struct MarshallingInfo { + using real_type = int; + using containing_tag = int32_t; + using jsvalue_pack_type = int32_t; +}; + +template <> +struct MarshallingInfo { + using real_type = unsigned int; + using containing_tag = uint32_t; + using jsvalue_pack_type = double; +}; + +template <> +struct MarshallingInfo { + using real_type = void; +}; + +namespace Tag { +template +using RealT = typename MarshallingInfo::real_type; + +// There are two ways you can unpack a C value from a JSValue. +// The containing type is the most appropriate C type that can contain the +// unpacked value. Implicit conversion may be performed and the value may need +// to be checked to make sure it is in range. +// The JSValue pack type, on the other hand, is the C type that is exactly +// equivalent to how JSValue stores the value, so no implicit conversion is +// performed unless the JSValue contains a pointer to a GC-thing, like BigInt. +template +using JSValueContainingT = RealT::containing_tag>; + +template +using JSValueContainingTag = typename MarshallingInfo::containing_tag; + +template +using JSValuePackT = typename MarshallingInfo::jsvalue_pack_type; + +template +using JSValuePackTag = std::conditional_t< + std::is_same_v, TypeWrapper>, int64_t, + std::conditional_t, TypeWrapper>, + uint64_t, JSValuePackT>>; +} // namespace Tag + +template +constexpr inline const char* static_type_name() { + return MarshallingInfo::name; } } // namespace Gjs diff --git a/gi/arg.cpp b/gi/arg.cpp index 82a74302a..fb4e05654 100644 --- a/gi/arg.cpp +++ b/gi/arg.cpp @@ -9,9 +9,14 @@ #include #include // for strcmp, strlen, memcpy +#include // for none_of +#include +#include // for mem_fn #include +#include // for move +#include -#include +#include #include #include @@ -32,6 +37,8 @@ #include #include #include // for InformalValueTypeName, IdVector +#include +#include #include #include "gi/arg-inl.h" @@ -43,6 +50,7 @@ #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" +#include "gi/info.h" #include "gi/interface.h" #include "gi/js-value-inl.h" #include "gi/object.h" @@ -51,25 +59,39 @@ #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/enum-utils.h" -#include "cjs/macros.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" +#include "cjs/macros.h" #include "util/log.h" -#include "util/misc.h" -GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( - 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); +using mozilla::Maybe, mozilla::Nothing, mozilla::Some; + +// This file contains marshalling functions for GIArgument. It is divided into +// three sections, with some helper functions coming first. +// 1. "To" marshallers - JS::Value or other JS data structure to GIArgument or +// pointer, used for in arguments +// 2. "From" marshallers - GIArgument or pointer to JS::Value or other JS data +// structure, used for out arguments and return values +// 3. "Release" marshallers - used when cleaning up GIArguments after a C +// function call + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_g_arg_release_internal(JSContext*, GITransfer, + const GI::TypeInfo, GITypeTag, + GjsArgumentType, GjsArgumentFlags, + GIArgument*); +static void throw_invalid_argument(JSContext*, JS::HandleValue, + const GI::TypeInfo, const char*, + GjsArgumentType); bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value) { /* Do proper value check for flags with GType's */ if (gtype != G_TYPE_NONE) { - GjsAutoTypeClass gflags_class(gtype); + Gjs::AutoTypeClass gflags_class{gtype}; uint32_t tmpval = static_cast(value); /* check all bits are valid bits for the flag and is a 32 bit flag*/ @@ -84,68 +106,27 @@ bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value) { } GJS_JSAPI_RETURN_CONVENTION -static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, +static bool _gjs_enum_value_is_valid(JSContext* cx, const GI::EnumInfo info, int64_t value) { - bool found; - int n_values; - int i; - - n_values = g_enum_info_get_n_values(enum_info); - found = false; - - for (i = 0; i < n_values; ++i) { - GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); - int64_t enum_value = g_value_info_get_value(value_info); - - if (enum_value == value) { - found = true; - break; - } - } - - if (!found) { + GI::EnumInfo::ValuesIterator values = info.values(); + if (std::none_of(values.begin(), values.end(), + [value](GI::AutoValueInfo info) { + return info.value() == value; + })) { gjs_throw(cx, "%" PRId64 " is not a valid value for enumeration %s", - value, g_base_info_get_name(enum_info)); - } - - return found; -} - -[[nodiscard]] static bool _gjs_enum_uses_signed_type(GIEnumInfo* enum_info) { - switch (g_enum_info_get_storage_type(enum_info)) { - case GI_TYPE_TAG_INT8: - case GI_TYPE_TAG_INT16: - case GI_TYPE_TAG_INT32: - case GI_TYPE_TAG_INT64: - return true; - default: - return false; + value, info.name()); + return false; } -} - -// This is hacky - g_function_info_invoke() and g_field_info_get/set_field() -// expect the enum value in gjs_arg_member(arg) and depend on all flags and -// enumerations being passed on the stack in a 32-bit field. See FIXME comment -// in g_field_info_get_field(). The same assumption of enums cast to 32-bit -// signed integers is found in g_value_set_enum()/g_value_set_flags(). -[[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value) { - if (_gjs_enum_uses_signed_type (enum_info)) - return int64_t(int_value); - else - return int64_t(uint32_t(int_value)); -} -/* Here for symmetry, but result is the same for the two cases */ -[[nodiscard]] static int _gjs_enum_to_int(int64_t value) { - return static_cast(value); + return true; } /* Check if an argument of the given needs to be released if we created it * from a JS value to pass it into a function and aren't transferring ownership. */ -[[nodiscard]] static bool type_needs_release(GITypeInfo* type_info, - GITypeTag type_tag) { - switch (type_tag) { +[[nodiscard]] +static bool type_needs_release(const GI::TypeInfo& type_info, GITypeTag tag) { + switch (tag) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: @@ -156,34 +137,17 @@ static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, return true; case GI_TYPE_TAG_INTERFACE: { - GType gtype; - - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - g_assert(interface_info != nullptr); - - switch (interface_info.type()) { - case GI_INFO_TYPE_STRUCT: - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_UNION: - case GI_INFO_TYPE_BOXED: - // These are subtypes of GIRegisteredTypeInfo for which the - // cast is safe - gtype = g_registered_type_info_get_g_type(interface_info); - break; - default: - gtype = G_TYPE_NONE; - } + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (auto reg_info = + interface_info.as()) { + GType gtype = reg_info->gtype(); - if (g_type_is_a(gtype, G_TYPE_CLOSURE)) - return true; - else if (g_type_is_a(gtype, G_TYPE_VALUE)) - return true; - else - return false; + if (g_type_is_a(gtype, G_TYPE_CLOSURE)) + return true; + else if (g_type_is_a(gtype, G_TYPE_VALUE)) + return true; + } + return false; } default: @@ -198,9 +162,10 @@ static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, /* Check if an argument of the given needs to be released if we obtained it * from out argument (or the return value), and we're transferring ownership */ -[[nodiscard]] static bool type_needs_out_release(GITypeInfo* type_info, - GITypeTag type_tag) { - switch (type_tag) { +[[nodiscard]] +static bool type_needs_out_release(const GI::TypeInfo& type_info, + GITypeTag tag) { + switch (tag) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: @@ -211,24 +176,13 @@ static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, return true; case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - - switch (interface_info.type()) { - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - return false; - - case GI_INFO_TYPE_STRUCT: - case GI_INFO_TYPE_UNION: - return g_type_info_is_pointer(type_info); - - case GI_INFO_TYPE_OBJECT: - return true; + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (interface_info.is_object()) + return true; + if (interface_info.is_struct() || interface_info.is_union()) + return type_info.is_pointer(); - default: - return false; - } + return false; } default: @@ -236,9 +190,13 @@ static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, } } +///// "TO" MARSHALLERS ///////////////////////////////////////////////////////// +// These marshaller functions are responsible for converting JS values to the +// required GIArgument type, for the in parameters of a C function call. + template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( - JSContext* cx, const JS::HandleValue& value, GITypeInfo* type_info, + JSContext* cx, JS::HandleValue value, const GI::TypeInfo type_info, GITransfer transfer, const char* arg_name, GjsArgumentType arg_type, T** list_p) { static_assert(std::is_same_v || std::is_same_v); @@ -261,11 +219,14 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( return false; } - GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); - g_assert(param_info); + GI::AutoTypeInfo element_type{type_info.element_type()}; + + GITypeTag element_tag = element_type.tag(); + g_assert(!GI_TYPE_TAG_IS_BASIC(element_tag) && + "use basic_array_to_linked_list() instead"); if (transfer == GI_TRANSFER_CONTAINER) { - if (type_needs_release (param_info, g_type_info_get_tag(param_info))) { + if (type_needs_release(element_type, element_tag)) { /* FIXME: to make this work, we'd have to keep a list of temporary * GIArguments for the function call so we could free them after * the surrounding container had been freed by the callee. @@ -293,14 +254,13 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( * Always say they can't for now. */ GIArgument elem_arg; - if (!gjs_value_to_gi_argument(cx, elem, param_info, + if (!gjs_value_to_gi_argument(cx, elem, element_type, GJS_ARGUMENT_LIST_ELEMENT, transfer, &elem_arg)) { return false; } - void* hash_pointer = - g_type_info_hash_pointer_from_argument(param_info, &elem_arg); + void* hash_pointer = element_type.hash_pointer_from_argument(&elem_arg); if constexpr (std::is_same_v) list = g_list_prepend(list, hash_pointer); @@ -328,19 +288,21 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( return g_hash_table_new(NULL, NULL); } -template -GJS_JSAPI_RETURN_CONVENTION static bool hashtable_int_key( - JSContext* cx, const JS::HandleValue& value, void** pointer_out) { +template +GJS_JSAPI_RETURN_CONVENTION static bool hashtable_int_key(JSContext* cx, + JS::HandleValue value, + void** pointer_out) { + using IntType = Gjs::Tag::RealT; static_assert(std::is_integral_v, "Need an integer"); bool out_of_range = false; - Gjs::JsValueHolder::Strict i; + Gjs::Tag::JSValueContainingT i; if (!Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range)) return false; if (out_of_range) { gjs_throw(cx, "value is out of range for hash table key of type %s", - Gjs::static_type_name()); + Gjs::static_type_name()); } *pointer_out = gjs_int_to_pointer(i); @@ -361,7 +323,7 @@ static bool value_to_ghashtable_key(JSContext* cx, JS::HandleValue value, gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting JS::Value to GHashTable key %s", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_BOOLEAN: @@ -411,7 +373,7 @@ static bool value_to_ghashtable_key(JSContext* cx, JS::HandleValue value, break; case GI_TYPE_TAG_FILENAME: { - GjsAutoChar cstr; + Gjs::AutoChar cstr; JS::RootedValue str_val(cx, value); if (!str_val.isString()) { JS::RootedString str(cx, JS::ToString(cx, str_val)); @@ -441,9 +403,11 @@ static bool value_to_ghashtable_key(JSContext* cx, JS::HandleValue value, case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: - /* FIXME: The above four could be supported, but are currently not. The ones - * below cannot be key types in a regular JS object; we would need to allow - * marshalling Map objects into GHashTables to support those. */ + // FIXME: The above four could be supported, but are currently not. The ones + // below cannot be key types in a regular JS object; we would need to allow + // marshalling Map objects into GHashTables to support those, as well as + // refactoring this function to take GITypeInfo* and splitting out the + // marshalling for basic types into a different function. case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_ERROR: @@ -457,42 +421,49 @@ static bool value_to_ghashtable_key(JSContext* cx, JS::HandleValue value, default: g_warning("Unhandled type %s for GHashTable key conversion", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); unsupported = true; break; } if (G_UNLIKELY(unsupported)) { gjs_throw(cx, "Type %s not supported for hash table keys", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); return false; } return true; } -template -[[nodiscard]] static T* heap_value_new_from_arg(GIArgument* val_arg) { - T* heap_val = g_new(T, 1); - *heap_val = gjs_arg_get(val_arg); +template +[[nodiscard]] static Gjs::Tag::RealT* heap_value_new_from_arg( + GIArgument* val_arg) { + auto* heap_val = g_new(Gjs::Tag::RealT, 1); + *heap_val = gjs_arg_get(val_arg); return heap_val; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, - GITypeInfo* type_info, GITransfer transfer, - GHashTable** hash_p) { + const GI::TypeInfo type_info, + GITransfer transfer, GHashTable** hash_p) { size_t id_ix, id_len; g_assert(props && "Property bag cannot be null"); - GjsAutoTypeInfo key_param_info = g_type_info_get_param_type(type_info, 0); - GjsAutoTypeInfo val_param_info = g_type_info_get_param_type(type_info, 1); + GI::AutoTypeInfo key_type{type_info.key_type()}; + GI::AutoTypeInfo value_type{type_info.value_type()}; + GITypeTag key_tag = key_type.tag(); + GITypeTag val_type = value_type.tag(); + + g_assert( + (!GI_TYPE_TAG_IS_BASIC(key_tag) || !GI_TYPE_TAG_IS_BASIC(val_type)) && + "use gjs_value_to_basic_ghash_gi_argument() instead"); if (transfer == GI_TRANSFER_CONTAINER) { - if (type_needs_release (key_param_info, g_type_info_get_tag(key_param_info)) || - type_needs_release (val_param_info, g_type_info_get_tag(val_param_info))) { + if (type_needs_release(key_type, key_tag) || + type_needs_release(value_type, val_type)) { /* FIXME: to make this work, we'd have to keep a list of temporary * GIArguments for the function call so we could free them after * the surrounding container had been freed by the callee. @@ -509,9 +480,8 @@ static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, if (!JS_Enumerate(context, props, &ids)) return false; - GITypeTag key_tag = g_type_info_get_tag(key_param_info); - GjsAutoPointer result = - create_hash_table_for_key_type(key_tag); + Gjs::AutoPointer result{ + create_hash_table_for_key_type(key_tag)}; JS::RootedValue key_js(context), val_js(context); JS::RootedId cur_id(context); @@ -525,12 +495,11 @@ static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, !value_to_ghashtable_key(context, key_js, key_tag, &key_ptr) || !JS_GetPropertyById(context, props, cur_id, &val_js) || // Type check and convert value to a C type - !gjs_value_to_gi_argument(context, val_js, val_param_info, nullptr, + !gjs_value_to_gi_argument(context, val_js, value_type, nullptr, GJS_ARGUMENT_HASH_ELEMENT, transfer, GjsArgumentFlags::MAY_BE_NULL, &val_arg)) return false; - GITypeTag val_type = g_type_info_get_tag(val_param_info); /* Use heap-allocated values for types that don't fit in a pointer */ if (val_type == GI_TYPE_TAG_INT64) { val_ptr = heap_value_new_from_arg(&val_arg); @@ -542,8 +511,7 @@ static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, val_ptr = heap_value_new_from_arg(&val_arg); } else { // Other types are simply stuffed inside the pointer - val_ptr = g_type_info_hash_pointer_from_argument(val_param_info, - &val_arg); + val_ptr = value_type.hash_pointer_from_argument(&val_arg); } #if __GNUC__ >= 8 // clang-format off @@ -572,28 +540,30 @@ template return array; } -template +template GJS_JSAPI_RETURN_CONVENTION static bool js_value_to_c_strict( - JSContext* cx, const JS::HandleValue& value, T* out) { - using ValueHolderT = Gjs::JsValueHolder::Strict; - if constexpr (Gjs::type_has_js_getter()) + JSContext* cx, JS::HandleValue value, Gjs::Tag::RealT* out) { + if constexpr (Gjs::type_has_js_getter()) return Gjs::js_value_to_c(cx, value, out); - ValueHolderT v; + Gjs::Tag::JSValueContainingT v; bool ret = Gjs::js_value_to_c(cx, value, &v); *out = v; return ret; } -template +template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_auto_array( JSContext* cx, JS::Value array_value, size_t length, void** arr_p) { + using RealT = Gjs::Tag::RealT; + JS::RootedObject array(cx, array_value.toObjectOrNull()); JS::RootedValue elem(cx); // Add one so we're always zero terminated - GjsSmartPointer result = array_allocate(length + 1); + Gjs::SmartPointer result{array_allocate(length + 1)}; for (size_t i = 0; i < length; ++i) { elem = JS::UndefinedValue(); @@ -603,9 +573,9 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_auto_array( return false; } - if (!js_value_to_c_strict(cx, elem, &result[i])) { + if (!js_value_to_c_strict(cx, elem, &result[i])) { gjs_throw(cx, "Invalid element in %s array", - Gjs::static_type_name()); + Gjs::static_type_name()); return false; } } @@ -615,39 +585,6 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_auto_array( return true; } -bool -gjs_array_from_strv(JSContext *context, - JS::MutableHandleValue value_p, - const char **strv) -{ - guint i; - JS::RootedValueVector elems(context); - - /* We treat a NULL strv as an empty array, since this function should always - * set an array value when returning true. - * Another alternative would be to set value_p to JS::NullValue, but clients - * would need to always check for both an empty array and null if that was - * the case. - */ - for (i = 0; strv != NULL && strv[i] != NULL; i++) { - if (!elems.growBy(1)) { - JS_ReportOutOfMemory(context); - return false; - } - - if (!gjs_string_from_utf8(context, strv[i], elems[i])) - return false; - } - - JSObject* obj = JS::NewArrayObject(context, elems); - if (!obj) - return false; - - value_p.setObject(*obj); - - return true; -} - bool gjs_array_to_strv(JSContext *context, JS::Value array_value, @@ -670,7 +607,7 @@ static bool gjs_string_to_intarray(JSContext* context, JS::HandleString str, if (!gjs_string_to_utf8_n(context, str, &result, length)) return false; - *arr_p = _gjs_memdup2(result.get(), *length); + *arr_p = Gjs::js_chars_to_glib(std::move(result)).release(); return true; } @@ -693,30 +630,58 @@ static bool gjs_string_to_intarray(JSContext* context, JS::HandleString str, default: /* can't convert a string to this type */ gjs_throw(context, "Cannot convert string to array of '%s'", - g_type_tag_to_string(element_type)); + gi_type_tag_to_string(element_type)); return false; } } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_array_to_ptrarray(JSContext *context, - JS::Value array_value, - unsigned int length, - GITransfer transfer, - GITypeInfo *param_info, - void **arr_p) -{ +static bool array_to_basic_c_array(JSContext* cx, JS::HandleValue v_array, + size_t length, GITypeTag element_tag, + void** array_out) { + // Always one extra element, to cater for null terminated arrays + Gjs::AutoPointer array{array_allocate(length + 1)}; + + JS::RootedObject array_obj{cx, &v_array.toObject()}; + JS::RootedValue elem{cx}; + for (size_t ix = 0; ix < length; ix++) { + GIArgument arg; + gjs_arg_unset(&arg); + + elem.setUndefined(); + if (!JS_GetElement(cx, array_obj, ix, &elem)) { + gjs_throw(cx, "Missing array element %zu", ix); + return false; + } + + if (!gjs_value_to_basic_gi_argument(cx, elem, element_tag, &arg, + nullptr, GJS_ARGUMENT_ARRAY_ELEMENT, + GjsArgumentFlags::NONE)) { + gjs_throw(cx, "Invalid element in array"); + return false; + } + + array[ix] = gjs_arg_get(&arg); + } + + *array_out = array.release(); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_array_to_ptrarray(JSContext* context, JS::Value array_value, + unsigned length, GITransfer transfer, + const GI::TypeInfo param_info, void** arr_p) { unsigned int i; JS::RootedObject array_obj(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); /* Always one extra element, to cater for null terminated arrays */ - GjsAutoPointer array = array_allocate(length + 1); + Gjs::AutoPointer array{array_allocate(length + 1)}; for (i = 0; i < length; i++) { GIArgument arg; - gjs_arg_unset(&arg); + gjs_arg_unset(&arg); elem = JS::UndefinedValue(); if (!JS_GetElement(context, array_obj, i, &elem)) { @@ -743,12 +708,13 @@ gjs_array_to_ptrarray(JSContext *context, GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_flat_array(JSContext* cx, JS::HandleValue array_value, - unsigned length, GITypeInfo* param_info, + unsigned length, + const GI::TypeInfo 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, param_size * length); + Gjs::AutoPointer flat_array{g_new0(uint8_t, param_size * length)}; JS::RootedObject array(cx, &array_value.toObject()); JS::RootedValue elem(cx); @@ -774,85 +740,94 @@ static bool gjs_array_to_flat_array(JSContext* cx, JS::HandleValue array_value, return true; } -[[nodiscard]] static bool is_gvalue(GIBaseInfo* info) { - switch (g_base_info_get_type(info)) { - case GI_INFO_TYPE_STRUCT: - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_BOXED: { - GType gtype = g_registered_type_info_get_g_type(info); - return g_type_is_a(gtype, G_TYPE_VALUE); - } +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_array_to_basic_array(JSContext* cx, JS::HandleValue v_array, + size_t length, + GITypeTag element_storage_type, + void** array_out) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_storage_type)); - default: + switch (element_storage_type) { + case GI_TYPE_TAG_UTF8: + return gjs_array_to_strv(cx, v_array, length, array_out); + case GI_TYPE_TAG_FILENAME: + return array_to_basic_c_array(cx, v_array, length, + element_storage_type, array_out); + case GI_TYPE_TAG_BOOLEAN: + return gjs_array_to_auto_array( + cx, v_array, length, array_out); + case GI_TYPE_TAG_UNICHAR: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_UINT8: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_INT8: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_UINT16: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_INT16: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_UINT32: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_INT32: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_INT64: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_UINT64: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_FLOAT: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_DOUBLE: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_GTYPE: + return gjs_array_to_auto_array(cx, v_array, length, + array_out); + case GI_TYPE_TAG_VOID: + gjs_throw(cx, "Unhandled array element type %d", + element_storage_type); return false; + default: + g_assert_not_reached(); } } 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); + const GI::TypeInfo param_info, void** arr_p) { + GITypeTag element_type = param_info.storage_type(); - switch (element_type) { - case GI_TYPE_TAG_UTF8: - return gjs_array_to_strv (context, array_value, length, arr_p); - case GI_TYPE_TAG_BOOLEAN: - return gjs_array_to_auto_array( - context, array_value, length, arr_p); - case GI_TYPE_TAG_UNICHAR: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_UINT8: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_INT8: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_UINT16: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_INT16: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_UINT32: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_INT32: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_INT64: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_UINT64: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_FLOAT: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_DOUBLE: - return gjs_array_to_auto_array(context, array_value, length, - arr_p); - case GI_TYPE_TAG_GTYPE: - return gjs_array_to_auto_array( - context, array_value, length, arr_p); + if (GI_TYPE_TAG_IS_BASIC(element_type)) { + return gjs_array_to_basic_array(context, array_value, length, + element_type, arr_p); + } - /* Everything else is a pointer type */ + switch (element_type) { case GI_TYPE_TAG_INTERFACE: - if (!g_type_info_is_pointer(param_info)) { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(param_info); - if (is_gvalue(interface_info)) { + if (!param_info.is_pointer()) { + GI::AutoBaseInfo interface_info{param_info.interface()}; + if (auto reg_info = + interface_info.as(); + reg_info && reg_info->is_g_value()) { // Special case for GValue "flat arrays", this could also - // using the generic case, but if we do so we're leaking atm. + // 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); - + size_t element_size = + gjs_type_get_element_size(param_info.tag(), param_info); if (element_size) { return gjs_array_to_flat_array(context, array_value, length, param_info, element_size, arr_p); @@ -864,237 +839,304 @@ static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: - case GI_TYPE_TAG_FILENAME: return gjs_array_to_ptrarray(context, array_value, length, transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer, param_info, arr_p); - case GI_TYPE_TAG_VOID: default: - gjs_throw(context, - "Unhandled array element type %d", element_type); + // Basic types already handled in gjs_array_to_basic_array() + gjs_throw(context, "Unhandled array element type %d", element_type); return false; } } +static inline size_t basic_type_element_size(GITypeTag element_tag) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); + + switch (element_tag) { + case GI_TYPE_TAG_BOOLEAN: + return sizeof(gboolean); + case GI_TYPE_TAG_INT8: + return sizeof(int8_t); + case GI_TYPE_TAG_UINT8: + return sizeof(uint8_t); + case GI_TYPE_TAG_INT16: + return sizeof(int16_t); + case GI_TYPE_TAG_UINT16: + return sizeof(uint16_t); + case GI_TYPE_TAG_INT32: + return sizeof(int32_t); + case GI_TYPE_TAG_UINT32: + return sizeof(uint32_t); + case GI_TYPE_TAG_INT64: + return sizeof(int64_t); + case GI_TYPE_TAG_UINT64: + return sizeof(uint64_t); + case GI_TYPE_TAG_FLOAT: + return sizeof(float); + case GI_TYPE_TAG_DOUBLE: + return sizeof(double); + case GI_TYPE_TAG_GTYPE: + return sizeof(GType); + case GI_TYPE_TAG_UNICHAR: + return sizeof(char32_t); + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + return sizeof(char*); + default: + g_return_val_if_reached(0); + } +} + +static inline void set_arg_from_carray_element(GIArgument* arg, + GITypeTag element_tag, + void* value) { + switch (element_tag) { + case GI_TYPE_TAG_BOOLEAN: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_INT8: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_UINT8: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_INT16: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_UINT16: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_INT32: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_UINT32: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_INT64: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_UINT64: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_FLOAT: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_DOUBLE: + gjs_arg_set(arg, *static_cast(value)); + break; + case GI_TYPE_TAG_INTERFACE: + gjs_arg_set(arg, value); + break; + case GI_TYPE_TAG_VOID: + case GI_TYPE_TAG_GTYPE: + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + case GI_TYPE_TAG_GHASH: + case GI_TYPE_TAG_ERROR: + case GI_TYPE_TAG_UNICHAR: + // non interface tag support handled elsewhere + g_assert_not_reached(); + } +} + 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) + const GI::TypeInfo type_info) { + if (type_info.is_pointer() && element_type != GI_TYPE_TAG_ARRAY) return sizeof(void*); switch (element_type) { - case GI_TYPE_TAG_BOOLEAN: - return sizeof(gboolean); - case GI_TYPE_TAG_INT8: - return sizeof(int8_t); - case GI_TYPE_TAG_UINT8: - return sizeof(uint8_t); - case GI_TYPE_TAG_INT16: - return sizeof(int16_t); - case GI_TYPE_TAG_UINT16: - return sizeof(uint16_t); - case GI_TYPE_TAG_INT32: - return sizeof(int32_t); - case GI_TYPE_TAG_UINT32: - return sizeof(uint32_t); - case GI_TYPE_TAG_INT64: - return sizeof(int64_t); - case GI_TYPE_TAG_UINT64: - return sizeof(uint64_t); - case GI_TYPE_TAG_FLOAT: - return sizeof(float); - case GI_TYPE_TAG_DOUBLE: - return sizeof(double); - case GI_TYPE_TAG_GTYPE: - 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: return sizeof(GList); case GI_TYPE_TAG_ERROR: 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 (interface_info.type()) { - 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); - default: - return 0; - } + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (interface_info.is_enum_or_flags()) + return sizeof(unsigned); + if (auto struct_info = interface_info.as()) + return struct_info->size(); + if (auto union_info = interface_info.as()) + return union_info->size(); + 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) + if (type_info.array_type() == GI_ARRAY_TYPE_C) { + if (!type_info.array_length_index()) return sizeof(void*); - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - GITypeTag param_tag = g_type_info_get_tag(param_info); - return gjs_type_get_element_size(param_tag, param_info); + GI::AutoTypeInfo element_type{type_info.element_type()}; + return gjs_type_get_element_size(element_type.tag(), element_type); } return sizeof(void*); case GI_TYPE_TAG_VOID: break; + + default: + return basic_type_element_size(element_type); } g_return_val_if_reached(0); } -enum class ArrayReleaseType { - EXPLICIT_LENGTH, - ZERO_TERMINATED, -}; +static GArray* garray_new_for_storage_type(unsigned length, + GITypeTag storage_type, + const GI::TypeInfo 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); +} -template -static inline bool gjs_gi_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); +static GArray* garray_new_for_basic_type(unsigned length, GITypeTag tag) { + size_t element_size = basic_type_element_size(tag); + return g_array_sized_new(true, false, element_size, length); +} - if (!arg_array) - return true; +char* gjs_argument_display_name(const char* arg_name, + GjsArgumentType arg_type) { + switch (arg_type) { + case GJS_ARGUMENT_ARGUMENT: + return g_strdup_printf("Argument '%s'", arg_name); + case GJS_ARGUMENT_RETURN_VALUE: + return g_strdup("Return value"); + case GJS_ARGUMENT_FIELD: + return g_strdup_printf("Field '%s'", arg_name); + case GJS_ARGUMENT_LIST_ELEMENT: + return g_strdup("List element"); + case GJS_ARGUMENT_HASH_ELEMENT: + return g_strdup("Hash element"); + case GJS_ARGUMENT_ARRAY_ELEMENT: + return g_strdup("Array element"); + default: + g_assert_not_reached (); + } +} - if (element_transfer != GI_TRANSFER_EVERYTHING) - return true; +static void throw_invalid_argument(JSContext* context, JS::HandleValue value, + const GI::TypeInfo arginfo, + const char* arg_name, + GjsArgumentType arg_type) { + Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_type)}; - if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { - if (length == 0) - return true; - } + gjs_throw(context, "Expected type %s for %s but got type '%s'", + arginfo.display_string(), display_name.get(), + JS::InformalValueTypeName(value)); +} - GITypeTag type_tag = g_type_info_get_tag(param_type); +GJS_JSAPI_RETURN_CONVENTION +static bool throw_invalid_argument_tag(JSContext* cx, JS::HandleValue value, + GITypeTag type_tag, const char* arg_name, + GjsArgumentType arg_type) { + Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_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; + gjs_throw(cx, "Expected type %s for %s but got type '%s'", + gi_type_tag_to_string(type_tag), display_name.get(), + JS::InformalValueTypeName(value)); + return false; +} - bool is_pointer = g_type_info_is_pointer(param_type); - for (size_t i = 0;; i++) { - GIArgument elem; - auto* element_start = &arg_array[i * element_size]; - auto* pointer = - is_pointer ? *reinterpret_cast(element_start) : nullptr; +GJS_JSAPI_RETURN_CONVENTION +static bool throw_invalid_interface_argument(JSContext* cx, + JS::HandleValue value, + const GI::BaseInfo interface_info, + const char* arg_name, + GjsArgumentType arg_type) { + Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_type)}; + + gjs_throw(cx, "Expected type %s for %s but got type '%s'", + interface_info.type_string(), display_name.get(), + JS::InformalValueTypeName(value)); + return false; +} - if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { - if (is_pointer) { - if (!pointer) - break; - } else if (*element_start == 0 && - memcmp(element_start, element_start + 1, - element_size - 1) == 0) { - break; - } - } +bool gjs_array_to_basic_explicit_array( + JSContext* cx, JS::HandleValue value, GITypeTag element_tag, + const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags, + void** contents_out, size_t* length_out) { + g_assert(contents_out && length_out && "forgot out parameter"); - gjs_arg_set(&elem, is_pointer ? pointer : element_start); - JS::AutoSaveExceptionState saved_exc(cx); - if (!gjs_g_arg_release_internal(cx, element_transfer, param_type, - type_tag, GJS_ARGUMENT_ARRAY_ELEMENT, - flags, &elem)) { - return false; - } + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to C array", + arg_name, gjs_debug_value(value).c_str()); - if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { - if (i == length - 1) - break; - } + if ((value.isNull() && !(flags & GjsArgumentFlags::MAY_BE_NULL)) || + (!value.isString() && !value.isObjectOrNull())) { + return throw_invalid_argument_tag(cx, value, element_tag, arg_name, + arg_type); } - return true; -} + if (value.isNull()) { + *contents_out = nullptr; + *length_out = 0; + return true; + } -static GArray* garray_new_for_storage_type(unsigned length, - 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); -} + if (value.isString()) { + // Allow strings as int8/uint8/int16/uint16 arrays + JS::RootedString str{cx, value.toString()}; + return gjs_string_to_intarray(cx, str, element_tag, contents_out, + length_out); + } -char* gjs_argument_display_name(const char* arg_name, - GjsArgumentType arg_type) { - switch (arg_type) { - case GJS_ARGUMENT_ARGUMENT: - return g_strdup_printf("Argument '%s'", arg_name); - case GJS_ARGUMENT_RETURN_VALUE: - return g_strdup("Return value"); - case GJS_ARGUMENT_FIELD: - return g_strdup_printf("Field '%s'", arg_name); - case GJS_ARGUMENT_LIST_ELEMENT: - return g_strdup("List element"); - case GJS_ARGUMENT_HASH_ELEMENT: - return g_strdup("Hash element"); - case GJS_ARGUMENT_ARRAY_ELEMENT: - return g_strdup("Array element"); - default: - g_assert_not_reached (); + JS::RootedObject array_obj{cx, &value.toObject()}; + if (JS_IsUint8Array(array_obj) && + (element_tag == GI_TYPE_TAG_INT8 || element_tag == GI_TYPE_TAG_UINT8)) { + GBytes* bytes = gjs_byte_array_get_bytes(array_obj); + *contents_out = g_bytes_unref_to_data(bytes, length_out); + return true; } -} -[[nodiscard]] static const char* type_tag_to_human_string( - GITypeInfo* type_info) { - GITypeTag tag; + const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + bool found_length; + if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) + return false; + if (found_length) { + uint32_t length; - tag = g_type_info_get_tag(type_info); + if (!gjs_object_require_converted_property(cx, array_obj, nullptr, + atoms.length(), &length)) { + return false; + } - if (tag == GI_TYPE_TAG_INTERFACE) { - GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); - return g_info_type_to_string(interface.type()); - } else { - return g_type_tag_to_string(tag); - } -} + // For basic types, type tag == storage type + if (!gjs_array_to_basic_array(cx, value, length, element_tag, + contents_out)) + return false; -static void -throw_invalid_argument(JSContext *context, - JS::HandleValue value, - GITypeInfo *arginfo, - const char *arg_name, - GjsArgumentType arg_type) -{ - GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); + *length_out = length; + return true; + } - gjs_throw(context, "Expected type %s for %s but got type '%s'", - type_tag_to_human_string(arginfo), display_name.get(), - JS::InformalValueTypeName(value)); + return throw_invalid_argument_tag(cx, value, element_tag, arg_name, + arg_type); } -GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, - GITypeInfo* type_info, const char* arg_name, - GjsArgumentType arg_type, GITransfer transfer, - GjsArgumentFlags flags, void** contents, - size_t* length_p) { + const GI::TypeInfo type_info, + const char* arg_name, GjsArgumentType arg_type, + GITransfer transfer, GjsArgumentFlags flags, + void** contents, size_t* length_p) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + GITypeTag element_tag = element_type.tag(); + + if (GI_TYPE_TAG_IS_BASIC(element_tag)) { + return gjs_array_to_basic_explicit_array(context, value, element_tag, + arg_name, arg_type, flags, + contents, length_p); + } + bool found_length; gjs_debug_marshal( @@ -1102,11 +1144,10 @@ bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, "Converting argument '%s' JS value %s to C array, transfer %d", arg_name, gjs_debug_value(value).c_str(), transfer); - GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); - if ((value.isNull() && !(flags & GjsArgumentFlags::MAY_BE_NULL)) || (!value.isString() && !value.isObjectOrNull())) { - throw_invalid_argument(context, value, param_info, arg_name, arg_type); + throw_invalid_argument(context, value, element_type, arg_name, + arg_type); return false; } @@ -1116,14 +1157,12 @@ bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, } else if (value.isString()) { /* Allow strings as int8/uint8/int16/uint16 arrays */ JS::RootedString str(context, value.toString()); - GITypeTag element_tag = g_type_info_get_tag(param_info); if (!gjs_string_to_intarray(context, str, element_tag, contents, length_p)) return false; } else { JS::RootedObject array_obj(context, &value.toObject()); - GITypeTag element_type = g_type_info_get_tag(param_info); - if (JS_IsUint8Array(array_obj) && (element_type == GI_TYPE_TAG_INT8 || - element_type == GI_TYPE_TAG_UINT8)) { + if (JS_IsUint8Array(array_obj) && (element_tag == GI_TYPE_TAG_INT8 || + element_tag == GI_TYPE_TAG_UINT8)) { GBytes* bytes = gjs_byte_array_get_bytes(array_obj); *contents = g_bytes_unref_to_data(bytes, length_p); return true; @@ -1140,18 +1179,15 @@ bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, context, array_obj, nullptr, atoms.length(), &length)) { return false; } else { - if (!gjs_array_to_array(context, - value, - length, - transfer, - param_info, - contents)) + if (!gjs_array_to_array(context, value, length, transfer, + element_type, contents)) return false; *length_p = length; } } else { - throw_invalid_argument(context, value, param_info, arg_name, arg_type); + throw_invalid_argument(context, value, element_type, arg_name, + arg_type); return false; } } @@ -1159,53 +1195,62 @@ bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, return true; } -namespace arg { -[[nodiscard]] static bool is_gdk_atom(GIBaseInfo* info) { - return (strcmp("Atom", g_base_info_get_name(info)) == 0 && - strcmp("Gdk", g_base_info_get_namespace(info)) == 0); -} -} // namespace arg - static void intern_gdk_atom(const char* name, GIArgument* ret) { - GjsAutoFunctionInfo atom_intern_fun = - g_irepository_find_by_name(nullptr, "Gdk", "atom_intern"); + GI::Repository repo; + GI::AutoFunctionInfo atom_intern_fun{ + repo.find_by_name("Gdk", "atom_intern").value()}; - GIArgument atom_intern_args[2]; + std::vector atom_intern_args{2}; /* Can only store char * in GIArgument. First argument to gdk_atom_intern * is const char *, string isn't modified. */ gjs_arg_set(&atom_intern_args[0], name); gjs_arg_set(&atom_intern_args[1], false); - mozilla::Unused << g_function_info_invoke(atom_intern_fun, atom_intern_args, - 2, nullptr, 0, ret, nullptr); -} - -static bool value_to_interface_gi_argument( - JSContext* cx, JS::HandleValue value, GIBaseInfo* interface_info, - GIInfoType interface_type, GITransfer transfer, bool expect_object, - GIArgument* arg, GjsArgumentType arg_type, GjsArgumentFlags flags, - bool* report_type_mismatch) { - g_assert(report_type_mismatch); - GType gtype; - - switch (interface_type) { - case GI_INFO_TYPE_BOXED: - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_STRUCT: - case GI_INFO_TYPE_UNION: - // These are subtypes of GIRegisteredTypeInfo for which the cast is - // safe - gtype = g_registered_type_info_get_g_type(interface_info); - break; + mozilla::Unused << atom_intern_fun.invoke(atom_intern_args, {}, ret); +} - default: - gtype = G_TYPE_NONE; +static bool value_to_gdk_atom_gi_argument_internal(JSContext* cx, + JS::HandleValue value, + GIArgument* arg, + const char* arg_name, + GjsArgumentType arg_type) { + if (!value.isNull() && !value.isString()) { + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, arg_type)}; + gjs_throw(cx, "Expected type String or null for %s but got type '%s'", + display_name.get(), JS::InformalValueTypeName(value)); + return false; } + if (value.isNull()) { + intern_gdk_atom("NONE", arg); + return true; + } + + JS::RootedString str{cx, value.toString()}; + JS::UniqueChars name{JS_EncodeStringToUTF8(cx, str)}; + if (!name) + return false; + + intern_gdk_atom(name.get(), arg); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +bool value_to_interface_gi_argument_internal( + JSContext* cx, JS::HandleValue value, const GI::BaseInfo interface_info, + GITransfer transfer, GIArgument* arg, const char* arg_name, + GjsArgumentType arg_type, GjsArgumentFlags flags) { + auto reg_type = interface_info.as(); + if (reg_type && reg_type->is_gdk_atom()) { + return value_to_gdk_atom_gi_argument_internal(cx, value, arg, arg_name, + arg_type); + } + + GType gtype = reg_type.map(std::mem_fn(&GI::RegisteredTypeInfo::gtype)) + .valueOr(G_TYPE_NONE); + if (gtype != G_TYPE_NONE) gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); @@ -1221,33 +1266,18 @@ static bool value_to_interface_gi_argument( Gjs::AutoGValue gvalue; if (!gjs_value_to_g_value(cx, value, &gvalue)) { - gjs_arg_unset(arg); + gjs_arg_unset(arg); return false; } gjs_arg_set(arg, g_boxed_copy(G_TYPE_VALUE, &gvalue)); return true; - } else if (arg::is_gdk_atom(interface_info)) { - if (!value.isNull() && !value.isString()) { - *report_type_mismatch = true; - return false; - } else if (value.isNull()) { - intern_gdk_atom("NONE", arg); - return true; - } - - JS::RootedString str(cx, value.toString()); - JS::UniqueChars name(JS_EncodeStringToUTF8(cx, str)); - if (!name) - return false; - - intern_gdk_atom(name.get(), arg); - return true; - - } else if (expect_object != value.isObjectOrNull()) { - *report_type_mismatch = true; - return false; + } else if (interface_info.is_enum_or_flags() == value.isObjectOrNull()) { + // for enum/flags, object/null are invalid. for everything else, + // everything except object/null is invalid. + return throw_invalid_interface_argument(cx, value, interface_info, + arg_name, arg_type); } else if (value.isNull()) { gjs_arg_set(arg, nullptr); @@ -1255,15 +1285,15 @@ static bool value_to_interface_gi_argument( } else if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); - if (interface_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_gtype_struct(interface_info)) { + auto struct_info = interface_info.as(); + if (struct_info && struct_info->is_gtype_struct()) { GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { - *report_type_mismatch = true; - return false; + return throw_invalid_interface_argument( + cx, value, interface_info, arg_name, arg_type); } // We use peek here to simplify reference counting (we just ignore @@ -1281,8 +1311,7 @@ static bool value_to_interface_gi_argument( } GType arg_gtype = gtype; - if (interface_type == GI_INFO_TYPE_STRUCT && gtype == G_TYPE_NONE && - !g_struct_info_is_foreign(interface_info)) { + if (struct_info && gtype == G_TYPE_NONE && !struct_info->is_foreign()) { GType actual_gtype = G_TYPE_NONE; // In case we have no known type from gi we should try to be // more dynamic and try to get the type from JS, to handle the @@ -1295,9 +1324,7 @@ static bool value_to_interface_gi_argument( gtype = actual_gtype; } - if ((interface_type == GI_INFO_TYPE_STRUCT || - interface_type == GI_INFO_TYPE_BOXED) && - !g_type_is_a(gtype, G_TYPE_CLOSURE)) { + if (struct_info && !g_type_is_a(gtype, G_TYPE_CLOSURE)) { // Handle Struct/Union first since we don't necessarily need a GType // for them. We special case Closures later, so skip them here. if (g_type_is_a(gtype, G_TYPE_BYTES) && JS_IsUint8Array(obj)) { @@ -1312,15 +1339,22 @@ static bool value_to_interface_gi_argument( g_type_is_a(gtype, G_TYPE_BOXED) || g_type_is_a(gtype, G_TYPE_VALUE) || g_type_is_a(gtype, G_TYPE_VARIANT)) { + if (!BoxedBase::typecheck(cx, obj, interface_info)) { + gjs_arg_unset(arg); + return false; + } return BoxedBase::transfer_to_gi_argument( - cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, - interface_info); + cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } } - if (interface_type == GI_INFO_TYPE_UNION) { + if (interface_info.is_union()) { + if (!UnionBase::typecheck(cx, obj, interface_info)) { + gjs_arg_unset(arg); + return false; + } return UnionBase::transfer_to_gi_argument( - cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); + cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } if (gtype != G_TYPE_NONE) { @@ -1330,7 +1364,7 @@ static bool value_to_interface_gi_argument( } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { if (!gjs_typecheck_param(cx, obj, gtype, true)) { - gjs_arg_unset(arg); + gjs_arg_unset(arg); return false; } gjs_arg_set(arg, gjs_g_param_from_param(cx, obj)); @@ -1340,11 +1374,12 @@ static bool value_to_interface_gi_argument( } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { - if (BoxedBase::typecheck(cx, obj, interface_info, gtype, - GjsTypecheckNoThrow())) { + if (BoxedBase::typecheck(cx, obj, interface_info, + GjsTypecheckNoThrow{}) && + BoxedBase::typecheck(cx, obj, gtype, + GjsTypecheckNoThrow{})) { return BoxedBase::transfer_to_gi_argument( - cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, - interface_info); + cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } GClosure* closure = @@ -1364,8 +1399,8 @@ static bool value_to_interface_gi_argument( // Should have been caught above as STRUCT/BOXED/UNION gjs_throw( cx, - "Boxed type %s registered for unexpected interface_type %d", - g_type_name(gtype), interface_type); + "Boxed type %s registered for unexpected interface_type %s", + g_type_name(gtype), interface_info.type_string()); return false; } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { @@ -1375,8 +1410,8 @@ static bool value_to_interface_gi_argument( } else if (G_TYPE_IS_INTERFACE(gtype)) { // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental - if (ObjectBase::typecheck(cx, obj, nullptr, gtype, - GjsTypecheckNoThrow())) { + if (ObjectBase::typecheck(cx, obj, gtype, + GjsTypecheckNoThrow{})) { return ObjectBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } @@ -1389,14 +1424,13 @@ static bool value_to_interface_gi_argument( gjs_throw(cx, "Unhandled GType %s unpacking GIArgument from Object", g_type_name(gtype)); - gjs_arg_unset(arg); + gjs_arg_unset(arg); return false; } gjs_debug(GJS_DEBUG_GFUNCTION, "conversion of JSObject value %s to type %s failed", - gjs_debug_value(value).c_str(), - g_base_info_get_name(interface_info)); + gjs_debug_value(value).c_str(), interface_info.name()); gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " @@ -1404,26 +1438,23 @@ static bool value_to_interface_gi_argument( return false; } else if (value.isNumber()) { - if (interface_type == GI_INFO_TYPE_ENUM) { + if (auto enum_info = interface_info.as()) { int64_t value_int64; - if (!JS::ToInt64(cx, value, &value_int64) || - !_gjs_enum_value_is_valid(cx, interface_info, value_int64)) + if (!JS::ToInt64(cx, value, &value_int64)) return false; - gjs_arg_set( - arg, _gjs_enum_to_int(value_int64)); - return true; - - } else if (interface_type == GI_INFO_TYPE_FLAGS) { - int64_t value_int64; - - if (!JS::ToInt64(cx, value, &value_int64) || - !_gjs_flags_value_is_valid(cx, gtype, value_int64)) - return false; + if (interface_info.is_flags()) { + if (!_gjs_flags_value_is_valid(cx, gtype, value_int64)) + return false; + } else { + if (!_gjs_enum_value_is_valid(cx, enum_info.value(), + value_int64)) + return false; + } - gjs_arg_set( - arg, _gjs_enum_to_int(value_int64)); + gjs_arg_set(arg, + enum_info->enum_to_int(value_int64)); return true; } else if (gtype == G_TYPE_NONE) { @@ -1441,23 +1472,23 @@ static bool value_to_interface_gi_argument( gjs_debug(GJS_DEBUG_GFUNCTION, "JSObject type '%s' is neither null nor an object", JS::InformalValueTypeName(value)); - *report_type_mismatch = true; - return false; + return throw_invalid_interface_argument(cx, value, interface_info, arg_name, + arg_type); } -template +template GJS_JSAPI_RETURN_CONVENTION inline static bool gjs_arg_set_from_js_value( - JSContext* cx, const JS::HandleValue& value, GIArgument* arg, - const char* arg_name, GjsArgumentType arg_type) { + JSContext* cx, JS::HandleValue value, GIArgument* arg, const char* arg_name, + GjsArgumentType arg_type) { bool out_of_range = false; - if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { + if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { if (out_of_range) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, arg_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "value %s is out of range for %s (type %s)", - std::to_string(gjs_arg_get(arg)).c_str(), - display_name.get(), Gjs::static_type_name()); + std::to_string(gjs_arg_get(arg)).c_str(), + display_name.get(), Gjs::static_type_name()); } return false; @@ -1465,9 +1496,9 @@ GJS_JSAPI_RETURN_CONVENTION inline static bool gjs_arg_set_from_js_value( gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "%s set to value %s (type %s)", - GjsAutoChar(gjs_argument_display_name(arg_name, arg_type)).get(), - std::to_string(gjs_arg_get(arg)).c_str(), - Gjs::static_type_name()); + Gjs::AutoChar{gjs_argument_display_name(arg_name, arg_type)}.get(), + std::to_string(gjs_arg_get(arg)).c_str(), + Gjs::static_type_name()); return true; } @@ -1477,196 +1508,547 @@ static bool check_nullable_argument(JSContext* cx, const char* arg_name, GITypeTag type_tag, GjsArgumentFlags flags, GIArgument* arg) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL) && !gjs_arg_get(arg)) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, arg_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "%s (type %s) may not be null", display_name.get(), - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); return false; } return true; } -bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, - GITypeInfo* type_info, const char* arg_name, - GjsArgumentType arg_type, GITransfer transfer, - GjsArgumentFlags flags, GIArgument* arg) { - GITypeTag type_tag = g_type_info_get_tag(type_info); +bool gjs_value_to_basic_gi_argument(JSContext* cx, JS::HandleValue value, + GITypeTag type_tag, GIArgument* arg, + const char* arg_name, + GjsArgumentType arg_type, + GjsArgumentFlags flags) { + g_assert(GI_TYPE_TAG_IS_BASIC(type_tag) && + "use gjs_value_to_gi_argument() for non-basic types"); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, - gjs_debug_value(value).c_str(), g_type_tag_to_string(type_tag)); + gjs_debug_value(value).c_str(), gi_type_tag_to_string(type_tag)); switch (type_tag) { - case GI_TYPE_TAG_VOID: - // just so it isn't uninitialized - gjs_arg_unset(arg); - return check_nullable_argument(context, arg_name, arg_type, type_tag, - flags, arg); + case GI_TYPE_TAG_VOID: + // don't know how to handle non-pointer VOID + return throw_invalid_argument_tag(cx, value, type_tag, arg_name, + arg_type); + case GI_TYPE_TAG_INT8: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); + case GI_TYPE_TAG_UINT8: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); + case GI_TYPE_TAG_INT16: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_INT8: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); - case GI_TYPE_TAG_UINT8: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); - case GI_TYPE_TAG_INT16: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); + case GI_TYPE_TAG_UINT16: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_UINT16: - return gjs_arg_set_from_js_value(context, value, arg, - arg_name, arg_type); + case GI_TYPE_TAG_INT32: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_INT32: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); + case GI_TYPE_TAG_UINT32: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_UINT32: - return gjs_arg_set_from_js_value(context, value, arg, - arg_name, arg_type); + case GI_TYPE_TAG_INT64: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_INT64: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); + case GI_TYPE_TAG_UINT64: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_UINT64: - return gjs_arg_set_from_js_value(context, value, arg, - arg_name, arg_type); + case GI_TYPE_TAG_BOOLEAN: + gjs_arg_set(arg, JS::ToBoolean(value)); + return true; - case GI_TYPE_TAG_BOOLEAN: - gjs_arg_set(arg, JS::ToBoolean(value)); - return true; + case GI_TYPE_TAG_FLOAT: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_FLOAT: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); + case GI_TYPE_TAG_DOUBLE: + return gjs_arg_set_from_js_value(cx, value, arg, arg_name, + arg_type); - case GI_TYPE_TAG_DOUBLE: - return gjs_arg_set_from_js_value(context, value, arg, arg_name, - arg_type); + case GI_TYPE_TAG_UNICHAR: + if (value.isString()) { + return gjs_unichar_from_string(cx, value, + &gjs_arg_member(arg)); + } - case GI_TYPE_TAG_UNICHAR: - if (value.isString()) { - if (!gjs_unichar_from_string(context, value, - &gjs_arg_member(arg))) - return false; - } else { - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); - return false; - } - break; + return throw_invalid_argument_tag(cx, value, type_tag, arg_name, + arg_type); - case GI_TYPE_TAG_GTYPE: - if (value.isObjectOrNull()) { - GType gtype; - JS::RootedObject obj(context, value.toObjectOrNull()); - if (!gjs_gtype_get_actual_gtype(context, obj, >ype)) - return false; + case GI_TYPE_TAG_GTYPE: + if (value.isObjectOrNull()) { + GType gtype; + JS::RootedObject obj{cx, value.toObjectOrNull()}; + if (!gjs_gtype_get_actual_gtype(cx, obj, >ype)) + return false; - if (gtype == G_TYPE_INVALID) - return false; - gjs_arg_set(arg, gtype); - } else { - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); - return false; - } - break; + if (gtype == G_TYPE_INVALID) + return false; + gjs_arg_set(arg, gtype); + return true; + } - case GI_TYPE_TAG_FILENAME: - if (value.isNull()) { - gjs_arg_set(arg, nullptr); - } else if (value.isString()) { - GjsAutoChar filename_str; - if (!gjs_string_to_filename(context, value, &filename_str)) - return false; + return throw_invalid_argument_tag(cx, value, type_tag, arg_name, + arg_type); - gjs_arg_set(arg, filename_str.release()); - } else { - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); - return false; - } + case GI_TYPE_TAG_FILENAME: + if (value.isNull()) { + gjs_arg_set(arg, nullptr); + } else if (value.isString()) { + Gjs::AutoChar filename_str; + if (!gjs_string_to_filename(cx, value, &filename_str)) + return false; - return check_nullable_argument(context, arg_name, arg_type, type_tag, - flags, arg); + gjs_arg_set(arg, filename_str.release()); + } else { + return throw_invalid_argument_tag(cx, value, type_tag, arg_name, + arg_type); + } - case GI_TYPE_TAG_UTF8: - if (value.isNull()) { - gjs_arg_set(arg, nullptr); - } else if (value.isString()) { - JS::RootedString str(context, value.toString()); - JS::UniqueChars utf8_str(JS_EncodeStringToUTF8(context, str)); - if (!utf8_str) - return false; + return check_nullable_argument(cx, arg_name, arg_type, type_tag, + flags, arg); - gjs_arg_set(arg, g_strdup(utf8_str.get())); - } else { - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); - return false; - } + case GI_TYPE_TAG_UTF8: + if (value.isNull()) { + gjs_arg_set(arg, nullptr); + } else if (value.isString()) { + JS::RootedString str{cx, value.toString()}; + JS::UniqueChars utf8_str{JS_EncodeStringToUTF8(cx, str)}; + if (!utf8_str) + return false; - return check_nullable_argument(context, arg_name, arg_type, type_tag, - flags, arg); + gjs_arg_set( + arg, Gjs::js_chars_to_glib(std::move(utf8_str)).release()); + } else { + return throw_invalid_argument_tag(cx, value, type_tag, arg_name, + arg_type); + } - case GI_TYPE_TAG_ERROR: - if (value.isNull()) { - gjs_arg_set(arg, nullptr); - } else if (value.isObject()) { - JS::RootedObject obj(context, &value.toObject()); - if (!ErrorBase::transfer_to_gi_argument(context, obj, arg, - GI_DIRECTION_IN, transfer)) - return false; - } else { - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); - return false; - } + return check_nullable_argument(cx, arg_name, arg_type, type_tag, + flags, arg); - return check_nullable_argument(context, arg_name, arg_type, type_tag, - flags, arg); + default: + g_return_val_if_reached(false); // non-basic type + } +} - case GI_TYPE_TAG_INTERFACE: - { - bool expect_object = true; +bool gjs_value_to_gerror_gi_argument(JSContext* cx, JS::HandleValue value, + GITransfer transfer, GIArgument* arg, + const char* arg_name, + GjsArgumentType arg_type, + GjsArgumentFlags flags) { + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type error", + arg_name, gjs_debug_value(value).c_str()); - GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); - g_assert(interface_info); + if (value.isNull()) { + gjs_arg_set(arg, nullptr); + } else if (value.isObject()) { + JS::RootedObject obj(cx, &value.toObject()); + if (!ErrorBase::transfer_to_gi_argument(cx, obj, arg, GI_DIRECTION_IN, + transfer)) + return false; + } else { + return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_ERROR, + arg_name, arg_type); + } - GIInfoType interface_type = interface_info.type(); - if (interface_type == GI_INFO_TYPE_ENUM || - interface_type == GI_INFO_TYPE_FLAGS || - arg::is_gdk_atom(interface_info)) - expect_object = false; + return check_nullable_argument(cx, arg_name, arg_type, GI_TYPE_TAG_ERROR, + flags, arg); +} - if (interface_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_foreign(interface_info)) { - return gjs_struct_foreign_convert_to_gi_argument( - context, value, interface_info, arg_name, arg_type, transfer, - flags, arg); - } +bool gjs_value_to_gdk_atom_gi_argument(JSContext* cx, JS::HandleValue value, + GIArgument* arg, const char* arg_name, + GjsArgumentType arg_type) { + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type interface", + arg_name, gjs_debug_value(value).c_str()); - bool report_type_mismatch = false; - if (!value_to_interface_gi_argument( - context, value, interface_info, interface_type, transfer, - expect_object, arg, arg_type, flags, &report_type_mismatch)) { - if (report_type_mismatch) - throw_invalid_argument(context, value, type_info, arg_name, - arg_type); + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is GdkAtom"); + + return value_to_gdk_atom_gi_argument_internal(cx, value, arg, arg_name, + arg_type); +} + +// Convert a JS value to GIArgument, specifically for arguments with type tag +// GI_TYPE_TAG_INTERFACE. +bool gjs_value_to_interface_gi_argument(JSContext* cx, JS::HandleValue value, + const GI::BaseInfo interface_info, + GITransfer transfer, GIArgument* arg, + const char* arg_name, + GjsArgumentType arg_type, + GjsArgumentFlags flags) { + if (auto reg_info = interface_info.as(); + reg_info && reg_info->is_gdk_atom()) { + return gjs_value_to_gdk_atom_gi_argument(cx, value, arg, arg_name, + arg_type); + } + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type interface", + arg_name, gjs_debug_value(value).c_str()); + + if (auto struct_info = interface_info.as(); + struct_info && struct_info->is_foreign()) { + return gjs_struct_foreign_convert_to_gi_argument( + cx, value, *struct_info, arg_name, arg_type, transfer, flags, arg); + } + if (!value_to_interface_gi_argument_internal(cx, value, interface_info, + transfer, arg, arg_name, + arg_type, flags)) + return false; + + if (interface_info.is_enum_or_flags()) + return true; + + return check_nullable_argument(cx, arg_name, arg_type, + GI_TYPE_TAG_INTERFACE, flags, arg); +} + +template +GJS_JSAPI_RETURN_CONVENTION static bool basic_array_to_linked_list( + JSContext* cx, JS::HandleValue value, GITypeTag element_tag, + const char* arg_name, GjsArgumentType arg_type, T** list_p) { + static_assert(std::is_same_v || std::is_same_v); + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && + "use gjs_array_to_g_list() for lists containing non-basic types"); + + constexpr GITypeTag list_tag = + std::is_same_v ? GI_TYPE_TAG_GLIST : GI_TYPE_TAG_GSLIST; + + // While a list can be NULL in C, that means empty array in JavaScript, it + // doesn't mean null in JavaScript. + if (!value.isObject()) + return false; + + const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + JS::RootedObject array_obj(cx, &value.toObject()); + + bool found_length; + if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) + return false; + if (!found_length) { + return throw_invalid_argument_tag(cx, value, list_tag, arg_name, + arg_type); + } + + uint32_t length; + if (!gjs_object_require_converted_property(cx, array_obj, nullptr, + atoms.length(), &length)) { + return throw_invalid_argument_tag(cx, value, list_tag, arg_name, + arg_type); + } + + JS::RootedObject array{cx, value.toObjectOrNull()}; + JS::RootedValue elem{cx}; + T* list = nullptr; + + for (size_t i = 0; i < length; ++i) { + GIArgument elem_arg = {0}; + + elem = JS::UndefinedValue(); + if (!JS_GetElement(cx, array, i, &elem)) { + gjs_throw(cx, "Missing array element %zu", i); return false; } - if (expect_object) - return check_nullable_argument(context, arg_name, arg_type, - type_tag, flags, arg); + if (!gjs_value_to_basic_gi_argument(cx, elem, element_tag, &elem_arg, + arg_name, GJS_ARGUMENT_LIST_ELEMENT, + GjsArgumentFlags::NONE)) { + return false; } - break; + void* hash_pointer = + gi_type_tag_hash_pointer_from_argument(element_tag, &elem_arg); + + if constexpr (std::is_same_v) + list = g_list_prepend(list, hash_pointer); + else if constexpr (std::is_same_v) + list = g_slist_prepend(list, hash_pointer); + } + + if constexpr (std::is_same_v) + list = g_list_reverse(list); + else if constexpr (std::is_same_v) + list = g_slist_reverse(list); + + *list_p = list; + + return true; +} + +bool gjs_value_to_basic_glist_gi_argument(JSContext* cx, JS::HandleValue value, + GITypeTag element_tag, + GIArgument* arg, const char* arg_name, + GjsArgumentType arg_type) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && + "use gjs_array_to_g_list() for lists containing non-basic types"); + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type glist", + arg_name, gjs_debug_value(value).c_str()); + + return basic_array_to_linked_list(cx, value, element_tag, arg_name, + arg_type, &gjs_arg_member(arg)); +} + +bool gjs_value_to_basic_gslist_gi_argument(JSContext* cx, JS::HandleValue value, + GITypeTag element_tag, + GIArgument* arg, + const char* arg_name, + GjsArgumentType arg_type) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && + "use gjs_array_to_g_list() for lists containing non-basic types"); + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type gslist", + arg_name, gjs_debug_value(value).c_str()); + + return basic_array_to_linked_list(cx, value, element_tag, arg_name, + arg_type, &gjs_arg_member(arg)); +} + +bool gjs_value_to_basic_ghash_gi_argument( + JSContext* cx, JS::HandleValue value, GITypeTag key_tag, + GITypeTag value_tag, GITransfer transfer, GIArgument* arg, + const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { + g_assert(GI_TYPE_TAG_IS_BASIC(key_tag) && + "use gjs_object_to_g_hash() for hashes with non-basic key types"); + g_assert( + GI_TYPE_TAG_IS_BASIC(value_tag) && + "use gjs_object_to_g_hash() for hashes with non-basic value types"); + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type ghash", + arg_name, gjs_debug_value(value).c_str()); + + if (value.isNull()) { + if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { + return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_GHASH, + arg_name, arg_type); + } + gjs_arg_set(arg, nullptr); + return true; + } + + if (!value.isObject()) { + return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_GHASH, + arg_name, arg_type); + } + + if (transfer == GI_TRANSFER_CONTAINER) { + if (Gjs::basic_type_needs_release(key_tag) || + Gjs::basic_type_needs_release(value_tag)) { + // See comment in gjs_value_to_g_hash() + gjs_throw(cx, "Container transfer for in parameters not supported"); + return false; + } + + transfer = GI_TRANSFER_NOTHING; + } + + JS::RootedObject props{cx, &value.toObject()}; + JS::Rooted ids{cx, cx}; + if (!JS_Enumerate(cx, props, &ids)) + return false; + + Gjs::AutoPointer result{ + create_hash_table_for_key_type(key_tag)}; + + JS::RootedValue v_key{cx}, v_val{cx}; + JS::RootedId cur_id{cx}; + for (size_t id_ix = 0, id_len = ids.length(); id_ix < id_len; ++id_ix) { + cur_id = ids[id_ix]; + void* key_ptr; + void* val_ptr; + GIArgument val_arg = {0}; + + if (!JS_IdToValue(cx, cur_id, &v_key) || + // Type check key type. + !value_to_ghashtable_key(cx, v_key, key_tag, &key_ptr) || + !JS_GetPropertyById(cx, props, cur_id, &v_val) || + // Type check and convert value to a C type + !gjs_value_to_basic_gi_argument(cx, v_val, value_tag, &val_arg, + nullptr, GJS_ARGUMENT_HASH_ELEMENT, + GjsArgumentFlags::MAY_BE_NULL)) + return false; + + // Use heap-allocated values for types that don't fit in a pointer + if (value_tag == GI_TYPE_TAG_INT64) { + val_ptr = heap_value_new_from_arg(&val_arg); + } else if (value_tag == GI_TYPE_TAG_UINT64) { + val_ptr = heap_value_new_from_arg(&val_arg); + } else if (value_tag == GI_TYPE_TAG_FLOAT) { + val_ptr = heap_value_new_from_arg(&val_arg); + } else if (value_tag == GI_TYPE_TAG_DOUBLE) { + val_ptr = heap_value_new_from_arg(&val_arg); + } else { + // Other types are simply stuffed inside the pointer + val_ptr = + gi_type_tag_hash_pointer_from_argument(value_tag, &val_arg); + } + + g_hash_table_insert(result, key_ptr, val_ptr); + } + + gjs_arg_set(arg, result.release()); + return true; +} + +bool gjs_value_to_basic_array_gi_argument(JSContext* cx, JS::HandleValue value, + GITypeTag element_tag, + GIArrayType array_type, + GIArgument* arg, const char* arg_name, + GjsArgumentType arg_type, + GjsArgumentFlags flags) { + Gjs::AutoPointer data; + size_t length; + if (!gjs_array_to_basic_explicit_array(cx, value, element_tag, arg_name, + arg_type, flags, data.out(), + &length)) { + return false; + } + + if (array_type == GI_ARRAY_TYPE_C) { + gjs_arg_set(arg, data.release()); + } else if (array_type == GI_ARRAY_TYPE_ARRAY) { + GArray* array = garray_new_for_basic_type(length, element_tag); + + if (data) + g_array_append_vals(array, data, length); + gjs_arg_set(arg, array); + } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { + return gjs_value_to_byte_array_gi_argument(cx, value, arg, arg_name, + flags); + } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { + GPtrArray* array = g_ptr_array_sized_new(length); + + g_ptr_array_set_size(array, length); + if (data) + memcpy(array->pdata, data, sizeof(void*) * length); + gjs_arg_set(arg, array); + } + return true; +} + +bool gjs_value_to_byte_array_gi_argument(JSContext* cx, JS::HandleValue value, + GIArgument* arg, const char* arg_name, + GjsArgumentFlags flags) { + // First, let's handle the case where we're passed an instance of + // Uint8Array and it needs to be marshalled to GByteArray. + if (value.isObject()) { + JSObject* bytearray_obj = &value.toObject(); + if (JS_IsUint8Array(bytearray_obj)) { + gjs_arg_set(arg, gjs_byte_array_get_byte_array(bytearray_obj)); + return true; + } + } + + Gjs::AutoPointer data; + size_t length; + if (!gjs_array_to_basic_explicit_array(cx, value, GI_TYPE_TAG_UINT8, + arg_name, GJS_ARGUMENT_ARGUMENT, + flags, data.out(), &length)) { + return false; + } + + GByteArray* byte_array = g_byte_array_sized_new(length); + + if (data) + g_byte_array_append(byte_array, data.as(), length); + gjs_arg_set(arg, byte_array); + return true; +} + +bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, + const GI::TypeInfo type_info, + const char* arg_name, GjsArgumentType arg_type, + GITransfer transfer, GjsArgumentFlags flags, + GIArgument* arg) { + GITypeTag type_tag = type_info.tag(); + + if (type_info.is_basic()) { + return gjs_value_to_basic_gi_argument(context, value, type_tag, arg, + arg_name, arg_type, flags); + } + + if (type_tag == GI_TYPE_TAG_ERROR) { + return gjs_value_to_gerror_gi_argument(context, value, transfer, arg, + arg_name, arg_type, flags); + } + + if (type_tag == GI_TYPE_TAG_INTERFACE) { + GI::AutoBaseInfo interface_info{type_info.interface()}; + return gjs_value_to_interface_gi_argument(context, value, + interface_info, transfer, arg, + arg_name, arg_type, flags); + } + + if (type_tag == GI_TYPE_TAG_GLIST || type_tag == GI_TYPE_TAG_GSLIST) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + if (type_tag == GI_TYPE_TAG_GLIST) + return gjs_value_to_basic_glist_gi_argument( + context, value, element_type.tag(), arg, arg_name, + arg_type); + return gjs_value_to_basic_gslist_gi_argument( + context, value, element_type.tag(), arg, arg_name, arg_type); + } + // else, fall through to generic marshaller + + } else if (type_tag == GI_TYPE_TAG_GHASH) { + GI::AutoTypeInfo key_type{type_info.key_type()}; + GI::AutoTypeInfo value_type{type_info.value_type()}; + if (key_type.is_basic() && value_type.is_basic()) { + return gjs_value_to_basic_ghash_gi_argument( + context, value, key_type.tag(), value_type.tag(), transfer, arg, + arg_name, arg_type, flags); + } + // else, fall through to generic marshaller + + } else if (type_tag == GI_TYPE_TAG_ARRAY) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + return gjs_value_to_basic_array_gi_argument( + context, value, element_type.tag(), type_info.array_type(), arg, + arg_name, arg_type, flags); + } + // else, fall through to generic marshaller + } + + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, + "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, + gjs_debug_value(value).c_str(), gi_type_tag_to_string(type_tag)); + + switch (type_tag) { + case GI_TYPE_TAG_VOID: + g_assert(type_info.is_pointer() && + "non-pointers should be handled by " + "gjs_value_to_basic_gi_argument()"); + // void pointer; cannot marshal. Pass null to C if argument is nullable. + gjs_arg_unset(arg); + return check_nullable_argument(context, arg_name, arg_type, type_tag, + flags, arg); case GI_TYPE_TAG_GLIST: return gjs_array_to_g_list(context, value, type_info, transfer, arg_name, arg_type, @@ -1701,23 +2083,9 @@ bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, break; case GI_TYPE_TAG_ARRAY: { - GjsAutoPointer data; + Gjs::AutoPointer data; size_t length; - GIArrayType array_type = g_type_info_get_array_type(type_info); - - /* First, let's handle the case where we're passed an instance - * of Uint8Array and it needs to be marshalled to GByteArray. - */ - if (value.isObject()) { - JSObject* bytearray_obj = &value.toObject(); - if (JS_IsUint8Array(bytearray_obj) && - array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { - gjs_arg_set(arg, gjs_byte_array_get_byte_array(bytearray_obj)); - break; - } else { - /* Fall through, !handled */ - } - } + GIArrayType array_type = type_info.array_type(); if (!gjs_array_to_explicit_array(context, value, type_info, arg_name, arg_type, transfer, flags, data.out(), @@ -1725,24 +2093,19 @@ bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, return false; } - GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); + GI::AutoTypeInfo element_type{type_info.element_type()}; if (array_type == GI_ARRAY_TYPE_C) { 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, param_info); + GArray* array = garray_new_for_storage_type( + length, element_type.storage_type(), element_type); if (data) g_array_append_vals(array, data, length); gjs_arg_set(arg, array); } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { - GByteArray *byte_array = g_byte_array_sized_new(length); - - if (data) - g_byte_array_append(byte_array, data.as(), - length); - gjs_arg_set(arg, byte_array); + // handled in gjs_value_to_basic_array_gi_argument() + g_assert_not_reached(); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GPtrArray *array = g_ptr_array_sized_new(length); @@ -1754,8 +2117,11 @@ bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, break; } default: + // basic types handled in gjs_value_to_basic_gi_argument(), ERROR + // handled in gjs_value_to_gerror_gi_argument(), and INTERFACE handled + // in gjs_value_to_interface_gi_argument() g_warning("Unhandled type %s for JavaScript to GIArgument conversion", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } @@ -1763,152 +2129,202 @@ bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, return true; } -/* If a callback function with a return value throws, we still have - * to return something to C. This function defines what that something - * is. It basically boils down to memset(arg, 0, sizeof(*arg)), but - * gives as a bit more future flexibility and also will work if - * libffi passes us a buffer that only has room for the appropriate - * branch of GIArgument. (Currently it appears that the return buffer - * has a fixed size large enough for the union of all types.) - */ -void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg) { - GITypeTag type_tag = g_type_info_get_tag(type_info); +bool gjs_value_to_callback_out_arg(JSContext* cx, JS::HandleValue value, + const GI::ArgInfo arg_info, + GIArgument* arg) { + g_assert((arg_info.direction() == GI_DIRECTION_OUT || + arg_info.direction() == GI_DIRECTION_INOUT) && + "gjs_value_to_callback_out_arg does not handle in arguments."); + + GjsArgumentFlags flags = GjsArgumentFlags::NONE; + GI::StackTypeInfo type_info; + arg_info.load_type(&type_info); + + // If the argument is optional and we're passed nullptr, + // ignore the GJS value. + if (arg_info.is_optional() && !arg) + return true; + + // Otherwise, throw an error to prevent a segfault. + if (!arg) { + gjs_throw(cx, "Return value %s is not optional but was passed NULL", + arg_info.name()); + return false; + } + + if (arg_info.may_be_null()) + flags |= GjsArgumentFlags::MAY_BE_NULL; + if (arg_info.caller_allocates()) + flags |= GjsArgumentFlags::CALLER_ALLOCATES; + + return gjs_value_to_gi_argument( + cx, value, type_info, arg_info.name(), + (arg_info.is_return_value() ? GJS_ARGUMENT_RETURN_VALUE + : GJS_ARGUMENT_ARGUMENT), + arg_info.ownership_transfer(), flags, arg); +} + +///// "FROM" MARSHALLERS /////////////////////////////////////////////////////// +// These marshaller functions are responsible for converting C values returned +// from a C function call, usually stored in a GIArgument, back to JS values. + +bool gjs_value_from_basic_gi_argument(JSContext* cx, + JS::MutableHandleValue value_out, + GITypeTag type_tag, GIArgument* arg) { + g_assert(GI_TYPE_TAG_IS_BASIC(type_tag) && + "use gjs_value_from_gi_argument() for non-basic types"); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument %s to JS::Value", + gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: - // just so it isn't uninitialized - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_INT8: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_UINT8: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_INT16: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_UINT16: - gjs_arg_unset(arg); - break; + // Pointers are handled in gjs_value_from_gi_argument(), and would + // set null instead + value_out.setUndefined(); + return true; + + case GI_TYPE_TAG_BOOLEAN: + value_out.setBoolean(gjs_arg_get(arg)); + return true; + case GI_TYPE_TAG_INT32: - gjs_arg_unset(arg); - break; + value_out.setInt32(gjs_arg_get(arg)); + return true; + case GI_TYPE_TAG_UINT32: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_UNICHAR: - gjs_arg_unset(arg); - break; + value_out.setNumber(gjs_arg_get(arg)); + return true; + case GI_TYPE_TAG_INT64: - gjs_arg_unset(arg); - break; + value_out.setNumber(gjs_arg_get_maybe_rounded(arg)); + return true; + case GI_TYPE_TAG_UINT64: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_BOOLEAN: - gjs_arg_unset(arg); - break; + value_out.setNumber(gjs_arg_get_maybe_rounded(arg)); + return true; + + case GI_TYPE_TAG_UINT16: + value_out.setInt32(gjs_arg_get(arg)); + return true; + + case GI_TYPE_TAG_INT16: + value_out.setInt32(gjs_arg_get(arg)); + return true; + + case GI_TYPE_TAG_UINT8: + value_out.setInt32(gjs_arg_get(arg)); + return true; + + case GI_TYPE_TAG_INT8: + value_out.setInt32(gjs_arg_get(arg)); + return true; + case GI_TYPE_TAG_FLOAT: - gjs_arg_unset(arg); - break; + value_out.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); + return true; + case GI_TYPE_TAG_DOUBLE: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_GTYPE: - gjs_arg_unset(arg); - break; + value_out.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); + return true; + + case GI_TYPE_TAG_GTYPE: { + GType gtype = gjs_arg_get(arg); + if (gtype == 0) { + value_out.setNull(); + return true; + } + + JSObject* obj = gjs_gtype_create_gtype_wrapper(cx, gtype); + if (!obj) + return false; + + value_out.setObject(*obj); + return true; + } + + case GI_TYPE_TAG_UNICHAR: { + char32_t value = gjs_arg_get(arg); + + // Preserve the bidirectional mapping between 0 and "" + if (value == 0) { + value_out.set(JS_GetEmptyStringValue(cx)); + return true; + } else if (!g_unichar_validate(value)) { + gjs_throw(cx, "Invalid unicode codepoint %" G_GUINT32_FORMAT, + value); + return false; + } + + char utf8[7]; + int bytes = g_unichar_to_utf8(value, utf8); + return gjs_string_from_utf8_n(cx, utf8, bytes, value_out); + } + case GI_TYPE_TAG_FILENAME: - case GI_TYPE_TAG_UTF8: - case GI_TYPE_TAG_GLIST: - case GI_TYPE_TAG_GSLIST: - case GI_TYPE_TAG_ERROR: - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - g_assert(interface_info != nullptr); + case GI_TYPE_TAG_UTF8: { + const char* str = gjs_arg_get(arg); + if (!str) { + value_out.setNull(); + return true; + } - GIInfoType interface_type = interface_info.type(); + if (type_tag == GI_TYPE_TAG_FILENAME) + return gjs_string_from_filename(cx, str, -1, value_out); + + return gjs_string_from_utf8(cx, str, value_out); + } - if (interface_type == GI_INFO_TYPE_ENUM || - interface_type == GI_INFO_TYPE_FLAGS) - gjs_arg_unset(arg); - else - gjs_arg_unset(arg); - } break; - case GI_TYPE_TAG_GHASH: - // Possibly better to return an empty hash table? - gjs_arg_unset(arg); - break; - case GI_TYPE_TAG_ARRAY: - gjs_arg_unset(arg); - break; default: - g_warning("Unhandled type %s for default GIArgument initialization", - g_type_tag_to_string(type_tag)); - break; + // this function handles only basic types + g_return_val_if_reached(false); } } -bool gjs_value_to_callback_out_arg(JSContext* context, JS::HandleValue value, - GIArgInfo* arg_info, GIArgument* arg) { - GIDirection direction [[maybe_unused]] = g_arg_info_get_direction(arg_info); - g_assert( - (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) && - "gjs_value_to_callback_out_arg does not handle in arguments."); - - GjsArgumentFlags flags = GjsArgumentFlags::NONE; - GITypeInfo type_info; - - g_arg_info_load_type(arg_info, &type_info); - - // If the argument is optional and we're passed nullptr, - // ignore the GJS value. - if (g_arg_info_is_optional(arg_info) && !arg) - return true; +bool gjs_array_from_strv(JSContext* cx, JS::MutableHandleValue value_out, + const char** strv) { + // We treat a NULL strv as an empty array, since this function should always + // set an array value when returning true. Another alternative would be + // value_out.setNull(), but clients would need to always check for both an + // empty array and null if that was the case. + JS::RootedValueVector elems{cx}; + for (size_t i = 0; strv && strv[i]; i++) { + if (!elems.growBy(1)) { + JS_ReportOutOfMemory(cx); + return false; + } - // Otherwise, throw an error to prevent a segfault. - if (!arg) { - gjs_throw(context, - "Return value %s is not optional but was passed NULL", - g_base_info_get_name(arg_info)); - return false; + if (!gjs_string_from_utf8(cx, strv[i], elems[i])) + return false; } - if (g_arg_info_may_be_null(arg_info)) - flags |= GjsArgumentFlags::MAY_BE_NULL; - if (g_arg_info_is_caller_allocates(arg_info)) - flags |= GjsArgumentFlags::CALLER_ALLOCATES; + JSObject* obj = JS::NewArrayObject(cx, elems); + if (!obj) + return false; - return gjs_value_to_gi_argument( - context, value, &type_info, g_base_info_get_name(arg_info), - (g_arg_info_is_return_value(arg_info) ? GJS_ARGUMENT_RETURN_VALUE - : GJS_ARGUMENT_ARGUMENT), - g_arg_info_get_ownership_transfer(arg_info), flags, arg); + value_out.setObject(*obj); + return true; } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list( - JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* type_info, + JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo type_info, GITransfer transfer, T* list) { static_assert(std::is_same_v || std::is_same_v); JS::RootedValueVector elems(cx); - GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); - - g_assert(param_info); + GI::AutoTypeInfo element_type{type_info.element_type()}; GIArgument arg; for (size_t i = 0; list; list = list->next, ++i) { - g_type_info_argument_from_hash_pointer(param_info, list->data, &arg); + element_type.argument_from_hash_pointer(list->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } - if (!gjs_value_from_gi_argument(cx, elems[i], param_info, + if (!gjs_value_from_gi_argument(cx, elems[i], element_type, GJS_ARGUMENT_LIST_ELEMENT, transfer, &arg)) return false; @@ -1923,43 +2339,30 @@ GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list( return true; } -template -GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list( - JSContext* cx, GITransfer transfer, GITypeInfo* type_info, - GjsArgumentFlags flags, GIArgument* arg) { - static_assert(std::is_same_v || std::is_same_v); - GjsSmartPointer list = gjs_arg_steal(arg); - - if (transfer == GI_TRANSFER_CONTAINER) - return true; - - GIArgument elem; - GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); - g_assert(param_info); - GITypeTag type_tag = g_type_info_get_tag(param_info); - - for (T* l = list; l; l = l->next) { - gjs_arg_set(&elem, l->data); +template +GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_basic_c_array( + JSContext* cx, JS::MutableHandleValueVector elems, GITypeTag element_tag, + GIArgument* arg, void* array, size_t length) { + using T = Gjs::Tag::RealT; + for (size_t i = 0; i < length; i++) { + gjs_arg_set(arg, *(static_cast(array) + i)); - if (!gjs_g_arg_release_internal(cx, transfer, param_info, type_tag, - GJS_ARGUMENT_LIST_ELEMENT, flags, - &elem)) { + if (!gjs_value_from_basic_gi_argument(cx, elems[i], element_tag, arg)) return false; - } } return true; } -template +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, - GITransfer transfer = GI_TRANSFER_EVERYTHING) { + const GI::TypeInfo element_type, 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)); + gjs_arg_set(arg, *(static_cast*>(array) + i)); - if (!gjs_value_from_gi_argument(cx, elems[i], param_info, + if (!gjs_value_from_gi_argument(cx, elems[i], element_type, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, arg)) return false; @@ -1969,120 +2372,181 @@ 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, GITransfer transfer, guint length, void* array) { - GITypeTag element_type; - guint i; - - element_type = g_type_info_get_tag(param_info); - - /* Special case array(guint8) */ - if (element_type == GI_TYPE_TAG_UINT8) { - JSObject* obj = gjs_byte_array_from_data_copy(context, length, array); - if (!obj) +static bool gjs_array_from_basic_c_array_internal( + JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, + size_t length, void* contents) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); + + // Special case array(uint8) + if (element_tag == GI_TYPE_TAG_UINT8) { + JSObject* u8array = gjs_byte_array_from_data_copy(cx, length, contents); + if (!u8array) return false; - value_p.setObject(*obj); + value_out.setObject(*u8array); return true; } - /* Special case array(unichar) to be a string in JS */ - if (element_type == GI_TYPE_TAG_UNICHAR) - return gjs_string_from_ucs4(context, (gunichar *) array, length, value_p); + // Special case array(unichar) to be a string in JS + if (element_tag == GI_TYPE_TAG_UNICHAR) { + return gjs_string_from_ucs4(cx, static_cast(contents), + length, value_out); + } // a null array pointer takes precedence over whatever `length` says - if (!array) { - JSObject* jsarray = JS::NewArrayObject(context, 0); - if (!jsarray) + if (!contents) { + JSObject* array = JS::NewArrayObject(cx, 0); + if (!array) return false; - value_p.setObject(*jsarray); + value_out.setObject(*array); return true; } - JS::RootedValueVector elems(context); + JS::RootedValueVector elems{cx}; if (!elems.resize(length)) { - JS_ReportOutOfMemory(context); + JS_ReportOutOfMemory(cx); return false; } GIArgument arg; - switch (element_type) { - /* Special cases handled above */ + switch (element_tag) { + // Special cases handled above case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UNICHAR: g_assert_not_reached(); + case GI_TYPE_TAG_BOOLEAN: - if (!fill_vector_from_carray( - context, elems, param_info, &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT8: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, + &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT16: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT16: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT32: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT32: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT64: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT64: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array( + cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_FLOAT: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, + &arg, contents, length)) return false; break; case GI_TYPE_TAG_DOUBLE: - if (!fill_vector_from_carray(context, elems, param_info, - &arg, array, length)) + if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, + &arg, contents, length)) + return false; + break; + + case GI_TYPE_TAG_GTYPE: + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, + &arg, contents, length)) return false; break; + case GI_TYPE_TAG_VOID: + gjs_throw(cx, "Unknown Array element-type %d", element_tag); + return false; + default: + g_assert_not_reached(); + } + + JSObject* array = JS::NewArrayObject(cx, elems); + if (!array) + return false; + + value_out.setObject(*array); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_array_from_carray_internal(JSContext* context, + JS::MutableHandleValue value_p, + GIArrayType array_type, + const GI::TypeInfo element_type, + GITransfer transfer, size_t length, + void* array) { + GITypeTag element_tag = element_type.tag(); + if (GI_TYPE_TAG_IS_BASIC(element_tag)) { + return gjs_array_from_basic_c_array_internal( + context, value_p, element_tag, length, array); + } + + // a null array pointer takes precedence over whatever `length` says + if (!array) { + JSObject* jsarray = JS::NewArrayObject(context, 0); + if (!jsarray) + return false; + value_p.setObject(*jsarray); + return true; + } + + JS::RootedValueVector elems(context); + if (!elems.resize(length)) { + JS_ReportOutOfMemory(context); + return false; + } + + GIArgument arg; + switch (element_tag) { case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(param_info); - GIInfoType info_type = interface_info.type(); + GI::AutoBaseInfo interface_info{element_type.interface()}; + GITypeTag storage_element_type = element_type.storage_type(); if (array_type != GI_ARRAY_TYPE_PTR_ARRAY && - (info_type == GI_INFO_TYPE_STRUCT || - info_type == GI_INFO_TYPE_UNION) && - !g_type_info_is_pointer(param_info)) { - 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); + (interface_info.is_struct() || interface_info.is_union() || + interface_info.is_enum_or_flags()) && + !element_type.is_pointer()) { + size_t element_size; + + if (auto union_info = interface_info.as()) { + element_size = union_info->size(); + } else if (auto struct_info = + interface_info.as()) { + element_size = struct_info->size(); + } else { + auto storage = + interface_info.as()->storage_type(); + element_size = basic_type_element_size(storage); + } - for (i = 0; i < length; i++) { - gjs_arg_set(&arg, - static_cast(array) + (struct_size * i)); + for (size_t i = 0; i < length; i++) { + auto value = static_cast(array) + (element_size * i); + // use the storage tag instead of element tag to handle + // enums and flags + set_arg_from_carray_element(&arg, storage_element_type, + value); if (!gjs_value_from_gi_argument( - context, elems[i], param_info, + context, elems[i], element_type, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) return false; } @@ -2091,22 +2555,20 @@ static bool gjs_array_from_carray_internal( } } /* 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_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: - if (!fill_vector_from_carray(context, elems, param_info, + if (!fill_vector_from_carray(context, elems, element_type, &arg, array, length, transfer)) return false; break; - case GI_TYPE_TAG_VOID: default: - gjs_throw(context, "Unknown Array element-type %d", element_type); - return false; + // Basic types handled above + gjs_throw(context, "Unknown Array element-type %s", + element_type.display_string()); + return false; } JSObject* obj = JS::NewArrayObject(context, elems); @@ -2119,39 +2581,41 @@ static bool gjs_array_from_carray_internal( } GJS_JSAPI_RETURN_CONVENTION -static bool gjs_array_from_fixed_size_array(JSContext* context, +static bool gjs_array_from_fixed_size_array(JSContext* cx, JS::MutableHandleValue value_p, - GITypeInfo* type_info, + const GI::TypeInfo type_info, GITransfer transfer, void* array) { - gint length; - - length = g_type_info_get_array_fixed_size(type_info); + Maybe length = type_info.array_fixed_size(); + g_assert(length); - g_assert (length != -1); - - 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, transfer, length, array); + return gjs_array_from_carray_internal(cx, value_p, type_info.array_type(), + type_info.element_type(), transfer, + *length, array); } -bool gjs_value_from_explicit_array(JSContext* context, +bool gjs_value_from_explicit_array(JSContext* cx, 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); + const GI::TypeInfo type_info, + GITransfer transfer, GIArgument* arg, + size_t length) { + return gjs_array_from_carray_internal(cx, value_p, type_info.array_type(), + type_info.element_type(), transfer, + length, gjs_arg_get(arg)); +} - return gjs_array_from_carray_internal( - context, value_p, g_type_info_get_array_type(type_info), param_info, - transfer, length, gjs_arg_get(arg)); +bool gjs_value_from_basic_explicit_array(JSContext* cx, + JS::MutableHandleValue value_out, + GITypeTag element_tag, GIArgument* arg, + size_t length) { + return gjs_array_from_basic_c_array_internal( + cx, value_out, element_tag, length, gjs_arg_get(arg)); } GJS_JSAPI_RETURN_CONVENTION -static bool gjs_array_from_boxed_array(JSContext* context, +static bool gjs_array_from_boxed_array(JSContext* cx, JS::MutableHandleValue value_p, GIArrayType array_type, - GITypeInfo* param_info, + const GI::TypeInfo element_type, GITransfer transfer, GIArgument* arg) { GArray *array; GPtrArray *ptr_array; @@ -2181,14 +2645,14 @@ static bool gjs_array_from_boxed_array(JSContext* context, g_assert_not_reached(); } - return gjs_array_from_carray_internal(context, value_p, array_type, - param_info, transfer, length, data); + return gjs_array_from_carray_internal(cx, value_p, array_type, element_type, + transfer, length, data); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, - GITypeInfo* param_info, GITransfer transfer, - const GValue* gvalue) { + const GI::TypeInfo element_type, + GITransfer transfer, const GValue* gvalue) { void* data = nullptr; size_t length = 0; GIArrayType array_type; @@ -2200,13 +2664,12 @@ bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, array_type = g_type_is_a(value_gtype, G_TYPE_BYTE_ARRAY) ? GI_ARRAY_TYPE_BYTE_ARRAY : GI_ARRAY_TYPE_ARRAY; - auto* array = reinterpret_cast(g_value_get_boxed(gvalue)); + auto* array = Gjs::gvalue_get(gvalue); data = array->data; length = array->len; } else if (g_type_is_a(value_gtype, G_TYPE_PTR_ARRAY)) { array_type = GI_ARRAY_TYPE_PTR_ARRAY; - auto* ptr_array = - reinterpret_cast(g_value_get_boxed(gvalue)); + auto* ptr_array = Gjs::gvalue_get(gvalue); data = ptr_array->pdata; length = ptr_array->len; } else { @@ -2215,33 +2678,74 @@ bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, return false; } - return gjs_array_from_carray_internal(cx, value_p, array_type, param_info, + return gjs_array_from_carray_internal(cx, value_p, array_type, element_type, transfer, length, data); } -template -GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( +template +GJS_JSAPI_RETURN_CONVENTION static bool +fill_vector_from_basic_zero_terminated_c_array( + JSContext* cx, JS::MutableHandleValueVector elems, GITypeTag element_tag, + GIArgument* arg, void* c_array) { + using T = Gjs::Tag::RealT; + T* array = static_cast(c_array); + + for (size_t ix = 0; array[ix]; ix++) { + gjs_arg_set(arg, array[ix]); + + if (!elems.growBy(1)) { + JS_ReportOutOfMemory(cx); + return false; + } + + if (!gjs_value_from_basic_gi_argument(cx, elems[ix], element_tag, arg)) + return false; + } + + return true; +} + +GJS_JSAPI_RETURN_CONVENTION static bool +fill_vector_from_zero_terminated_pointer_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) - GITypeInfo* param_info, GIArgument* arg, void* c_array, + const GI::TypeInfo param_info, GIArgument* arg, void* c_array, GITransfer transfer = GI_TRANSFER_EVERYTHING) { - T* array = static_cast(c_array); + void** array = static_cast(c_array); for (size_t i = 0;; i++) { - if constexpr (std::is_scalar_v) { - if (!array[i]) - break; + 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, array[i]); - gjs_arg_set(arg, element_start); + if (!elems.growBy(1)) { + JS_ReportOutOfMemory(cx); + return false; } + if (!gjs_value_from_gi_argument(cx, elems[i], param_info, + GJS_ARGUMENT_ARRAY_ELEMENT, transfer, + arg)) + return false; + } + + return true; +} + +GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_non_pointer_carray( + JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) + const GI::TypeInfo param_info, GIArgument* arg, size_t element_size, + void* c_array, GITransfer transfer = GI_TRANSFER_EVERYTHING) { + uint8_t* element_start = reinterpret_cast(c_array); + + for (size_t i = 0;; i++) { + if (*element_start == 0 && + memcmp(element_start, element_start + 1, element_size - 1) == 0) + break; + + gjs_arg_set(arg, element_start); + element_start += element_size; + if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; @@ -2256,124 +2760,161 @@ GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( return true; } -GJS_JSAPI_RETURN_CONVENTION -static bool gjs_array_from_zero_terminated_c_array( - JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* param_info, - GITransfer transfer, void* c_array) { - GITypeTag element_type; +bool gjs_array_from_basic_zero_terminated_array( + JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, + void* c_array) { + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && + "Use gjs_array_from_zero_terminated_c_array for non-basic types"); - element_type = g_type_info_get_tag(param_info); + if (!c_array) { + // OK, but no conversion to do + value_out.setNull(); + return true; + } - /* Special case array(guint8) */ - if (element_type == GI_TYPE_TAG_UINT8) { - size_t len = strlen(static_cast(c_array)); - JSObject* obj = gjs_byte_array_from_data_copy(context, len, c_array); - if (!obj) + // Special case array(uint8_t) + if (element_tag == GI_TYPE_TAG_UINT8) { + size_t length = strlen(static_cast(c_array)); + JSObject* byte_array = + gjs_byte_array_from_data_copy(cx, length, c_array); + if (!byte_array) return false; - value_p.setObject(*obj); + + value_out.setObject(*byte_array); return true; } - /* Special case array(gunichar) to JS string */ - if (element_type == GI_TYPE_TAG_UNICHAR) - return gjs_string_from_ucs4(context, (gunichar *) c_array, -1, value_p); + // Special case array(gunichar) to JS string + if (element_tag == GI_TYPE_TAG_UNICHAR) { + return gjs_string_from_ucs4(cx, static_cast(c_array), -1, + value_out); + } - JS::RootedValueVector elems(context); + JS::RootedValueVector elems{cx}; GIArgument arg; - switch (element_type) { - /* Special cases handled above. */ - case GI_TYPE_TAG_UINT8: - case GI_TYPE_TAG_UNICHAR: - g_assert_not_reached(); + switch (element_tag) { case GI_TYPE_TAG_INT8: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT16: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT16: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT32: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT32: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT64: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT64: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_FLOAT: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_DOUBLE: - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) return false; break; - case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(param_info); + case GI_TYPE_TAG_GTYPE: + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + if (!fill_vector_from_basic_zero_terminated_c_array( + cx, &elems, element_tag, &arg, c_array)) + return false; + break; + // Boolean zero-terminated array makes no sense, because false is also + // zero + case GI_TYPE_TAG_BOOLEAN: + gjs_throw(cx, "Boolean zero-terminated array not supported"); + return false; + case GI_TYPE_TAG_VOID: + gjs_throw(cx, "Unknown element-type 'void'"); + return false; + default: + // UINT8 and UNICHAR are special cases handled above + g_assert_not_reached(); + } - if (!g_type_info_is_pointer(param_info) && - is_gvalue(interface_info)) { - if (!fill_vector_from_zero_terminated_carray( - context, elems, param_info, &arg, c_array)) + JSObject* array = JS::NewArrayObject(cx, elems); + if (!array) + return false; + + value_out.setObject(*array); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_array_from_zero_terminated_c_array( + JSContext* context, JS::MutableHandleValue value_p, + const GI::TypeInfo element_type, GITransfer transfer, void* c_array) { + GITypeTag element_tag = element_type.tag(); + + if (element_type.is_basic()) { + return gjs_array_from_basic_zero_terminated_array(context, value_p, + element_tag, c_array); + } + + JS::RootedValueVector elems(context); + + GIArgument arg; + switch (element_tag) { + case GI_TYPE_TAG_INTERFACE: { + GI::AutoBaseInfo interface_info{element_type.interface()}; + auto reg_info = interface_info.as(); + bool element_is_pointer = element_type.is_pointer(); + bool is_struct = reg_info->is_struct(); + + if (!element_is_pointer && is_struct) { + auto struct_info = interface_info.as(); + size_t element_size = struct_info->size(); + + if (!fill_vector_from_zero_terminated_non_pointer_carray( + context, elems, element_type, &arg, element_size, + c_array)) return false; break; } - if (!g_type_info_is_pointer(param_info)) { - gjs_throw(context, - "Flat C array of %s.%s not supported (see " - "https://gitlab.gnome.org/GNOME/cjs/-/issues/603)", - interface_info.ns(), interface_info.name()); - return false; - } - [[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_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, transfer)) + if (!fill_vector_from_zero_terminated_pointer_carray( + context, elems, element_type, &arg, c_array, transfer)) return false; break; - /* Boolean zero-terminated array makes no sense, because FALSE is also - * zero */ - case GI_TYPE_TAG_BOOLEAN: - gjs_throw(context, "Boolean zero-terminated array not supported"); - return false; - case GI_TYPE_TAG_VOID: default: - gjs_throw(context, "Unknown element-type %d", element_type); - return false; + // Handled in gjs_array_from_basic_zero_terminated_c_array() + gjs_throw(context, "Unknown element-type %s", + element_type.display_string()); + return false; } JSObject* obj = JS::NewArrayObject(context, elems); @@ -2386,11 +2927,15 @@ static bool gjs_array_from_zero_terminated_c_array( } bool gjs_object_from_g_hash(JSContext* context, JS::MutableHandleValue value_p, - GITypeInfo* key_param_info, - GITypeInfo* val_param_info, GITransfer transfer, + const GI::TypeInfo key_type, + const GI::TypeInfo val_type, GITransfer transfer, GHashTable* hash) { GHashTableIter iter; + g_assert((!GI_TYPE_TAG_IS_BASIC(key_type.tag()) || + !GI_TYPE_TAG_IS_BASIC(val_type.tag())) && + "use gjs_value_from_basic_ghash() instead"); + // a NULL hash table becomes a null JS value if (hash==NULL) { value_p.setNull(); @@ -2411,150 +2956,289 @@ bool gjs_object_from_g_hash(JSContext* context, JS::MutableHandleValue value_p, void* val_pointer; GIArgument keyarg, valarg; 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_gi_argument(context, &keyjs, key_param_info, + key_type.argument_from_hash_pointer(key_pointer, &keyarg); + if (!gjs_value_from_gi_argument(context, &keyjs, key_type, GJS_ARGUMENT_HASH_ELEMENT, transfer, &keyarg)) return false; - keystr = JS::ToString(context, keyjs); - if (!keystr) - return false; - - JS::UniqueChars keyutf8(JS_EncodeStringToUTF8(context, keystr)); - if (!keyutf8) + JS::RootedId key{context}; + if (!JS_ValueToId(context, keyjs, &key)) return false; - g_type_info_argument_from_hash_pointer(val_param_info, val_pointer, - &valarg); - if (!gjs_value_from_gi_argument(context, &valjs, val_param_info, + val_type.argument_from_hash_pointer(val_pointer, &valarg); + if (!gjs_value_from_gi_argument(context, &valjs, val_type, GJS_ARGUMENT_HASH_ELEMENT, transfer, - &valarg)) - return false; - - if (!JS_DefineProperty(context, obj, keyutf8.get(), valjs, - JSPROP_ENUMERATE)) + &valarg) || + !JS_DefinePropertyById(context, obj, key, valjs, JSPROP_ENUMERATE)) return false; } return true; } -bool gjs_value_from_gi_argument(JSContext* context, - JS::MutableHandleValue value_p, - GITypeInfo* type_info, - GjsArgumentType argument_type, - GITransfer transfer, GIArgument* arg) { - GITypeTag type_tag = g_type_info_get_tag(type_info); +bool gjs_value_from_basic_fixed_size_array_gi_argument( + JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, + size_t fixed_size, GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument fixed array of %s to JS::Value", + gi_type_tag_to_string(element_tag)); + + void* c_array = gjs_arg_get(arg); + if (!c_array) { + // OK, but no conversion to do + value_out.setNull(); + return true; + } + + return gjs_array_from_basic_c_array_internal(cx, value_out, element_tag, + fixed_size, c_array); +} +bool gjs_value_from_byte_array_gi_argument(JSContext* cx, + JS::MutableHandleValue value_out, + GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, - "Converting GIArgument %s to JS::Value", - g_type_tag_to_string(type_tag)); + "Converting GIArgument byte array to JS::Value"); - switch (type_tag) { - case GI_TYPE_TAG_VOID: - // If the argument is a pointer, convert to null to match our - // in handling. - if (g_type_info_is_pointer(type_info)) - value_p.setNull(); - else - value_p.setUndefined(); - break; + auto* byte_array = gjs_arg_get(arg); + if (!byte_array) { + value_out.setNull(); + return true; + } - case GI_TYPE_TAG_BOOLEAN: - value_p.setBoolean(gjs_arg_get(arg)); - break; + JSObject* u8array = gjs_byte_array_from_byte_array(cx, byte_array); + if (!u8array) + return false; - case GI_TYPE_TAG_INT32: - value_p.setInt32(gjs_arg_get(arg)); - break; + value_out.setObject(*u8array); + return true; +} - case GI_TYPE_TAG_UINT32: - value_p.setNumber(gjs_arg_get(arg)); - break; +bool gjs_value_from_basic_garray_gi_argument(JSContext* cx, + JS::MutableHandleValue value_out, + GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument GArray of %s to JS::Value", + gi_type_tag_to_string(element_tag)); - case GI_TYPE_TAG_INT64: - value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); - break; + auto* garray = gjs_arg_get(arg); + if (!garray) { + value_out.setNull(); + return true; + } - case GI_TYPE_TAG_UINT64: - value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); - break; + return gjs_array_from_basic_c_array_internal(cx, value_out, element_tag, + garray->len, garray->data); +} - case GI_TYPE_TAG_UINT16: - value_p.setInt32(gjs_arg_get(arg)); - break; +bool gjs_value_from_basic_gptrarray_gi_argument( + JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument GPtrArray of %s to JS::Value", + gi_type_tag_to_string(element_tag)); - case GI_TYPE_TAG_INT16: - value_p.setInt32(gjs_arg_get(arg)); - break; + auto* ptr_array = gjs_arg_get(arg); + if (!ptr_array) { + value_out.setNull(); + return true; + } - case GI_TYPE_TAG_UINT8: - value_p.setInt32(gjs_arg_get(arg)); - break; + return gjs_array_from_basic_c_array_internal( + cx, value_out, element_tag, ptr_array->len, ptr_array->pdata); +} - case GI_TYPE_TAG_INT8: - value_p.setInt32(gjs_arg_get(arg)); - break; +template +GJS_JSAPI_RETURN_CONVENTION static bool array_from_basic_linked_list( + JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, + T* list) { + static_assert(std::is_same_v || std::is_same_v); + g_assert( + GI_TYPE_TAG_IS_BASIC(element_tag) && + "use gjs_array_from_g_list() for lists containing non-basic types"); - case GI_TYPE_TAG_FLOAT: - value_p.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); - break; + GIArgument arg; + JS::RootedValueVector elems{cx}; - case GI_TYPE_TAG_DOUBLE: - value_p.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); - break; + for (size_t i = 0; list; list = list->next, ++i) { + // for basic types, type tag == storage type + gi_type_tag_argument_from_hash_pointer(element_tag, list->data, &arg); - case GI_TYPE_TAG_GTYPE: - { - GType gtype = gjs_arg_get(arg); - if (gtype == 0) { - value_p.setNull(); - return true; + if (!elems.growBy(1)) { + JS_ReportOutOfMemory(cx); + return false; } - JSObject* obj = gjs_gtype_create_gtype_wrapper(context, gtype); - if (!obj) + if (!gjs_value_from_basic_gi_argument(cx, elems[i], element_tag, &arg)) return false; + } - value_p.setObject(*obj); + JS::RootedObject obj{cx, JS::NewArrayObject(cx, elems)}; + if (!obj) + return false; + + value_out.setObject(*obj); + + return true; +} + +bool gjs_array_from_basic_glist_gi_argument(JSContext* cx, + JS::MutableHandleValue value_out, + GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GArgument glist to JS::Value"); + return array_from_basic_linked_list(cx, value_out, element_tag, + gjs_arg_get(arg)); +} + +bool gjs_array_from_basic_gslist_gi_argument(JSContext* cx, + JS::MutableHandleValue value_out, + GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GArgument gslist to JS::Value"); + return array_from_basic_linked_list(cx, value_out, element_tag, + gjs_arg_get(arg)); +} + +bool gjs_value_from_basic_ghash(JSContext* cx, JS::MutableHandleValue value_out, + GITypeTag key_tag, GITypeTag value_tag, + GHashTable* hash) { + g_assert( + GI_TYPE_TAG_IS_BASIC(key_tag) && + "use gjs_object_from_g_hash() for hashes with non-basic key types"); + g_assert( + GI_TYPE_TAG_IS_BASIC(value_tag) && + "use gjs_object_from_g_hash() for hashes with non-basic value types"); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument ghash to JS::Value"); + + // a NULL hash table becomes a null JS value + if (!hash) { + value_out.setNull(); return true; } - break; - case GI_TYPE_TAG_UNICHAR: { - char32_t value = gjs_arg_get(arg); + JS::RootedObject obj{cx, JS_NewPlainObject(cx)}; + if (!obj) + return false; - // Preserve the bidirectional mapping between 0 and "" - if (value == 0) { - value_p.set(JS_GetEmptyStringValue(context)); - return true; - } else if (!g_unichar_validate(value)) { - gjs_throw(context, "Invalid unicode codepoint %" G_GUINT32_FORMAT, - value); + JS::RootedValue v_key{cx}, v_val{cx}; + JS::RootedId key{cx}; + GIArgument key_arg, value_arg; + GHashTableIter iter; + void* key_pointer; + void* val_pointer; + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) { + gi_type_tag_argument_from_hash_pointer(key_tag, key_pointer, &key_arg); + gi_type_tag_argument_from_hash_pointer(value_tag, val_pointer, + &value_arg); + if (!gjs_value_from_basic_gi_argument(cx, &v_key, key_tag, &key_arg) || + !JS_ValueToId(cx, v_key, &key) || + !gjs_value_from_basic_gi_argument(cx, &v_val, value_tag, + &value_arg) || + !JS_DefinePropertyById(cx, obj, key, v_val, JSPROP_ENUMERATE)) return false; + } + + value_out.setObject(*obj); + return true; +} + +bool gjs_value_from_gi_argument(JSContext* context, + JS::MutableHandleValue value_p, + const GI::TypeInfo type_info, + GjsArgumentType argument_type, + GITransfer transfer, GIArgument* arg) { + GITypeTag type_tag = type_info.tag(); + if (type_info.is_basic()) { + return gjs_value_from_basic_gi_argument(context, value_p, type_tag, + arg); + } + + if (type_tag == GI_TYPE_TAG_GLIST) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + return gjs_array_from_basic_glist_gi_argument( + context, value_p, element_type.tag(), arg); } + // else fall through to generic marshaller + } - char utf8[7]; - int bytes = g_unichar_to_utf8(value, utf8); - return gjs_string_from_utf8_n(context, utf8, bytes, value_p); + if (type_tag == GI_TYPE_TAG_GSLIST) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + return gjs_array_from_basic_gslist_gi_argument( + context, value_p, element_type.tag(), arg); + } + // else fall through to generic marshaller } - case GI_TYPE_TAG_FILENAME: - case GI_TYPE_TAG_UTF8: { - const char* str = gjs_arg_get(arg); - if (!str) { - value_p.setNull(); - return true; + if (type_tag == GI_TYPE_TAG_GHASH) { + GI::AutoTypeInfo key_type{type_info.key_type()}; + GI::AutoTypeInfo value_type{type_info.value_type()}; + if (key_type.is_basic() && value_type.is_basic()) { + return gjs_value_from_basic_ghash(context, value_p, key_type.tag(), + value_type.tag(), + gjs_arg_get(arg)); } + // else fall through to generic marshaller + } + + if (type_tag == GI_TYPE_TAG_ARRAY) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + switch (type_info.array_type()) { + case GI_ARRAY_TYPE_C: { + if (type_info.is_zero_terminated()) { + return gjs_array_from_basic_zero_terminated_array( + context, value_p, element_type.tag(), + gjs_arg_get(arg)); + } + + Maybe fixed_size = type_info.array_fixed_size(); + g_assert(fixed_size && + "arrays with length param handled in " + "gjs_value_from_basic_explicit_array()"); + return gjs_value_from_basic_fixed_size_array_gi_argument( + context, value_p, element_type.tag(), *fixed_size, arg); + } + + case GI_ARRAY_TYPE_BYTE_ARRAY: + return gjs_value_from_byte_array_gi_argument(context, + value_p, arg); - if (type_tag == GI_TYPE_TAG_FILENAME) - return gjs_string_from_filename(context, str, -1, value_p); + case GI_ARRAY_TYPE_ARRAY: + return gjs_value_from_basic_garray_gi_argument( + context, value_p, element_type.tag(), arg); - return gjs_string_from_utf8(context, str, value_p); + case GI_ARRAY_TYPE_PTR_ARRAY: + return gjs_value_from_basic_gptrarray_gi_argument( + context, value_p, element_type.tag(), arg); + } + } + // else fall through to generic marshaller } + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GIArgument %s to JS::Value", + gi_type_tag_to_string(type_tag)); + + switch (type_tag) { + case GI_TYPE_TAG_VOID: + g_assert(type_info.is_pointer() && + "non-pointers should be handled by " + "gjs_value_from_basic_gi_argument()"); + // If the argument is a pointer, convert to null to match our + // in handling. + value_p.setNull(); + return true; + case GI_TYPE_TAG_ERROR: { GError* ptr = gjs_arg_get(arg); if (!ptr) { @@ -2572,65 +3256,51 @@ bool gjs_value_from_gi_argument(JSContext* context, case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - g_assert(interface_info); + GI::AutoBaseInfo interface_info{type_info.interface()}; - GIInfoType interface_type = interface_info.type(); - - if (interface_type == GI_INFO_TYPE_UNRESOLVED) { - gjs_throw(context, - "Unable to resolve arg type '%s'", - g_base_info_get_name(interface_info)); + if (interface_info.is_unresolved()) { + gjs_throw(context, "Unable to resolve arg type '%s'", + interface_info.name()); return false; } - /* Enum/Flags are aren't pointer types, unlike the other interface subtypes */ - if (interface_type == GI_INFO_TYPE_ENUM) { - int64_t value_int64 = _gjs_enum_from_int( - interface_info, - gjs_arg_get(arg)); - - if (!_gjs_enum_value_is_valid(context, interface_info, - value_int64)) - return false; - - value_p.setNumber(static_cast(value_int64)); - return true; - } + // Enum/Flags are aren't pointer types, unlike the other interface + // subtypes + if (auto enum_info = interface_info.as()) { + int64_t value_int64 = + enum_info->enum_from_int(gjs_arg_get(arg)); + + if (interface_info.is_flags()) { + GType gtype = enum_info->gtype(); + + if (gtype != G_TYPE_NONE) { + // Check to make sure 32 bit flag + if (static_cast(value_int64) != value_int64) { + gjs_throw(context, + "0x%" PRIx64 + " is not a valid value for flags %s", + value_int64, g_type_name(gtype)); + return false; + } - if (interface_type == GI_INFO_TYPE_FLAGS) { - int64_t value_int64 = _gjs_enum_from_int( - interface_info, - gjs_arg_get(arg)); - - GType gtype = g_registered_type_info_get_g_type( - interface_info.as()); - - if (gtype != G_TYPE_NONE) { - /* check make sure 32 bit flag */ - if (static_cast(value_int64) != value_int64) { - // Not a 32-bit integer - gjs_throw(context, - "0x%" PRIx64 - " is not a valid value for flags %s", - value_int64, g_type_name(gtype)); - return false; + // Pass only valid values + Gjs::AutoTypeClass gflags_class{gtype}; + value_int64 &= gflags_class->mask; } - - /* Pass only valid values*/ - GjsAutoTypeClass gflags_class(gtype); - value_int64 &= gflags_class->mask; + } else { + if (!_gjs_enum_value_is_valid(context, enum_info.value(), + value_int64)) + return false; } value_p.setNumber(static_cast(value_int64)); return true; } - if (interface_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_foreign(interface_info.as())) { + if (auto struct_info = interface_info.as(); + struct_info && struct_info->is_foreign()) { return gjs_struct_foreign_convert_from_gi_argument( - context, value_p, interface_info, arg); + context, value_p, struct_info.value(), arg); } /* Everything else is a pointer type, NULL is the easy case */ @@ -2639,9 +3309,8 @@ bool gjs_value_from_gi_argument(JSContext* context, return true; } - if (interface_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_gtype_struct( - interface_info.as())) { + if (auto struct_info = interface_info.as(); + struct_info && struct_info->is_gtype_struct()) { /* XXX: here we make the implicit assumption that GTypeClass is the same as GTypeInterface. This is true for the GType field, which is what we use, but not for the rest of the structure! @@ -2655,8 +3324,8 @@ bool gjs_value_from_gi_argument(JSContext* context, return gjs_lookup_object_constructor(context, gtype, value_p); } - GType gtype = g_registered_type_info_get_g_type( - interface_info.as()); + GType gtype = + interface_info.as()->gtype(); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); @@ -2680,22 +3349,21 @@ bool gjs_value_from_gi_argument(JSContext* context, return true; } - if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) { - if (arg::is_gdk_atom(interface_info)) { - GjsAutoFunctionInfo atom_name_fun = - g_struct_info_find_method(interface_info, "name"); - GIArgument atom_name_ret; + if (auto struct_info = interface_info.as()) { + if (struct_info->is_gdk_atom()) { + GI::AutoFunctionInfo atom_name_fun{ + struct_info->method("name").value()}; - GjsAutoError error = nullptr; - if (!g_function_info_invoke(atom_name_fun, arg, 1, nullptr, - 0, &atom_name_ret, - error.out())) { + GIArgument atom_name_ret; + Gjs::GErrorResult<> result = + atom_name_fun.invoke({{*arg}}, {}, &atom_name_ret); + if (result.isErr()) { gjs_throw(context, "Failed to call gdk_atom_name(): %s", - error->message); + result.inspectErr()->message); return false; } - GjsAutoChar name = gjs_arg_get(&atom_name_ret); + Gjs::AutoChar name{gjs_arg_get(&atom_name_ret)}; if (g_strcmp0("NONE", name) == 0) { value_p.setNull(); return true; @@ -2721,11 +3389,11 @@ bool gjs_value_from_gi_argument(JSContext* context, if (transfer == GI_TRANSFER_EVERYTHING) obj = BoxedInstance::new_for_c_struct( - context, interface_info, gjs_arg_get(arg)); + context, struct_info.value(), gjs_arg_get(arg)); else obj = BoxedInstance::new_for_c_struct( - context, interface_info, gjs_arg_get(arg), - BoxedInstance::NoCopy()); + context, struct_info.value(), gjs_arg_get(arg), + BoxedInstance::NoCopy{}); if (!obj) return false; @@ -2734,10 +3402,9 @@ bool gjs_value_from_gi_argument(JSContext* context, return true; } - if (interface_type == GI_INFO_TYPE_UNION) { + if (auto union_info = interface_info.as()) { JSObject* obj = UnionInstance::new_for_c_union( - context, interface_info.as(), - gjs_arg_get(arg)); + context, union_info.value(), gjs_arg_get(arg)); if (!obj) return false; @@ -2757,9 +3424,8 @@ bool gjs_value_from_gi_argument(JSContext* context, g_type_is_a(gtype, G_TYPE_FLAGS)) { /* Should have been handled above */ gjs_throw(context, - "Type %s registered for unexpected interface_type %d", - g_type_name(gtype), - interface_type); + "Type %s registered for unexpected interface_type %s", + g_type_name(gtype), interface_info.type_string()); return false; } @@ -2800,26 +3466,21 @@ bool gjs_value_from_gi_argument(JSContext* context, return true; } - if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { - if (g_type_info_is_zero_terminated(type_info)) { - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - g_assert(param_info != nullptr); - + if (type_info.array_type() == GI_ARRAY_TYPE_C) { + if (type_info.is_zero_terminated()) { return gjs_array_from_zero_terminated_c_array( - context, value_p, param_info, transfer, + context, value_p, type_info.element_type(), 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)); + g_assert(!type_info.array_length_index() && + "Use gjs_value_from_explicit_array() for arrays with " + "length param"); 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) { + } else if (type_info.array_type() == GI_ARRAY_TYPE_BYTE_ARRAY) { auto* byte_array = gjs_arg_get(arg); JSObject* array = gjs_byte_array_from_byte_array(context, byte_array); @@ -2831,13 +3492,9 @@ bool gjs_value_from_gi_argument(JSContext* context, value_p.setObject(*array); } else { // this assumes the array type is GArray or GPtrArray - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - g_assert(param_info != nullptr); - return gjs_array_from_boxed_array( - context, value_p, g_type_info_get_array_type(type_info), - param_info, transfer, arg); + context, value_p, type_info.array_type(), + type_info.element_type(), transfer, arg); } break; @@ -2849,32 +3506,52 @@ bool gjs_value_from_gi_argument(JSContext* context, gjs_arg_get(arg)); case GI_TYPE_TAG_GHASH: - { - GjsAutoTypeInfo key_param_info = - g_type_info_get_param_type(type_info, 0); - GjsAutoTypeInfo val_param_info = - g_type_info_get_param_type(type_info, 1); - g_assert(key_param_info != nullptr); - g_assert(val_param_info != nullptr); - - return gjs_object_from_g_hash(context, value_p, key_param_info, - val_param_info, transfer, + return gjs_object_from_g_hash(context, value_p, type_info.key_type(), + type_info.value_type(), transfer, gjs_arg_get(arg)); - } - break; default: + // basic types handled in gjs_value_from_basic_gi_argument() g_warning("Unhandled type %s converting GIArgument to JavaScript", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); return false; } return true; } +///// RELEASE MARSHALLERS ////////////////////////////////////////////////////// +// These marshaller function are responsible for releasing the values stored in +// GIArgument after a C function call succeeds or fails. + +template +GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list( + JSContext* cx, GITransfer transfer, const GI::TypeInfo type_info, + GjsArgumentFlags flags, GIArgument* arg) { + static_assert(std::is_same_v || std::is_same_v); + Gjs::SmartPointer list{gjs_arg_steal(arg)}; + + if (transfer == GI_TRANSFER_CONTAINER) + return true; + + GIArgument elem; + GI::AutoTypeInfo element_type{type_info.element_type()}; + + for (T* l = list; l; l = l->next) { + gjs_arg_set(&elem, l->data); + + if (!gjs_g_arg_release_internal( + cx, transfer, element_type, element_type.tag(), + GJS_ARGUMENT_LIST_ELEMENT, flags, &elem)) + return false; + } + + return true; +} + struct GHR_closure { JSContext *context; - GjsAutoTypeInfo key_param_info, val_param_info; + GI::AutoTypeInfo key_type, val_type; GITransfer transfer; GjsArgumentFlags flags; bool failed; @@ -2883,18 +3560,22 @@ struct GHR_closure { static gboolean gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { GHR_closure *c = (GHR_closure *) user_data; + + GITypeTag key_tag = c->key_type.tag(); + GITypeTag val_tag = c->val_type.tag(); + g_assert( + (!GI_TYPE_TAG_IS_BASIC(key_tag) || !GI_TYPE_TAG_IS_BASIC(val_tag)) && + "use basic_ghash_release() instead"); + GIArgument key_arg, val_arg; gjs_arg_set(&key_arg, key); 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), - GJS_ARGUMENT_HASH_ELEMENT, c->flags, - &key_arg)) + if (!gjs_g_arg_release_internal(c->context, c->transfer, c->key_type, + key_tag, GJS_ARGUMENT_HASH_ELEMENT, + c->flags, &key_arg)) c->failed = true; - GITypeTag val_type = g_type_info_get_tag(c->val_param_info); - - switch (val_type) { + switch (val_tag) { case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_INT64: @@ -2904,9 +3585,107 @@ gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { default: if (!gjs_g_arg_release_internal( - c->context, c->transfer, c->val_param_info, val_type, + c->context, c->transfer, c->val_type, val_tag, GJS_ARGUMENT_HASH_ELEMENT, c->flags, &val_arg)) - c->failed = true; + c->failed = true; + } + + return true; +} + +enum class ArrayReleaseType { + EXPLICIT_LENGTH, + ZERO_TERMINATED, +}; + +template +static inline void release_basic_array_internal(GITypeTag element_tag, + Maybe length, + void** array) { + if (!Gjs::basic_type_needs_release(element_tag)) + return; + + for (size_t ix = 0;; ix++) { + if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { + if (!array[ix]) + break; + } + if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { + if (ix == *length) + break; + } + + g_free(array[ix]); + } +} + +template +static inline bool gjs_gi_argument_release_array_internal( + JSContext* cx, GITransfer element_transfer, GjsArgumentFlags flags, + const GI::TypeInfo element_type, Maybe length, GIArgument* arg) { + Gjs::AutoPointer arg_array{ + gjs_arg_steal(arg)}; + + if (!arg_array) + return true; + + if (element_transfer != GI_TRANSFER_EVERYTHING) + return true; + + if (element_type.is_basic()) { + release_basic_array_internal(element_type.tag(), length, + arg_array.as()); + return true; + } + + if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { + if (*length == 0) + return true; + } + + if (flags & GjsArgumentFlags::ARG_IN && + !type_needs_release(element_type, element_type.tag())) + return true; + + if (flags & GjsArgumentFlags::ARG_OUT && + !type_needs_out_release(element_type, element_type.tag())) + return true; + + GITypeTag type_tag = element_type.tag(); + size_t element_size = gjs_type_get_element_size(type_tag, element_type); + if G_UNLIKELY (element_size == 0) + return true; + + bool is_pointer = element_type.is_pointer(); + for (size_t i = 0;; i++) { + GIArgument elem; + auto* element_start = &arg_array[i * element_size]; + auto* pointer = + is_pointer ? *reinterpret_cast(element_start) : nullptr; + + if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { + if (is_pointer) { + if (!pointer) + break; + } else if (*element_start == 0 && + memcmp(element_start, element_start + 1, + element_size - 1) == 0) { + break; + } + } + + gjs_arg_set(&elem, is_pointer ? pointer : element_start); + JS::AutoSaveExceptionState saved_exc(cx); + if (!gjs_g_arg_release_internal(cx, element_transfer, element_type, + type_tag, GJS_ARGUMENT_ARRAY_ELEMENT, + flags, &elem)) { + return false; + } + + if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { + if (i == *length - 1) + break; + } } return true; @@ -2921,34 +3700,226 @@ constexpr static bool is_transfer_in_nothing(GITransfer transfer, return (transfer == GI_TRANSFER_NOTHING) && (flags & GjsArgumentFlags::ARG_IN); } +static void release_basic_type_internal(GITypeTag type_tag, GIArgument* arg) { + if (is_string_type(type_tag)) + g_clear_pointer(&gjs_arg_member(arg), g_free); +} + +template +static void basic_linked_list_release(GITransfer transfer, + GITypeTag element_tag, GIArgument* arg) { + static_assert(std::is_same_v || std::is_same_v); + g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && + "use gjs_g_arg_release_g_list() for lists with non-basic types"); + + Gjs::SmartPointer list = gjs_arg_steal(arg); + + if (transfer == GI_TRANSFER_CONTAINER) + return; + + GIArgument elem; + for (T* l = list; l; l = l->next) { + gjs_arg_set(&elem, l->data); + release_basic_type_internal(element_tag, &elem); + } +} + +void gjs_gi_argument_release_basic_glist(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument GList"); + basic_linked_list_release(transfer, element_tag, arg); +} + +void gjs_gi_argument_release_basic_gslist(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument GSList"); + basic_linked_list_release(transfer, element_tag, arg); +} + +void gjs_gi_argument_release_basic_ghash(GITransfer transfer, GITypeTag key_tag, + GITypeTag value_tag, GIArgument* arg) { + g_assert(GI_TYPE_TAG_IS_BASIC(key_tag) && GI_TYPE_TAG_IS_BASIC(value_tag)); + + if (!gjs_arg_get(arg)) + return; + + Gjs::AutoPointer hash_table{ + gjs_arg_steal(arg)}; + if (transfer == GI_TRANSFER_CONTAINER) { + g_hash_table_remove_all(hash_table); + } else { + std::array data{key_tag, value_tag}; + g_hash_table_foreach_steal( + hash_table, + [](void* key, void* val, void* user_data) -> gboolean { + auto* tags = static_cast*>(user_data); + GITypeTag key_tag = (*tags)[0], value_tag = (*tags)[1]; + GIArgument key_arg, val_arg; + gjs_arg_set(&key_arg, key); + gjs_arg_set(&val_arg, val); + release_basic_type_internal(key_tag, &key_arg); + + switch (value_tag) { + case GI_TYPE_TAG_DOUBLE: + case GI_TYPE_TAG_FLOAT: + case GI_TYPE_TAG_INT64: + case GI_TYPE_TAG_UINT64: + g_clear_pointer(&gjs_arg_member(&val_arg), + g_free); + break; + + default: + release_basic_type_internal(value_tag, &val_arg); + } + + return true; + }, + &data); + } +} + +void gjs_gi_argument_release_basic_c_array(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + if (!gjs_arg_get(arg)) + return; + + if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) + g_clear_pointer(&gjs_arg_member(arg), g_strfreev); + else + g_clear_pointer(&gjs_arg_member(arg), g_free); +} + +void gjs_gi_argument_release_basic_c_array(GITransfer transfer, + GITypeTag element_tag, size_t length, + GIArgument* arg) { + if (!gjs_arg_get(arg)) + return; + + Gjs::AutoPointer array{gjs_arg_steal(arg)}; + + if (!is_string_type(element_tag) || transfer == GI_TRANSFER_CONTAINER) + return; + + for (size_t ix = 0; ix < length; ix++) + g_free(array[ix]); +} + +void gjs_gi_argument_release_basic_garray(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + if (!gjs_arg_get(arg)) + return; + + Gjs::AutoPointer array{ + gjs_arg_steal(arg)}; + + if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) + return; + + for (size_t ix = 0; ix < array->len; ix++) + g_free(g_array_index(array, char*, ix)); +} + +void gjs_gi_argument_release_byte_array(GIArgument* arg) { + if (!gjs_arg_get(arg)) + return; + + g_clear_pointer(&gjs_arg_member(arg), g_byte_array_unref); +} + +void gjs_gi_argument_release_basic_gptrarray(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + if (!gjs_arg_get(arg)) + return; + + Gjs::AutoPointer array{ + gjs_arg_steal(arg)}; + + if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) + return; + + g_ptr_array_foreach( + array, [](void* ptr, void*) { g_free(ptr); }, nullptr); +} + GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( - JSContext* context, GITransfer transfer, GITypeInfo* type_info, + JSContext* context, GITransfer transfer, const GI::TypeInfo type_info, GITypeTag type_tag, [[maybe_unused]] GjsArgumentType argument_type, GjsArgumentFlags flags, GIArgument* arg) { g_assert(transfer != GI_TRANSFER_NOTHING || flags != GjsArgumentFlags::NONE); + if (type_info.is_basic()) { + release_basic_type_internal(type_tag, arg); + return true; + } + + if (type_tag == GI_TYPE_TAG_GLIST) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + basic_linked_list_release(transfer, element_type.tag(), arg); + return true; + } + // else fall through to generic marshaller + } + + if (type_tag == GI_TYPE_TAG_GSLIST) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + basic_linked_list_release(transfer, element_type.tag(), + arg); + return true; + } + // else fall through to generic marshaller + } + + if (type_tag == GI_TYPE_TAG_GHASH) { + GI::AutoTypeInfo key_type{type_info.key_type()}; + GI::AutoTypeInfo value_type{type_info.value_type()}; + if (key_type.is_basic() && value_type.is_basic()) { + gjs_gi_argument_release_basic_ghash(transfer, key_type.tag(), + value_type.tag(), arg); + return true; + } + // else fall through to generic marshaller + } + + if (type_tag == GI_TYPE_TAG_ARRAY) { + GI::AutoTypeInfo element_type{type_info.element_type()}; + if (element_type.is_basic()) { + switch (type_info.array_type()) { + case GI_ARRAY_TYPE_C: + gjs_gi_argument_release_basic_c_array( + transfer, element_type.tag(), arg); + return true; + case GI_ARRAY_TYPE_ARRAY: + gjs_gi_argument_release_basic_garray( + transfer, element_type.tag(), arg); + return true; + case GI_ARRAY_TYPE_BYTE_ARRAY: + gjs_gi_argument_release_byte_array(arg); + return true; + case GI_ARRAY_TYPE_PTR_ARRAY: + gjs_gi_argument_release_basic_gptrarray( + transfer, element_type.tag(), arg); + return true; + default: + g_assert_not_reached(); + } + } + // else fall through to generic marshaller + } + switch (type_tag) { case GI_TYPE_TAG_VOID: - case GI_TYPE_TAG_BOOLEAN: - case GI_TYPE_TAG_INT8: - case GI_TYPE_TAG_UINT8: - case GI_TYPE_TAG_INT16: - case GI_TYPE_TAG_UINT16: - case GI_TYPE_TAG_INT32: - case GI_TYPE_TAG_UINT32: - case GI_TYPE_TAG_INT64: - case GI_TYPE_TAG_UINT64: - case GI_TYPE_TAG_FLOAT: - case GI_TYPE_TAG_DOUBLE: - case GI_TYPE_TAG_UNICHAR: - case GI_TYPE_TAG_GTYPE: - break; - - case GI_TYPE_TAG_FILENAME: - case GI_TYPE_TAG_UTF8: - g_clear_pointer(&gjs_arg_member(arg), g_free); + g_assert(type_info.is_pointer() && + "non-pointer should be handled by " + "release_basic_type_internal()"); break; case GI_TYPE_TAG_ERROR: @@ -2958,26 +3929,22 @@ static bool gjs_g_arg_release_internal( case GI_TYPE_TAG_INTERFACE: { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(type_info); - g_assert(interface_info); - - GIInfoType interface_type = interface_info.type(); + GI::AutoBaseInfo interface_info{type_info.interface()}; - if (interface_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_foreign(interface_info.as())) + if (auto struct_info = interface_info.as(); + struct_info && struct_info->is_foreign()) return gjs_struct_foreign_release_gi_argument( - context, transfer, interface_info, arg); + context, transfer, struct_info.value(), arg); - if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) - return true; + if (interface_info.is_enum_or_flags()) + return true; // enum and flags /* Anything else is a pointer */ if (!gjs_arg_get(arg)) return true; - GType gtype = g_registered_type_info_get_g_type( - interface_info.as()); + GType gtype = + interface_info.as()->gtype(); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); @@ -3002,7 +3969,7 @@ static bool gjs_g_arg_release_internal( g_closure_unref); } else if (g_type_is_a(gtype, G_TYPE_VALUE)) { /* G_TYPE_VALUE is-a G_TYPE_BOXED, but we special case it */ - if (g_type_info_is_pointer (type_info)) + if (type_info.is_pointer()) g_boxed_free(gtype, gjs_arg_steal(arg)); else g_clear_pointer(&gjs_arg_member(arg), @@ -3037,51 +4004,19 @@ static bool gjs_g_arg_release_internal( case GI_TYPE_TAG_ARRAY: { - GIArrayType array_type = g_type_info_get_array_type(type_info); + GIArrayType array_type = type_info.array_type(); if (!gjs_arg_get(arg)) { /* OK */ } else if (array_type == GI_ARRAY_TYPE_C) { - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - GITypeTag element_type; - - element_type = g_type_info_get_tag(param_info); - - switch (element_type) { - case GI_TYPE_TAG_UTF8: - case GI_TYPE_TAG_FILENAME: - if (transfer == GI_TRANSFER_CONTAINER) - g_clear_pointer(&gjs_arg_member(arg), - g_free); - else - g_clear_pointer(&gjs_arg_member(arg), - g_strfreev); - break; - - case GI_TYPE_TAG_BOOLEAN: - case GI_TYPE_TAG_UINT8: - case GI_TYPE_TAG_UINT16: - case GI_TYPE_TAG_UINT32: - case GI_TYPE_TAG_UINT64: - case GI_TYPE_TAG_INT8: - case GI_TYPE_TAG_INT16: - case GI_TYPE_TAG_INT32: - case GI_TYPE_TAG_INT64: - case GI_TYPE_TAG_FLOAT: - case GI_TYPE_TAG_DOUBLE: - case GI_TYPE_TAG_UNICHAR: - case GI_TYPE_TAG_GTYPE: - g_clear_pointer(&gjs_arg_member(arg), g_free); - break; + GI::AutoTypeInfo element_type{type_info.element_type()}; + switch (element_type.tag()) { case GI_TYPE_TAG_INTERFACE: - if (!g_type_info_is_pointer(param_info)) { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(param_info); - GIInfoType info_type = interface_info.type(); - if (info_type == GI_INFO_TYPE_STRUCT || - info_type == GI_INFO_TYPE_UNION) { + if (!element_type.is_pointer()) { + GI::AutoBaseInfo interface_info{element_type.interface()}; + if (interface_info.is_struct() || + interface_info.is_union()) { g_clear_pointer(&gjs_arg_member(arg), g_free); break; } @@ -3098,64 +4033,44 @@ static bool gjs_g_arg_release_internal( transfer != GI_TRANSFER_EVERYTHING) element_transfer = GI_TRANSFER_NOTHING; - if (g_type_info_is_zero_terminated(type_info)) { + if (type_info.is_zero_terminated()) { return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>( context, element_transfer, - flags | GjsArgumentFlags::ARG_OUT, param_info, 0, arg); + flags | GjsArgumentFlags::ARG_OUT, element_type, {}, + arg); } else { return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>( context, element_transfer, - flags | GjsArgumentFlags::ARG_OUT, param_info, - g_type_info_get_array_fixed_size(type_info), arg); + flags | GjsArgumentFlags::ARG_OUT, element_type, + type_info.array_fixed_size(), arg); } } - case GI_TYPE_TAG_VOID: default: + // basic types handled above gjs_throw(context, "Releasing a C array with explicit length, that was nested" "inside another container. This is not supported (and will leak)"); return false; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { - GITypeTag element_type; - - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - element_type = g_type_info_get_tag(param_info); - - switch (element_type) { - case GI_TYPE_TAG_BOOLEAN: - case GI_TYPE_TAG_UNICHAR: - case GI_TYPE_TAG_UINT8: - case GI_TYPE_TAG_UINT16: - case GI_TYPE_TAG_UINT32: - case GI_TYPE_TAG_UINT64: - case GI_TYPE_TAG_INT8: - case GI_TYPE_TAG_INT16: - case GI_TYPE_TAG_INT32: - case GI_TYPE_TAG_INT64: - case GI_TYPE_TAG_FLOAT: - case GI_TYPE_TAG_DOUBLE: - case GI_TYPE_TAG_GTYPE: - g_clear_pointer(&gjs_arg_member(arg), g_array_unref); - break; + GI::AutoTypeInfo element_type = type_info.element_type(); + GITypeTag element_tag = element_type.tag(); - case GI_TYPE_TAG_UTF8: - case GI_TYPE_TAG_FILENAME: + switch (element_tag) { 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: { - GjsAutoPointer array = - gjs_arg_steal(arg); + Gjs::AutoPointer array{ + gjs_arg_steal(arg)}; if (transfer != GI_TRANSFER_CONTAINER && - type_needs_out_release(param_info, element_type)) { + type_needs_out_release(element_type, element_tag)) { guint i; for (i = 0; i < array->len; i++) { @@ -3164,7 +4079,7 @@ static bool gjs_g_arg_release_internal( gjs_arg_set(&arg_iter, g_array_index(array, gpointer, i)); if (!gjs_g_arg_release_internal( - context, transfer, param_info, element_type, + context, transfer, element_type, element_tag, GJS_ARGUMENT_ARRAY_ELEMENT, flags, &arg_iter)) return false; } @@ -3173,22 +4088,18 @@ static bool gjs_g_arg_release_internal( break; } - case GI_TYPE_TAG_VOID: default: + // basic types handled above gjs_throw(context, "Don't know how to release GArray element-type %d", - element_type); + element_tag); return false; } - } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { - g_clear_pointer(&gjs_arg_member(arg), - g_byte_array_unref); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - GjsAutoPointer array = - gjs_arg_steal(arg); + GI::AutoTypeInfo element_type{type_info.element_type()}; + Gjs::AutoPointer array{ + gjs_arg_steal(arg)}; if (transfer != GI_TRANSFER_CONTAINER) { guint i; @@ -3197,12 +4108,13 @@ static bool gjs_g_arg_release_internal( GIArgument arg_iter; gjs_arg_set(&arg_iter, g_ptr_array_index(array, i)); - if (!gjs_gi_argument_release(context, transfer, param_info, - flags, &arg_iter)) + if (!gjs_gi_argument_release( + context, transfer, element_type, flags, &arg_iter)) return false; } } } else { + // GI_ARRAY_TYPE_BYTEARRAY handled above; other values unknown g_assert_not_reached(); } break; @@ -3218,18 +4130,17 @@ static bool gjs_g_arg_release_internal( case GI_TYPE_TAG_GHASH: if (gjs_arg_get(arg)) { - GjsAutoPointer - hash_table = gjs_arg_steal(arg); + Gjs::AutoPointer + hash_table{gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER) g_hash_table_remove_all(hash_table); else { - GHR_closure c = {context, nullptr, nullptr, - transfer, flags, false}; - - c.key_param_info = g_type_info_get_param_type(type_info, 0); - g_assert(c.key_param_info != nullptr); - c.val_param_info = g_type_info_get_param_type(type_info, 1); - g_assert(c.val_param_info != nullptr); + GHR_closure c = {context, + type_info.key_type(), + type_info.value_type(), + transfer, + flags, + false}; g_hash_table_foreach_steal(hash_table, gjs_ghr_helper, &c); @@ -3240,8 +4151,9 @@ static bool gjs_g_arg_release_internal( break; default: + // basic types should have been handled in release_basic_type_internal() g_warning("Unhandled type %s releasing GIArgument", - g_type_tag_to_string(type_tag)); + gi_type_tag_to_string(type_tag)); return false; } @@ -3249,24 +4161,35 @@ static bool gjs_g_arg_release_internal( } bool gjs_gi_argument_release(JSContext* cx, GITransfer transfer, - GITypeInfo* type_info, GjsArgumentFlags flags, - GIArgument* arg) { + const GI::TypeInfo type_info, + GjsArgumentFlags flags, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING && !is_transfer_in_nothing(transfer, flags)) return true; - GITypeTag type_tag = g_type_info_get_tag(type_info); - gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s out param or return value", - g_type_tag_to_string(type_tag)); + type_info.type_string()); - return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, + return gjs_g_arg_release_internal(cx, transfer, type_info, type_info.tag(), GJS_ARGUMENT_ARGUMENT, flags, arg); } +void gjs_gi_argument_release_basic(GITransfer transfer, GITypeTag type_tag, + GjsArgumentFlags flags, GIArgument* arg) { + if (transfer == GI_TRANSFER_NOTHING && + !is_transfer_in_nothing(transfer, flags)) + return; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument %s out param or return value", + gi_type_tag_to_string(type_tag)); + + release_basic_type_internal(type_tag, arg); +} + bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, - GITypeInfo* type_info, + const GI::TypeInfo type_info, GjsArgumentFlags flags, GIArgument* arg) { /* GI_TRANSFER_EVERYTHING: we don't own the argument anymore. * GI_TRANSFER_CONTAINER: @@ -3277,52 +4200,117 @@ bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, if (transfer != GI_TRANSFER_NOTHING) return true; - GITypeTag type_tag = g_type_info_get_tag(type_info); + GITypeTag tag = type_info.tag(); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s in param", - g_type_tag_to_string(type_tag)); + type_info.type_string()); - if (type_needs_release (type_info, type_tag)) - return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, - GJS_ARGUMENT_ARGUMENT, flags, arg); + if (!type_needs_release(type_info, tag)) + return true; - return true; + return gjs_g_arg_release_internal(cx, transfer, type_info, tag, + GJS_ARGUMENT_ARGUMENT, flags, arg); } -bool gjs_gi_argument_release_in_array(JSContext* context, GITransfer transfer, - GITypeInfo* type_info, unsigned length, - GIArgument* arg) { +void gjs_gi_argument_release_basic_in_array(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + if (transfer != GI_TRANSFER_NOTHING) + return; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument basic C array in param"); + + if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) + g_clear_pointer(&gjs_arg_member(arg), g_strfreev); + else + g_clear_pointer(&gjs_arg_member(arg), g_free); +} + +void gjs_gi_argument_release_basic_in_array(GITransfer transfer, + GITypeTag element_tag, + size_t length, GIArgument* arg) { + if (transfer != GI_TRANSFER_NOTHING) + return; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument basic C array in param"); + + Gjs::AutoPointer array{gjs_arg_steal(arg)}; + + if (!is_string_type(element_tag)) + return; + + for (size_t ix = 0; ix < length; ix++) + g_free(array[ix]); +} + +bool gjs_gi_argument_release_in_array(JSContext* cx, GITransfer transfer, + const GI::TypeInfo type_info, + size_t length, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); - GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< - ArrayReleaseType::EXPLICIT_LENGTH>(context, GI_TRANSFER_EVERYTHING, - GjsArgumentFlags::ARG_IN, param_type, - length, arg); + ArrayReleaseType::EXPLICIT_LENGTH>( + cx, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, + type_info.element_type(), Some(length), arg); } -bool gjs_gi_argument_release_in_array(JSContext* context, GITransfer transfer, - GITypeInfo* type_info, GIArgument* arg) { +bool gjs_gi_argument_release_in_array(JSContext* cx, GITransfer transfer, + const GI::TypeInfo type_info, + GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); - GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< - ArrayReleaseType::ZERO_TERMINATED>(context, GI_TRANSFER_EVERYTHING, - GjsArgumentFlags::ARG_IN, param_type, - 0, arg); + ArrayReleaseType::ZERO_TERMINATED>(cx, GI_TRANSFER_EVERYTHING, + GjsArgumentFlags::ARG_IN, + type_info.element_type(), {}, arg); } -bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, - GITypeInfo* type_info, unsigned length, - GIArgument* arg) { +void gjs_gi_argument_release_basic_out_array(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg) { + if (transfer == GI_TRANSFER_NOTHING) + return; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument array out param"); + + if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) + g_clear_pointer(&gjs_arg_member(arg), g_strfreev); + else + g_clear_pointer(&gjs_arg_member(arg), g_free); +} + +void gjs_gi_argument_release_basic_out_array(GITransfer transfer, + GITypeTag element_tag, + size_t length, GIArgument* arg) { + if (transfer == GI_TRANSFER_NOTHING) + return; + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GIArgument array out param"); + + Gjs::AutoPointer array{gjs_arg_steal(arg)}; + + if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) + return; + + for (size_t ix = 0; ix < length; ix++) + g_free(array[ix]); +} + +bool gjs_gi_argument_release_out_array(JSContext* cx, GITransfer transfer, + const GI::TypeInfo type_info, + size_t length, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return true; @@ -3333,15 +4321,15 @@ bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING; - GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< - ArrayReleaseType::EXPLICIT_LENGTH>(context, element_transfer, - GjsArgumentFlags::ARG_OUT, - param_type, length, arg); + ArrayReleaseType::EXPLICIT_LENGTH>( + cx, element_transfer, GjsArgumentFlags::ARG_OUT, + type_info.element_type(), Some(length), arg); } bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, - GITypeInfo* type_info, GIArgument* arg) { + const GI::TypeInfo type_info, + GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return true; @@ -3352,9 +4340,8 @@ bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING; - GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>(context, element_transfer, GjsArgumentFlags::ARG_OUT, - param_type, 0, arg); + type_info.element_type(), {}, arg); } diff --git a/gi/arg.h b/gi/arg.h index fac73eb4d..6f73e813d 100644 --- a/gi/arg.h +++ b/gi/arg.h @@ -10,13 +10,14 @@ #include // for size_t #include -#include +#include #include #include // for GHashTable #include #include +#include "gi/info.h" #include "cjs/macros.h" // Different roles for a GIArgument; currently used only in exception and debug @@ -51,90 +52,205 @@ GjsArgumentFlags operator|(GjsArgumentFlags const& v1, GjsArgumentFlags const& v GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_callback_out_arg(JSContext* context, JS::HandleValue value, - GIArgInfo* arg_info, GIArgument* arg); + const GI::ArgInfo arg_info, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION -bool gjs_array_to_explicit_array(JSContext* cx, JS::HandleValue value, - GITypeInfo* type_info, const char* arg_name, - GjsArgumentType arg_type, GITransfer transfer, - GjsArgumentFlags flags, void** contents, - size_t* length_p); +bool gjs_array_to_explicit_array(JSContext*, JS::HandleValue, + const GI::TypeInfo, const char* arg_name, + GjsArgumentType, GITransfer, GjsArgumentFlags, + void** contents, size_t* length_p); -size_t gjs_type_get_element_size(GITypeTag element_type, GITypeInfo* type_info); - -void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg); +size_t gjs_type_get_element_size(GITypeTag element_tag, const GI::TypeInfo); +// FIXME: GI::TypeInfo&? GJS_JSAPI_RETURN_CONVENTION -bool gjs_value_to_gi_argument(JSContext*, JS::HandleValue, GITypeInfo*, +bool gjs_value_to_gi_argument(JSContext*, JS::HandleValue, const GI::TypeInfo, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool inline gjs_value_to_gi_argument(JSContext* cx, JS::HandleValue value, - GITypeInfo* type_info, + const GI::TypeInfo type_info, GjsArgumentType argument_type, GITransfer transfer, GIArgument* arg) { return gjs_value_to_gi_argument(cx, value, type_info, nullptr /* arg_name */, argument_type, transfer, GjsArgumentFlags::NONE, arg); } - GJS_JSAPI_RETURN_CONVENTION -bool gjs_value_from_gi_argument(JSContext*, JS::MutableHandleValue, GITypeInfo*, - GjsArgumentType, GITransfer, GIArgument*); +bool gjs_value_to_basic_gi_argument(JSContext*, JS::HandleValue, GITypeTag, + GIArgument*, const char* arg_name, + GjsArgumentType, GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_gerror_gi_argument(JSContext*, JS::HandleValue, GITransfer, + GIArgument*, const char* arg_name, + GjsArgumentType, GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_basic_glist_gi_argument(JSContext*, JS::HandleValue, + GITypeTag element_tag, GIArgument*, + const char* arg_name, + GjsArgumentType); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_basic_gslist_gi_argument(JSContext*, JS::HandleValue, + GITypeTag element_tag, GIArgument*, + const char* arg_name, + GjsArgumentType); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_basic_ghash_gi_argument(JSContext*, JS::HandleValue, + GITypeTag key_tag, + GITypeTag value_tag, GITransfer, + GIArgument*, const char* arg_name, + GjsArgumentType, GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_basic_array_gi_argument(JSContext*, JS::HandleValue, + GITypeTag element_tag, GIArrayType, + GIArgument*, const char* arg_name, + GjsArgumentType, GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_byte_array_gi_argument(JSContext*, JS::HandleValue, + GIArgument*, const char* arg_name, + GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_array_to_basic_explicit_array(JSContext*, JS::HandleValue, + GITypeTag element_tag, + const char* arg_name, GjsArgumentType, + GjsArgumentFlags, void** contents_out, + size_t* length_out); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_gdk_atom_gi_argument(JSContext*, JS::HandleValue, GIArgument*, + const char* arg_name, GjsArgumentType); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_to_interface_gi_argument(JSContext*, JS::HandleValue, + const GI::BaseInfo interface_info, + GITransfer, GIArgument*, + const char* arg_name, GjsArgumentType, + GjsArgumentFlags); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_basic_gi_argument(JSContext*, JS::MutableHandleValue, + GITypeTag, GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_gi_argument(JSContext*, JS::MutableHandleValue, + const GI::TypeInfo, GjsArgumentType, GITransfer, + GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_value_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, - GITypeInfo* type_info, GIArgument* arg, - bool copy_structs) { + const GI::TypeInfo type_info, + GIArgument* arg, bool copy_structs) { return gjs_value_from_gi_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, - JS::MutableHandleValue value_p, - GITypeInfo* type_info, GITransfer transfer, - GIArgument* arg, int length); +bool gjs_value_from_basic_ghash(JSContext*, JS::MutableHandleValue, + GITypeTag key_tag, GITypeTag value_tag, + GHashTable*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_array_from_basic_glist_gi_argument(JSContext*, JS::MutableHandleValue, + GITypeTag element_tag, GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_array_from_basic_gslist_gi_argument(JSContext*, JS::MutableHandleValue, + GITypeTag element_tag, + GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_array_from_basic_zero_terminated_array(JSContext*, + JS::MutableHandleValue, + GITypeTag element_tag, + void* c_array); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_basic_fixed_size_array_gi_argument(JSContext*, + JS::MutableHandleValue, + GITypeTag element_tag, + size_t fixed_size, + GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_basic_explicit_array(JSContext*, JS::MutableHandleValue, + GITypeTag element_tag, GIArgument*, + size_t length); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_explicit_array(JSContext*, JS::MutableHandleValue, + const GI::TypeInfo, GITransfer, GIArgument*, + size_t 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) { + const GI::TypeInfo type_info, + GIArgument* arg, size_t length) { return gjs_value_from_explicit_array(context, value_p, type_info, GI_TRANSFER_EVERYTHING, arg, length); } - GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release(JSContext*, GITransfer, GITypeInfo*, +bool gjs_value_from_byte_array_gi_argument(JSContext*, JS::MutableHandleValue, + GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_basic_garray_gi_argument(JSContext*, JS::MutableHandleValue, + GITypeTag element_tag, + GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_value_from_basic_gptrarray_gi_argument(JSContext*, + JS::MutableHandleValue, + GITypeTag element_tag, + GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_gi_argument_release(JSContext*, GITransfer, const GI::TypeInfo, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_gi_argument_release(JSContext* cx, GITransfer transfer, - GITypeInfo* type_info, GIArgument* arg) { + const GI::TypeInfo type_info, + GIArgument* arg) { return gjs_gi_argument_release(cx, transfer, type_info, GjsArgumentFlags::NONE, arg); } +void gjs_gi_argument_release_basic(GITransfer, GITypeTag, GjsArgumentFlags, + GIArgument*); +void gjs_gi_argument_release_basic_glist(GITransfer, GITypeTag element_tag, + GIArgument*); +void gjs_gi_argument_release_basic_gslist(GITransfer, GITypeTag element_tag, + GIArgument*); +void gjs_gi_argument_release_basic_ghash(GITransfer, GITypeTag key_tag, + GITypeTag value_tag, GIArgument*); +void gjs_gi_argument_release_basic_garray(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg); +void gjs_gi_argument_release_basic_gptrarray(GITransfer transfer, + GITypeTag element_tag, + GIArgument* arg); +void gjs_gi_argument_release_basic_c_array(GITransfer, GITypeTag element_tag, + GIArgument*); +void gjs_gi_argument_release_basic_c_array(GITransfer, GITypeTag element_tag, + size_t length, GIArgument*); +void gjs_gi_argument_release_basic_in_array(GITransfer, GITypeTag element_tag, + GIArgument*); +void gjs_gi_argument_release_basic_in_array(GITransfer, GITypeTag element_tag, + size_t length, GIArgument*); +void gjs_gi_argument_release_basic_out_array(GITransfer, GITypeTag element_tag, + GIArgument*); +void gjs_gi_argument_release_basic_out_array(GITransfer, GITypeTag element_tag, + size_t length, GIArgument*); +void gjs_gi_argument_release_byte_array(GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, GITypeInfo*, - unsigned length, GIArgument*); -GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, GITypeInfo*, +bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, + const GI::TypeInfo, size_t length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, GITypeInfo*, - unsigned length, GIArgument*); +bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, + const GI::TypeInfo, GIArgument*); GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, GITypeInfo*, +bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, + const GI::TypeInfo, size_t length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION -bool gjs_gi_argument_release_in_arg(JSContext*, GITransfer, GITypeInfo*, +bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, + const GI::TypeInfo, GIArgument*); +GJS_JSAPI_RETURN_CONVENTION +bool gjs_gi_argument_release_in_arg(JSContext*, GITransfer, const GI::TypeInfo, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, - GITypeInfo* type_info, + const GI::TypeInfo type_info, GIArgument* arg) { return gjs_gi_argument_release_in_arg(cx, transfer, type_info, GjsArgumentFlags::ARG_IN, arg); @@ -143,8 +259,6 @@ inline bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, GJS_JSAPI_RETURN_CONVENTION bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value); -[[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value); - GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_strv(JSContext *context, JS::MutableHandleValue value_p, @@ -158,13 +272,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, GITransfer, + const GI::TypeInfo 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); + const GI::TypeInfo key_param_info, + const GI::TypeInfo val_param_info, + GITransfer transfer, GHashTable* hash); #endif // GI_ARG_H_ diff --git a/gi/boxed.cpp b/gi/boxed.cpp index 67b59ff5f..4d1f861ab 100644 --- a/gi/boxed.cpp +++ b/gi/boxed.cpp @@ -7,9 +7,10 @@ #include #include // for memcpy, size_t, strcmp +#include // for one_of, any_of #include // for move, forward -#include +#include #include #include @@ -28,6 +29,7 @@ #include #include // for IdVector #include +#include #include "gi/arg-inl.h" #include "gi/arg.h" @@ -38,12 +40,17 @@ #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" +using mozilla::Maybe, mozilla::Some; + +[[nodiscard]] static bool struct_is_simple(const GI::StructInfo& info); + BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj), m_allocated_directly(false), @@ -51,8 +58,6 @@ BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj) GJS_INC_COUNTER(boxed_instance); } -[[nodiscard]] static bool struct_is_simple(GIStructInfo* info); - // See GIWrapperBase::resolve(). bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { @@ -65,22 +70,19 @@ bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, } // Look for methods and other class properties - GjsAutoFunctionInfo method_info = - g_struct_info_find_method(info(), prop_name.get()); + Maybe method_info{info().method(prop_name.get())}; if (!method_info) { *resolved = false; return true; } -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(method_info); -#endif + method_info->log_usage(); - if (g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { - gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", - method_info.name(), ns(), name()); + if (method_info->is_method()) { + gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s", + method_info->name(), format_name().c_str()); /* obj is the Boxed prototype */ - if (!gjs_define_function(cx, obj, gtype(), method_info)) + if (!gjs_define_function(cx, obj, gtype(), *method_info)) return false; *resolved = true; @@ -95,14 +97,9 @@ bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, bool BoxedPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { - int n_methods = g_struct_info_get_n_methods(info()); - for (int i = 0; i < n_methods; i++) { - GjsAutoFunctionInfo meth_info = g_struct_info_get_method(info(), i); - GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); - - if (flags & GI_FUNCTION_IS_METHOD) { - const char* name = meth_info.name(); - jsid id = gjs_intern_string_to_id(cx, name); + for (GI::AutoFunctionInfo meth_info : info().methods()) { + if (meth_info.is_method()) { + jsid id = gjs_intern_string_to_id(cx, meth_info.name()); if (id.isVoid()) return false; if (!properties.append(id)) { @@ -129,7 +126,7 @@ BoxedBase* BoxedBase::get_copy_source(JSContext* context, JS::RootedObject object(context, &value.toObject()); BoxedBase* source_priv = BoxedBase::for_js(context, object); - if (!source_priv || !g_base_info_equal(info(), source_priv->info())) + if (!source_priv || info() != source_priv->info()) return nullptr; return source_priv; @@ -146,7 +143,7 @@ BoxedBase* BoxedBase::get_copy_source(JSContext* context, void BoxedInstance::allocate_directly(void) { g_assert(get_prototype()->can_allocate_directly()); - own_ptr(g_malloc0(g_struct_info_get_size(info()))); + own_ptr(g_malloc0(info().size())); m_allocated_directly = true; debug_lifecycle("Boxed pointer directly allocated"); @@ -156,20 +153,15 @@ void BoxedInstance::allocate_directly(void) { // do n O(n) lookups, so put put the fields into a hash table and store it on // proto->priv for fast lookup. std::unique_ptr BoxedPrototype::create_field_map( - JSContext* cx, GIStructInfo* struct_info) { - int n_fields; - int i; - + JSContext* cx, const GI::StructInfo struct_info) { auto result = std::make_unique(); - n_fields = g_struct_info_get_n_fields(struct_info); - if (!result->reserve(n_fields)) { + GI::StructInfo::FieldsIterator fields = struct_info.fields(); + if (!result->reserve(fields.size())) { JS_ReportOutOfMemory(cx); return nullptr; } - for (i = 0; i < n_fields; i++) { - GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); - + for (GI::AutoFieldInfo field_info : fields) { // We get the string as a jsid later, which is interned. We intern the // string here as well, so it will be the same string pointer JSString* atom = JS_AtomizeAndPinString(cx, field_info.name()); @@ -199,18 +191,19 @@ bool BoxedPrototype::ensure_field_map(JSContext* cx) { * Look up the introspection info corresponding to the field name @prop_name, * creating the field cache if necessary. */ -GIFieldInfo* BoxedPrototype::lookup_field(JSContext* cx, JSString* prop_name) { +Maybe BoxedPrototype::lookup_field(JSContext* cx, + JSString* prop_name) { if (!ensure_field_map(cx)) - return nullptr; + return {}; auto entry = m_field_map->lookup(prop_name); if (!entry) { gjs_throw(cx, "No field %s on boxed type %s", gjs_debug_string(prop_name).c_str(), name()); - return nullptr; + return {}; } - return entry->value().get(); + return Some(entry->value()); } /* Initialize a newly created Boxed from an object that is a "hash" of @@ -239,7 +232,7 @@ bool BoxedInstance::init_from_props(JSContext* context, JS::Value props_value) { return false; } - GIFieldInfo* field_info = + Maybe field_info = get_prototype()->lookup_field(context, ids[ix].toString()); if (!field_info) return false; @@ -251,7 +244,7 @@ bool BoxedInstance::init_from_props(JSContext* context, JS::Value props_value) { &value)) return false; - if (!field_setter_impl(context, field_info, value)) + if (!field_setter_impl(context, *field_info, value)) return false; } @@ -300,7 +293,7 @@ void BoxedInstance::copy_boxed(BoxedInstance* source) { */ void BoxedInstance::copy_memory(void* boxed_ptr) { allocate_directly(); - memcpy(m_ptr, boxed_ptr, g_struct_info_get_size(info())); + memcpy(m_ptr, boxed_ptr, info().size()); } void BoxedInstance::copy_memory(BoxedInstance* source) { @@ -356,14 +349,13 @@ bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj, // exists, otherwise we malloc the correct amount of space if possible; // finally, we fallback on the default constructor. if (proto->has_zero_args_constructor()) { - GjsAutoFunctionInfo func_info = proto->zero_args_constructor_info(); + GI::AutoFunctionInfo func_info{proto->zero_args_constructor_info()}; GIArgument rval_arg; - GjsAutoError error; - - if (!g_function_info_invoke(func_info, NULL, 0, NULL, 0, &rval_arg, &error)) { + Gjs::GErrorResult<> result = func_info.invoke({}, {}, &rval_arg); + if (result.isErr()) { gjs_throw(context, "Failed to invoke boxed constructor: %s", - error->message); + result.inspectErr()->message); return false; } @@ -450,12 +442,11 @@ BoxedPrototype::~BoxedPrototype(void) { * Does the same thing as g_struct_info_get_field(), but throws a JS exception * if there is no such field. */ -GIFieldInfo* BoxedBase::get_field_info(JSContext* cx, uint32_t id) const { - GIFieldInfo* field_info = g_struct_info_get_field(info(), id); - if (field_info == NULL) { +Maybe BoxedBase::get_field_info(JSContext* cx, + uint32_t id) const { + Maybe field_info = info().fields()[id]; + if (!field_info) gjs_throw(cx, "No field %d on boxed type %s", id, name()); - return NULL; - } return field_info; } @@ -479,19 +470,15 @@ GIFieldInfo* BoxedBase::get_field_info(JSContext* cx, uint32_t id) const { * garbage collected while the nested JS object is active. */ bool BoxedInstance::get_nested_interface_object( - JSContext* context, JSObject* parent_obj, GIFieldInfo* field_info, - GIStructInfo* struct_info, JS::MutableHandleValue value) const { - int offset; - + JSContext* context, JSObject* parent_obj, const GI::FieldInfo field_info, + const GI::StructInfo struct_info, JS::MutableHandleValue value) const { if (!struct_is_simple(struct_info)) { - gjs_throw(context, "Reading field %s.%s is not supported", name(), - g_base_info_get_name(field_info)); + gjs_throw(context, "Reading field %s.%s is not supported", + format_name().c_str(), field_info.name()); return false; } - offset = g_field_info_get_offset (field_info); - JS::RootedObject obj{ context, gjs_new_object_with_generic_prototype(context, struct_info)}; if (!obj) @@ -500,7 +487,7 @@ bool BoxedInstance::get_nested_interface_object( BoxedInstance* priv = BoxedInstance::new_for_js_object(context, obj); /* A structure nested inside a parent object; doesn't have an independent allocation */ - priv->share_ptr(raw_ptr() + offset); + priv->share_ptr(raw_ptr() + field_info.offset()); priv->debug_lifecycle( "Boxed pointer created, pointing inside memory owned by parent"); @@ -528,60 +515,56 @@ bool BoxedBase::field_getter(JSContext* context, unsigned argc, JS::Value* vp) { uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); - GjsAutoFieldInfo field_info = priv->get_field_info(context, field_ix); + Maybe field_info{ + priv->get_field_info(context, field_ix)}; if (!field_info) return false; - return priv->to_instance()->field_getter_impl(context, obj, field_info, - args.rval()); + return priv->to_instance()->field_getter_impl( + context, obj, field_info.ref(), args.rval()); } // See BoxedBase::field_getter(). bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj, - GIFieldInfo* field_info, + const GI::FieldInfo field_info, JS::MutableHandleValue rval) const { - GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); + GI::AutoTypeInfo type_info{field_info.type_info()}; - if (!g_type_info_is_pointer(type_info) && - g_type_info_get_tag(type_info) == GI_TYPE_TAG_INTERFACE) { - GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); - - if (interface_info.type() == GI_INFO_TYPE_STRUCT || - interface_info.type() == GI_INFO_TYPE_BOXED) { + if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) { + GI::AutoBaseInfo interface{type_info.interface()}; + if (auto struct_info = interface.as()) { return get_nested_interface_object(cx, obj, field_info, - interface_info, rval); + struct_info.value(), rval); } } GIArgument arg; - if (!g_field_info_get_field(field_info, m_ptr, &arg)) { - gjs_throw(cx, "Reading field %s.%s is not supported", name(), - g_base_info_get_name(field_info)); + if (field_info.read(m_ptr, &arg).isErr()) { + gjs_throw(cx, "Reading field %s.%s is not supported", + format_name().c_str(), field_info.name()); return false; } - if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && - g_type_info_get_array_length(type_info) != -1) { - auto length_field_ix = g_type_info_get_array_length(type_info); - GjsAutoFieldInfo length_field_info = - get_field_info(cx, length_field_ix); + if (type_info.tag() == GI_TYPE_TAG_ARRAY && + type_info.array_length_index()) { + unsigned length_field_ix = type_info.array_length_index().value(); + Maybe length_field_info{ + get_field_info(cx, length_field_ix)}; if (!length_field_info) { - gjs_throw(cx, "Reading field %s.%s is not supported", name(), - g_base_info_get_name(field_info)); + gjs_throw(cx, "Reading field %s.%s is not supported", + format_name().c_str(), field_info.name()); return false; } GIArgument length_arg; - if (!g_field_info_get_field(length_field_info, m_ptr, &length_arg)) { - gjs_throw(cx, "Reading field %s.%s is not supported", name(), - length_field_info.name()); + if (length_field_info->read(m_ptr, &length_arg).isErr()) { + gjs_throw(cx, "Reading field %s.%s is not supported", + format_name().c_str(), length_field_info->name()); return false; } - GjsAutoTypeInfo length_type_info = - g_field_info_get_type(length_field_info); size_t length = gjs_gi_argument_get_array_length( - g_type_info_get_tag(length_type_info), &length_arg); + length_field_info->type_info().tag(), &length_arg); return gjs_value_from_explicit_array(cx, rval, type_info, &arg, length); } @@ -601,15 +584,12 @@ bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj, * is being set. The contents of the BoxedInstance JS object in @value are * copied into the correct place in this BoxedInstance's memory. */ -bool BoxedInstance::set_nested_interface_object(JSContext* context, - GIFieldInfo* field_info, - GIStructInfo* struct_info, - JS::HandleValue value) { - int offset; - +bool BoxedInstance::set_nested_interface_object( + JSContext* context, const GI::FieldInfo field_info, + const GI::StructInfo struct_info, JS::HandleValue value) { if (!struct_is_simple(struct_info)) { - gjs_throw(context, "Writing field %s.%s is not supported", name(), - g_base_info_get_name(field_info)); + gjs_throw(context, "Writing field %s.%s is not supported", + format_name().c_str(), field_info.name()); return false; } @@ -636,46 +616,42 @@ bool BoxedInstance::set_nested_interface_object(JSContext* context, if (!source_priv->check_is_instance(context, "copy")) return false; - offset = g_field_info_get_offset (field_info); - memcpy(raw_ptr() + offset, source_priv->to_instance()->ptr(), - g_struct_info_get_size(source_priv->info())); + memcpy(raw_ptr() + field_info.offset(), source_priv->to_instance()->ptr(), + source_priv->info().size()); return true; } // See BoxedBase::field_setter(). bool BoxedInstance::field_setter_impl(JSContext* context, - GIFieldInfo* field_info, + const GI::FieldInfo field_info, JS::HandleValue value) { - GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); + GI::AutoTypeInfo type_info{field_info.type_info()}; - if (!g_type_info_is_pointer (type_info) && - g_type_info_get_tag (type_info) == GI_TYPE_TAG_INTERFACE) { - GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); - - if (interface_info.type() == GI_INFO_TYPE_STRUCT || - interface_info.type() == GI_INFO_TYPE_BOXED) { + if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) { + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (auto struct_info = interface_info.as()) { return set_nested_interface_object(context, field_info, - interface_info, value); + struct_info.value(), value); } } GIArgument arg; - if (!gjs_value_to_gi_argument(context, value, type_info, - g_base_info_get_name(field_info), + if (!gjs_value_to_gi_argument(context, value, type_info, field_info.name(), GJS_ARGUMENT_FIELD, GI_TRANSFER_NOTHING, GjsArgumentFlags::MAY_BE_NULL, &arg)) return false; bool success = true; - if (!g_field_info_set_field(field_info, m_ptr, &arg)) { - gjs_throw(context, "Writing field %s.%s is not supported", name(), - g_base_info_get_name(field_info)); + if (field_info.write(m_ptr, &arg).isErr()) { + gjs_throw(context, "Writing field %s.%s is not supported", + format_name().c_str(), field_info.name()); success = false; } JS::AutoSaveExceptionState saved_exc(context); - if (!gjs_gi_argument_release(context, GI_TRANSFER_NOTHING, type_info, &arg)) + if (!gjs_gi_argument_release(context, GI_TRANSFER_NOTHING, type_info, + GjsArgumentFlags::ARG_IN, &arg)) gjs_log_exception(context); saved_exc.restore(); @@ -696,11 +672,11 @@ bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); - GjsAutoFieldInfo field_info = priv->get_field_info(cx, field_ix); + Maybe field_info{priv->get_field_info(cx, field_ix)}; if (!field_info) return false; - if (!priv->to_instance()->field_setter_impl(cx, field_info, args[0])) + if (!priv->to_instance()->field_setter_impl(cx, *field_info, args[0])) return false; args.rval().setUndefined(); /* No stored value */ @@ -715,8 +691,7 @@ bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { */ bool BoxedPrototype::define_boxed_class_fields(JSContext* cx, JS::HandleObject proto) { - int n_fields = g_struct_info_get_n_fields(info()); - int i; + uint32_t count = 0; // We define all fields as read/write so that the user gets an error // message. If we omitted fields or defined them read-only we'd: @@ -733,9 +708,8 @@ bool BoxedPrototype::define_boxed_class_fields(JSContext* cx, // // At this point methods have already been defined on the prototype, so we // may get name conflicts which we need to check for. - for (i = 0; i < n_fields; i++) { - GjsAutoFieldInfo field = g_struct_info_get_field(info(), i); - JS::RootedValue private_id(cx, JS::PrivateUint32Value(i)); + for (GI::AutoFieldInfo field : info().fields()) { + JS::RootedValue private_id{cx, JS::PrivateUint32Value(count++)}; JS::RootedId id{cx, gjs_intern_string_to_id(cx, field.name())}; bool already_defined; @@ -743,9 +717,9 @@ bool BoxedPrototype::define_boxed_class_fields(JSContext* cx, return false; if (already_defined) { gjs_debug(GJS_DEBUG_GBOXED, - "Field %s.%s.%s overlaps with method of the same name; " + "Field %s.%s overlaps with method of the same name; " "skipping", - ns(), name(), field.name()); + format_name().c_str(), field.name()); continue; } @@ -793,108 +767,49 @@ const struct JSClass BoxedBase::klass = { }; // clang-format on -[[nodiscard]] static bool type_can_be_allocated_directly( - GITypeInfo* type_info) { - bool is_simple = true; - - if (g_type_info_is_pointer(type_info)) { - if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && - g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - return type_can_be_allocated_directly(param_info); - } +[[nodiscard]] +static bool type_can_be_allocated_directly(const GI::TypeInfo& type_info) { + if (type_info.is_pointer()) { + if (type_info.tag() == GI_TYPE_TAG_ARRAY && + type_info.array_type() == GI_ARRAY_TYPE_C) + return type_can_be_allocated_directly(type_info.element_type()); return true; - } else { - switch (g_type_info_get_tag(type_info)) { - case GI_TYPE_TAG_INTERFACE: - { - GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); - switch (interface.type()) { - case GI_INFO_TYPE_BOXED: - case GI_INFO_TYPE_STRUCT: - return struct_is_simple(interface.as()); - case GI_INFO_TYPE_UNION: - /* FIXME: Need to implement */ - is_simple = false; - break; - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_VFUNC: - case GI_INFO_TYPE_CALLBACK: - case GI_INFO_TYPE_INVALID: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_FUNCTION: - case GI_INFO_TYPE_CONSTANT: - case GI_INFO_TYPE_VALUE: - case GI_INFO_TYPE_SIGNAL: - case GI_INFO_TYPE_PROPERTY: - case GI_INFO_TYPE_FIELD: - case GI_INFO_TYPE_ARG: - case GI_INFO_TYPE_TYPE: - case GI_INFO_TYPE_UNRESOLVED: - is_simple = false; - break; - case GI_INFO_TYPE_INVALID_0: - g_assert_not_reached(); - break; - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - default: - break; - } - break; - } - case GI_TYPE_TAG_BOOLEAN: - case GI_TYPE_TAG_INT8: - case GI_TYPE_TAG_UINT8: - case GI_TYPE_TAG_INT16: - case GI_TYPE_TAG_UINT16: - case GI_TYPE_TAG_INT32: - case GI_TYPE_TAG_UINT32: - case GI_TYPE_TAG_INT64: - case GI_TYPE_TAG_UINT64: - case GI_TYPE_TAG_FLOAT: - case GI_TYPE_TAG_DOUBLE: - case GI_TYPE_TAG_UNICHAR: - case GI_TYPE_TAG_VOID: - case GI_TYPE_TAG_GTYPE: - case GI_TYPE_TAG_ERROR: - case GI_TYPE_TAG_UTF8: - case GI_TYPE_TAG_FILENAME: - case GI_TYPE_TAG_ARRAY: - case GI_TYPE_TAG_GLIST: - case GI_TYPE_TAG_GSLIST: - case GI_TYPE_TAG_GHASH: - default: - break; - } } - return is_simple; + + if (type_info.tag() != GI_TYPE_TAG_INTERFACE) + return true; + + GI::AutoBaseInfo interface_info{type_info.interface()}; + if (auto struct_info = interface_info.as()) + return struct_is_simple(struct_info.value()); + if (interface_info.is_union()) + return false; // FIXME: Need to implement + if (interface_info.is_enum_or_flags()) + return true; + return false; } -[[nodiscard]] static bool simple_struct_has_pointers(GIStructInfo*); +[[nodiscard]] +static bool simple_struct_has_pointers(const GI::StructInfo); -[[nodiscard]] static bool direct_allocation_has_pointers( - GITypeInfo* type_info) { - if (g_type_info_is_pointer(type_info)) { - if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && - g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { - GjsAutoTypeInfo param_info = - g_type_info_get_param_type(type_info, 0); - return direct_allocation_has_pointers(param_info); +[[nodiscard]] +static bool direct_allocation_has_pointers(const GI::TypeInfo type_info) { + if (type_info.is_pointer()) { + if (type_info.tag() == GI_TYPE_TAG_ARRAY && + type_info.array_type() == GI_ARRAY_TYPE_C) { + return direct_allocation_has_pointers(type_info.element_type()); } - return g_type_info_get_tag(type_info) != GI_TYPE_TAG_VOID; + return type_info.tag() != GI_TYPE_TAG_VOID; } - if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) + if (type_info.tag() != GI_TYPE_TAG_INTERFACE) return false; - GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); - if (interface.type() == GI_INFO_TYPE_BOXED || - interface.type() == GI_INFO_TYPE_STRUCT) - return simple_struct_has_pointers(interface.as()); + GI::AutoBaseInfo interface{type_info.interface()}; + if (auto struct_info = interface.as()) + return simple_struct_has_pointers(struct_info.value()); return false; } @@ -903,42 +818,33 @@ const struct JSClass BoxedBase::klass = { * type that we know how to assign to. If so, then we can allocate and free * instances without needing a constructor. */ -[[nodiscard]] static bool struct_is_simple(GIStructInfo* info) { - int n_fields = g_struct_info_get_n_fields(info); - bool is_simple = true; - int i; +[[nodiscard]] +bool struct_is_simple(const GI::StructInfo& info) { + GI::StructInfo::FieldsIterator iter = info.fields(); - /* If it's opaque, it's not simple */ - if (n_fields == 0) + // If it's opaque, it's not simple + if (iter.size() == 0) return false; - for (i = 0; i < n_fields && is_simple; i++) { - GjsAutoFieldInfo field_info = g_struct_info_get_field(info, i); - GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); - - is_simple = type_can_be_allocated_directly(type_info); - } - - return is_simple; + return std::all_of( + iter.begin(), iter.end(), [](GI::AutoFieldInfo field_info) { + return type_can_be_allocated_directly(field_info.type_info()); + }); } -[[nodiscard]] static bool simple_struct_has_pointers(GIStructInfo* info) { +[[nodiscard]] +static bool simple_struct_has_pointers(const GI::StructInfo info) { g_assert(struct_is_simple(info) && "Don't call simple_struct_has_pointers() on a non-simple struct"); - int n_fields = g_struct_info_get_n_fields(info); - g_assert(n_fields > 0); - - for (int i = 0; i < n_fields; i++) { - GjsAutoFieldInfo field_info = g_struct_info_get_field(info, i); - GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); - if (direct_allocation_has_pointers(type_info)) - return true; - } - return false; + GI::StructInfo::FieldsIterator fields = info.fields(); + return std::any_of( + fields.begin(), fields.end(), [](GI::AutoFieldInfo field) { + return direct_allocation_has_pointers(field.type_info()); + }); } -BoxedPrototype::BoxedPrototype(GIStructInfo* info, GType gtype) +BoxedPrototype::BoxedPrototype(const GI::StructInfo info, GType gtype) : GIWrapperPrototype(info, gtype), m_zero_args_constructor(-1), m_default_constructor(-1), @@ -955,7 +861,7 @@ BoxedPrototype::BoxedPrototype(GIStructInfo* info, GType gtype) // Overrides GIWrapperPrototype::init(). bool BoxedPrototype::init(JSContext* context) { - int i, n_methods; + int i = 0; int first_constructor = -1; jsid first_constructor_name = JS::PropertyKey::Void(); jsid zero_args_constructor_name = JS::PropertyKey::Void(); @@ -966,15 +872,8 @@ bool BoxedPrototype::init(JSContext* context) { * really make sense for non-boxed types, since there is no memory management * for the return value. */ - n_methods = g_struct_info_get_n_methods(m_info); - - for (i = 0; i < n_methods; ++i) { - GIFunctionInfoFlags flags; - - GjsAutoFunctionInfo func_info = g_struct_info_get_method(m_info, i); - - flags = g_function_info_get_flags(func_info); - if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) { + for (GI::AutoFunctionInfo func_info : m_info.methods()) { + if (func_info.is_constructor()) { if (first_constructor < 0) { first_constructor = i; first_constructor_name = @@ -983,8 +882,7 @@ bool BoxedPrototype::init(JSContext* context) { return false; } - if (m_zero_args_constructor < 0 && - g_callable_info_get_n_args(func_info) == 0) { + if (m_zero_args_constructor < 0 && func_info.n_args() == 0) { m_zero_args_constructor = i; zero_args_constructor_name = gjs_intern_string_to_id(context, func_info.name()); @@ -999,6 +897,7 @@ bool BoxedPrototype::init(JSContext* context) { m_default_constructor_name = atoms.new_(); } } + i++; } if (m_default_constructor < 0) { @@ -1024,9 +923,9 @@ bool BoxedPrototype::init(JSContext* context) { */ bool BoxedPrototype::define_class(JSContext* context, JS::HandleObject in_object, - GIStructInfo* info) { + const GI::StructInfo info) { JS::RootedObject prototype(context), unused_constructor(context); - GType gtype = g_registered_type_info_get_g_type(info); + GType gtype = info.gtype(); BoxedPrototype* priv = BoxedPrototype::create_class( context, in_object, info, gtype, &unused_constructor, &prototype); if (!priv || !priv->define_boxed_class_fields(context, prototype)) @@ -1045,14 +944,13 @@ bool BoxedPrototype::define_class(JSContext* context, * std::forward in order to avoid duplicating code. */ template JSObject* BoxedInstance::new_for_c_struct_impl(JSContext* cx, - GIStructInfo* info, void* gboxed, - Args&&... args) { + const GI::StructInfo info, + void* gboxed, Args&&... args) { if (gboxed == NULL) return NULL; - gjs_debug_marshal(GJS_DEBUG_GBOXED, - "Wrapping struct %s %p with JSObject", - g_base_info_get_name((GIBaseInfo *)info), gboxed); + gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s %p with JSObject", + info.name(), gboxed); JS::RootedObject obj(cx, gjs_new_object_with_generic_prototype(cx, info)); if (!obj) @@ -1080,12 +978,14 @@ JSObject* BoxedInstance::new_for_c_struct_impl(JSContext* cx, * the passed-in pointer but not own it, while the normal method will take a * reference, or if the boxed type can be directly allocated, copy the memory. */ -JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, +JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, + const GI::StructInfo info, void* gboxed) { return new_for_c_struct_impl(cx, info, gboxed); } -JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, +JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, + const GI::StructInfo info, void* gboxed, NoCopy no_copy) { return new_for_c_struct_impl(cx, info, gboxed, no_copy); } diff --git a/gi/boxed.h b/gi/boxed.h index 087cf7fd8..306b40321 100644 --- a/gi/boxed.h +++ b/gi/boxed.h @@ -12,7 +12,6 @@ #include // for unique_ptr -#include #include #include @@ -22,10 +21,11 @@ #include #include #include +#include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/wrapperutils.h" -#include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" @@ -67,21 +67,23 @@ class BoxedBase // Helper methods that work on either instances or prototypes GJS_JSAPI_RETURN_CONVENTION - GIFieldInfo* get_field_info(JSContext* cx, uint32_t id) const; + mozilla::Maybe get_field_info(JSContext*, + uint32_t id) const; public: [[nodiscard]] BoxedBase* get_copy_source(JSContext* cx, JS::Value value) const; }; -class BoxedPrototype : public GIWrapperPrototype { +class BoxedPrototype + : public GIWrapperPrototype { friend class GIWrapperPrototype; + GI::AutoStructInfo, GI::StructInfo>; friend class GIWrapperBase; using FieldMap = - JS::GCHashMap, GjsAutoFieldInfo, + JS::GCHashMap, GI::AutoFieldInfo, js::DefaultHasher, js::SystemAllocPolicy>; int m_zero_args_constructor; // -1 if none @@ -91,13 +93,11 @@ class BoxedPrototype : public GIWrapperPrototype= 0; } - [[nodiscard]] GIFunctionInfo* zero_args_constructor_info() const { - return g_struct_info_get_method(info(), m_zero_args_constructor); + [[nodiscard]] + GI::AutoFunctionInfo zero_args_constructor_info() const { + g_assert(has_zero_args_constructor()); + return *info().methods()[m_zero_args_constructor]; } // The ID is traced from the object, so it's OK to create a handle from it. [[nodiscard]] JS::HandleId default_constructor_name() const { return JS::HandleId::fromMarkedLocation( - m_default_constructor_name.address()); + m_default_constructor_name.unsafeAddress()); } // JSClass operations @@ -138,8 +140,8 @@ class BoxedPrototype : public GIWrapperPrototype create_field_map( - JSContext* cx, GIStructInfo* struct_info); + static std::unique_ptr create_field_map(JSContext*, + const GI::StructInfo); GJS_JSAPI_RETURN_CONVENTION bool ensure_field_map(JSContext* cx); GJS_JSAPI_RETURN_CONVENTION @@ -148,9 +150,10 @@ class BoxedPrototype : public GIWrapperPrototype lookup_field(JSContext*, + JSString* prop_name); }; class BoxedInstance @@ -197,13 +200,11 @@ class BoxedInstance GJS_JSAPI_RETURN_CONVENTION bool get_nested_interface_object(JSContext* cx, JSObject* parent_obj, - GIFieldInfo* field_info, - GIStructInfo* interface_info, + const GI::FieldInfo, const GI::StructInfo, JS::MutableHandleValue value) const; GJS_JSAPI_RETURN_CONVENTION - bool set_nested_interface_object(JSContext* cx, GIFieldInfo* field_info, - GIStructInfo* interface_info, - JS::HandleValue value); + bool set_nested_interface_object(JSContext*, const GI::FieldInfo, + const GI::StructInfo, JS::HandleValue); GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr); @@ -211,11 +212,10 @@ class BoxedInstance // JS property accessors GJS_JSAPI_RETURN_CONVENTION - bool field_getter_impl(JSContext* cx, JSObject* obj, GIFieldInfo* info, + bool field_getter_impl(JSContext*, JSObject*, const GI::FieldInfo, JS::MutableHandleValue rval) const; GJS_JSAPI_RETURN_CONVENTION - bool field_setter_impl(JSContext* cx, GIFieldInfo* info, - JS::HandleValue value); + bool field_setter_impl(JSContext*, const GI::FieldInfo, JS::HandleValue); // JS constructor @@ -235,14 +235,14 @@ class BoxedInstance bool init_from_c_struct(JSContext* cx, void* gboxed, NoCopy); template GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct_impl( - JSContext* cx, GIStructInfo* info, void* gboxed, Args&&... args); + JSContext*, const GI::StructInfo, void* gboxed, Args&&...); public: GJS_JSAPI_RETURN_CONVENTION - static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, + static JSObject* new_for_c_struct(JSContext*, const GI::StructInfo, void* gboxed); GJS_JSAPI_RETURN_CONVENTION - static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, + static JSObject* new_for_c_struct(JSContext*, const GI::StructInfo, void* gboxed, NoCopy); }; diff --git a/gi/closure.h b/gi/closure.h index 2bbc04bd0..00465ac64 100644 --- a/gi/closure.h +++ b/gi/closure.h @@ -16,8 +16,8 @@ #include #include "gi/utils-inl.h" +#include "cjs/auto.h" #include "cjs/jsapi-util-root.h" -#include "cjs/jsapi-util.h" #include "cjs/macros.h" class JSTracer; @@ -53,7 +53,7 @@ class Closure : public GClosure { static void unref(Closure* self) { g_closure_unref(self); } public: - using Ptr = GjsAutoPointer; + using Ptr = Gjs::AutoPointer; [[nodiscard]] constexpr static Closure* for_gclosure(GClosure* gclosure) { // We need to do this in order to ensure this is a constant expression diff --git a/gi/enumeration.cpp b/gi/enumeration.cpp index fb8ba356e..e43110486 100644 --- a/gi/enumeration.cpp +++ b/gi/enumeration.cpp @@ -6,7 +6,6 @@ #include -#include #include #include @@ -17,29 +16,27 @@ #include "gi/cwrapper.h" #include "gi/enumeration.h" +#include "gi/info.h" #include "gi/wrapperutils.h" +#include "cjs/auto.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_define_enum_value(JSContext *context, - JS::HandleObject in_object, - GIValueInfo *info) -{ - const char* value_name; +static bool gjs_define_enum_value(JSContext* context, + JS::HandleObject in_object, + const GI::ValueInfo info) { gsize i; - gint64 value_val; - value_name = g_base_info_get_name( (GIBaseInfo*) info); - value_val = g_value_info_get_value(info); + const char* value_name = info.name(); + int64_t value_val = info.value(); /* g-i converts enum members such as GDK_GRAVITY_SOUTH_WEST to * Gdk.GravityType.south-west (where 'south-west' is value_name) * Convert back to all SOUTH_WEST. */ - GjsAutoChar fixed_name = g_ascii_strup(value_name, -1); + Gjs::AutoChar fixed_name{g_ascii_strup(value_name, -1)}; for (i = 0; fixed_name[i]; ++i) { char c = fixed_name[i]; if (!(('A' <= c && c <= 'Z') || @@ -64,33 +61,20 @@ gjs_define_enum_value(JSContext *context, return true; } -bool -gjs_define_enum_values(JSContext *context, - JS::HandleObject in_object, - GIEnumInfo *info) -{ - int i, n_values; - +bool gjs_define_enum_values(JSContext* context, JS::HandleObject in_object, + const GI::EnumInfo info) { /* Fill in enum values first, so we don't define the enum itself until we're * sure we can finish successfully. */ - n_values = g_enum_info_get_n_values(info); - for (i = 0; i < n_values; ++i) { - GjsAutoBaseInfo value_info = g_enum_info_get_value(info, i); - + for (GI::AutoValueInfo value_info : info.values()) { if (!gjs_define_enum_value(context, in_object, value_info)) return false; } return true; } -bool -gjs_define_enumeration(JSContext *context, - JS::HandleObject in_object, - GIEnumInfo *info) -{ - const char *enum_name; - +bool gjs_define_enumeration(JSContext* context, JS::HandleObject in_object, + const GI::EnumInfo info) { /* An enumeration is simply an object containing integer attributes for * each enum value. It does not have a special JSClass. * @@ -100,27 +84,24 @@ gjs_define_enumeration(JSContext *context, * be more complicated in general. I think this is fine. */ - enum_name = g_base_info_get_name( (GIBaseInfo*) info); + const char* enum_name = info.name(); JS::RootedObject enum_obj(context, JS_NewPlainObject(context)); if (!enum_obj) { - gjs_throw(context, "Could not create enumeration %s.%s", - g_base_info_get_namespace(info), enum_name); + gjs_throw(context, "Could not create enumeration %s.%s", info.ns(), + enum_name); return false; } - GType gtype = g_registered_type_info_get_g_type(info); + GType gtype = info.gtype(); if (!gjs_define_enum_values(context, enum_obj, info) || - !gjs_define_static_methods(context, enum_obj, gtype, - info) || + !gjs_define_static_methods(context, enum_obj, gtype, info) || !gjs_wrapper_define_gtype_prop(context, enum_obj, gtype)) return false; - gjs_debug(GJS_DEBUG_GENUM, - "Defining %s.%s as %p", - g_base_info_get_namespace( (GIBaseInfo*) info), - enum_name, enum_obj.get()); + gjs_debug(GJS_DEBUG_GENUM, "Defining %s.%s as %p", info.ns(), enum_name, + enum_obj.get()); if (!JS_DefineProperty(context, in_object, enum_name, enum_obj, GJS_MODULE_PROP_FLAGS)) { diff --git a/gi/enumeration.h b/gi/enumeration.h index 4f8376eb5..d03f6d749 100644 --- a/gi/enumeration.h +++ b/gi/enumeration.h @@ -7,20 +7,17 @@ #include -#include - #include +#include "gi/info.h" #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION -bool gjs_define_enum_values(JSContext *context, - JS::HandleObject in_object, - GIEnumInfo *info); +bool gjs_define_enum_values(JSContext*, JS::HandleObject in_object, + const GI::EnumInfo); GJS_JSAPI_RETURN_CONVENTION -bool gjs_define_enumeration(JSContext *context, - JS::HandleObject in_object, - GIEnumInfo *info); +bool gjs_define_enumeration(JSContext*, JS::HandleObject in_object, + const GI::EnumInfo); #endif // GI_ENUMERATION_H_ diff --git a/gi/foreign.cpp b/gi/foreign.cpp index 67a0a6c64..225e1300f 100644 --- a/gi/foreign.cpp +++ b/gi/foreign.cpp @@ -10,13 +10,14 @@ #include #include // for pair -#include +#include #include #include #include #include "gi/foreign.h" +#include "gi/info.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -65,12 +66,11 @@ void gjs_struct_foreign_register(const char* gi_namespace, GJS_JSAPI_RETURN_CONVENTION static GjsForeignInfo* gjs_struct_foreign_lookup(JSContext* cx, - GIStructInfo* info) { - const char* ns = g_base_info_get_namespace(info); - StructID key{ns, g_base_info_get_name(info)}; + const GI::StructInfo info) { + StructID key{info.ns(), info.name()}; auto entry = foreign_structs_table.find(key); if (entry == foreign_structs_table.end()) { - if (gjs_foreign_load_foreign_module(cx, ns)) + if (gjs_foreign_load_foreign_module(cx, info.ns())) entry = foreign_structs_table.find(key); } @@ -84,7 +84,7 @@ static GjsForeignInfo* gjs_struct_foreign_lookup(JSContext* cx, } bool gjs_struct_foreign_convert_to_gi_argument( - JSContext* context, JS::Value value, GIStructInfo* info, + JSContext* context, JS::Value value, const GI::StructInfo info, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { GjsForeignInfo *foreign; @@ -102,7 +102,7 @@ bool gjs_struct_foreign_convert_to_gi_argument( bool gjs_struct_foreign_convert_from_gi_argument(JSContext* context, JS::MutableHandleValue value_p, - GIStructInfo* info, + const GI::StructInfo info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(context, info); if (!foreign) @@ -116,7 +116,7 @@ bool gjs_struct_foreign_convert_from_gi_argument(JSContext* context, bool gjs_struct_foreign_release_gi_argument(JSContext* context, GITransfer transfer, - GIStructInfo* info, + const GI::StructInfo info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(context, info); if (!foreign) diff --git a/gi/foreign.h b/gi/foreign.h index 4f16756a0..02a3468e0 100644 --- a/gi/foreign.h +++ b/gi/foreign.h @@ -7,12 +7,13 @@ #include -#include +#include #include #include #include "gi/arg.h" +#include "gi/info.h" #include "cjs/macros.h" typedef bool (*GjsArgOverrideToGIArgumentFunc)(JSContext*, JS::Value, @@ -38,17 +39,18 @@ void gjs_struct_foreign_register(const char* gi_namespace, GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_to_gi_argument(JSContext*, JS::Value, - GIStructInfo*, + const GI::StructInfo, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_from_gi_argument(JSContext*, JS::MutableHandleValue, - GIStructInfo*, GIArgument*); + const GI::StructInfo, + GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_release_gi_argument(JSContext*, GITransfer, - GIStructInfo*, GIArgument*); + const GI::StructInfo, GIArgument*); #endif // GI_FOREIGN_H_ diff --git a/gi/function.cpp b/gi/function.cpp index c2d308fdb..c1c68068b 100644 --- a/gi/function.cpp +++ b/gi/function.cpp @@ -14,8 +14,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -37,17 +37,22 @@ #include #include // for HandleValueArray #include // for JSProtoKey +#include #include "gi/arg-cache.h" #include "gi/arg-inl.h" +#include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/gerror.h" +#include "gi/info.h" #include "gi/object.h" #include "gi/utils-inl.h" +#include "cjs/auto.h" #include "cjs/context-private.h" +#include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -55,6 +60,8 @@ #include "cjs/profiler-private.h" #include "util/log.h" +using mozilla::Maybe, mozilla::Some; + /* We use guint8 for arguments; functions can't * have more than this. */ @@ -69,7 +76,7 @@ class Function : public CWrapper { static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_function; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GFUNCTION; - GjsAutoCallableInfo m_info; + GI::AutoCallableInfo m_info; ArgsCache m_arguments; @@ -77,11 +84,8 @@ class Function : public CWrapper { uint8_t m_js_out_argc; GIFunctionInvoker m_invoker; - explicit Function(GICallableInfo* info) - : m_info(info, GjsAutoTakeOwnership()), - m_js_in_argc(0), - m_js_out_argc(0), - m_invoker({}) { + explicit Function(const GI::CallableInfo info) + : m_info(info), m_js_in_argc(0), m_js_out_argc(0), m_invoker({}) { GJS_INC_COUNTER(function); } ~Function(); @@ -158,7 +162,7 @@ class Function : public CWrapper { public: GJS_JSAPI_RETURN_CONVENTION - static JSObject* create(JSContext* cx, GType gtype, GICallableInfo* info); + static JSObject* create(JSContext*, GType, const GI::CallableInfo); [[nodiscard]] std::string format_name(); @@ -168,7 +172,8 @@ class Function : public CWrapper { GIArgument* r_value = nullptr); GJS_JSAPI_RETURN_CONVENTION - static bool invoke_constructor_uncached(JSContext* cx, GIFunctionInfo* info, + static bool invoke_constructor_uncached(JSContext* cx, + const GI::FunctionInfo info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { @@ -181,23 +186,24 @@ class Function : public CWrapper { } // namespace Gjs -template +template static inline void set_ffi_arg(void* result, GIArgument* value) { + using T = Gjs::Tag::RealT; if constexpr (std::is_integral_v && std::is_signed_v) { - *static_cast(result) = gjs_arg_get(value); + *static_cast(result) = gjs_arg_get(value); } else if constexpr (std::is_floating_point_v || std::is_unsigned_v) { - *static_cast(result) = gjs_arg_get(value); + *static_cast(result) = gjs_arg_get(value); } else if constexpr (std::is_pointer_v) { *static_cast(result) = - gjs_pointer_to_int(gjs_arg_get(value)); + gjs_pointer_to_int(gjs_arg_get(value)); } } -static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type, +static void set_return_ffi_arg_from_gi_argument(const GI::TypeInfo ret_type, void* result, GIArgument* return_value) { // Be consistent with gjs_value_to_gi_argument() - switch (g_type_info_get_tag(ret_type)) { + switch (ret_type.tag()) { case GI_TYPE_TAG_VOID: g_assert_not_reached(); case GI_TYPE_TAG_INT8: @@ -219,7 +225,7 @@ static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type, set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_BOOLEAN: - set_ffi_arg(result, return_value); + set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UNICHAR: set_ffi_arg(result, return_value); @@ -228,16 +234,10 @@ static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type, set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INTERFACE: - { - GjsAutoBaseInfo interface_info(g_type_info_get_interface(ret_type)); - GIInfoType interface_type = interface_info.type(); - - if (interface_type == GI_INFO_TYPE_ENUM || - interface_type == GI_INFO_TYPE_FLAGS) - set_ffi_arg(result, return_value); - else - set_ffi_arg(result, return_value); - } + if (ret_type.interface().is_enum_or_flags()) + set_ffi_arg(result, return_value); + else + set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT64: // Other primitive types need to squeeze into 64-bit ffi_arg too @@ -250,7 +250,7 @@ static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type, set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_GTYPE: - set_ffi_arg(result, return_value); + set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: @@ -274,11 +274,10 @@ void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when, 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." : "."); - } + << "Because it would crash the application, it has been blocked.\n" + << "The offending callback was " << m_info.name() << "()" + << (m_is_vfunc ? ", a vfunc." : "."); + if (dump_stack) { message << "\n" << gjs_dumpstack_string(); } @@ -293,14 +292,14 @@ void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when, * getting the return value back. */ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { - GITypeInfo ret_type; + GI::StackTypeInfo 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) { + m_info.load_return_type(&ret_type); + if (ret_type.tag() != GI_TYPE_TAG_VOID) { GIArgument argument = {}; - gjs_gi_argument_init_default(&ret_type, &argument); - set_return_ffi_arg_from_gi_argument(&ret_type, result, &argument); + gjs_arg_unset(&argument); + set_return_ffi_arg_from_gi_argument(ret_type, result, &argument); } if (G_UNLIKELY(!is_valid())) { @@ -332,8 +331,7 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { JSAutoRealm ar(context, callable()); - int n_args = g_callable_info_get_n_args(m_info); - g_assert(n_args >= 0); + unsigned n_args = m_info.n_args(); struct AutoCallbackData { AutoCallbackData(GjsCallbackTrampoline* trampoline, @@ -356,7 +354,7 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { AutoCallbackData callback_data(this, gjs); JS::RootedObject this_object(context); - int c_args_offset = 0; + unsigned c_args_offset = 0; GObject* gobj = nullptr; if (m_is_vfunc) { gobj = G_OBJECT(gjs_arg_get(args[0])); @@ -382,7 +380,7 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { JS::RootedValue rval(context); if (!callback_closure_inner(context, this_object, gobj, &rval, args, - &ret_type, n_args, c_args_offset, result)) { + ret_type, n_args, c_args_offset, result)) { if (!JS_IsExceptionPending(context)) { // "Uncatchable" exception thrown, we have to exit. We may be in a // main loop, or maybe not, but there's no way to tell, so we have @@ -402,7 +400,7 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { // value that was thrown. Otherwise, log it as "uncaught" (critical // instead of warning) - if (!g_callable_info_can_throw_gerror(m_info)) { + if (!m_info.can_throw_gerror()) { gjs_log_exception_uncaught(context); return; } @@ -416,9 +414,9 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { } } -inline GIArgument* get_argument_for_arg_info(GIArgInfo* arg_info, +inline GIArgument* get_argument_for_arg_info(const GI::ArgInfo arg_info, GIArgument** args, int index) { - if (!g_arg_info_is_caller_allocates(arg_info)) + if (!arg_info.caller_allocates()) return *reinterpret_cast(args[index]); else return args[index]; @@ -426,39 +424,39 @@ inline GIArgument* get_argument_for_arg_info(GIArgInfo* arg_info, bool GjsCallbackTrampoline::callback_closure_inner( JSContext* context, JS::HandleObject this_object, GObject* gobject, - JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type, - int n_args, int c_args_offset, void* result) { - int n_outargs = 0; + JS::MutableHandleValue rval, GIArgument** args, const GI::TypeInfo ret_type, + unsigned n_args, unsigned c_args_offset, void* result) { + unsigned n_outargs = 0; JS::RootedValueVector jsargs(context); if (!jsargs.reserve(n_args)) g_error("Unable to reserve space for vector"); - GITypeTag ret_tag = g_type_info_get_tag(ret_type); + GITypeTag ret_tag = ret_type.tag(); 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; - GITypeInfo type_info; + for (unsigned i = 0, n_jsargs = 0; i < n_args; i++) { + GI::StackArgInfo arg_info; + GI::StackTypeInfo type_info; GjsParamType param_type; - g_callable_info_load_arg(m_info, i, &arg_info); - g_arg_info_load_type(&arg_info, &type_info); + m_info.load_arg(i, &arg_info); + arg_info.load_type(&type_info); /* Skip void * arguments */ - if (g_type_info_get_tag(&type_info) == GI_TYPE_TAG_VOID) + if (type_info.tag() == GI_TYPE_TAG_VOID) continue; - if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT) { + if (arg_info.direction() == GI_DIRECTION_OUT) { n_outargs++; continue; } - if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT) + if (arg_info.direction() == GI_DIRECTION_INOUT) n_outargs++; - if (g_arg_info_get_ownership_transfer(&arg_info) != GI_TRANSFER_NOTHING) + if (arg_info.ownership_transfer() != GI_TRANSFER_NOTHING) in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER; param_type = m_param_types[i]; @@ -467,24 +465,27 @@ bool GjsCallbackTrampoline::callback_closure_inner( case PARAM_SKIPPED: continue; case PARAM_ARRAY: { - gint array_length_pos = g_type_info_get_array_length(&type_info); - GIArgInfo array_length_arg; - GITypeInfo arg_type_info; + // In initialize(), we already don't store PARAM_ARRAY for non- + // fixed-size arrays + unsigned array_length_pos = + type_info.array_length_index().value(); - g_callable_info_load_arg(m_info, array_length_pos, - &array_length_arg); - g_arg_info_load_type(&array_length_arg, &arg_type_info); + GI::StackArgInfo array_length_arg; + GI::StackTypeInfo arg_type_info; + + m_info.load_arg(array_length_pos, &array_length_arg); + array_length_arg.load_type(&arg_type_info); size_t length = gjs_gi_argument_get_array_length( - g_type_info_get_tag(&arg_type_info), + arg_type_info.tag(), args[array_length_pos + c_args_offset]); if (!jsargs.growBy(1)) g_error("Unable to grow vector"); 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)) + context, jsargs[n_jsargs++], type_info, + arg_info.ownership_transfer(), args[i + c_args_offset], + length)) return false; break; } @@ -493,12 +494,12 @@ bool GjsCallbackTrampoline::callback_closure_inner( g_error("Unable to grow vector"); GIArgument* arg = args[i + c_args_offset]; - if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT && - !g_arg_info_is_caller_allocates(&arg_info)) + if (arg_info.direction() == GI_DIRECTION_INOUT && + !arg_info.caller_allocates()) arg = *reinterpret_cast(arg); if (!gjs_value_from_gi_argument(context, jsargs[n_jsargs++], - &type_info, arg, false)) + type_info, arg, false)) return false; break; } @@ -519,9 +520,8 @@ bool GjsCallbackTrampoline::callback_closure_inner( /* void return value, no out args, nothing to do */ } else if (n_outargs == 0) { GIArgument argument; - GITransfer transfer; - transfer = g_callable_info_get_caller_owns(m_info); + GITransfer transfer = m_info.caller_owns(); /* non-void return value, no out args. Should * be a single return value. */ if (!gjs_value_to_gi_argument(context, rval, ret_type, "callback", @@ -533,15 +533,15 @@ bool GjsCallbackTrampoline::callback_closure_inner( } else if (n_outargs == 1 && ret_type_is_void) { /* void return value, one out args. Should * be a single return value. */ - for (int i = 0; i < n_args; i++) { - GIArgInfo arg_info; - g_callable_info_load_arg(m_info, i, &arg_info); - if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) + for (unsigned i = 0; i < n_args; i++) { + GI::StackArgInfo arg_info; + m_info.load_arg(i, &arg_info); + if (arg_info.direction() == GI_DIRECTION_IN) continue; if (!gjs_value_to_callback_out_arg( - context, rval, &arg_info, - get_argument_for_arg_info(&arg_info, args, + context, rval, arg_info, + get_argument_for_arg_info(arg_info, args, i + c_args_offset))) return false; @@ -569,7 +569,7 @@ bool GjsCallbackTrampoline::callback_closure_inner( if (!ret_type_is_void) { GIArgument argument; - GITransfer transfer = g_callable_info_get_caller_owns(m_info); + GITransfer transfer = m_info.caller_owns(); if (!JS_GetElement(context, out_array, elem_idx, &elem)) return false; @@ -590,8 +590,8 @@ bool GjsCallbackTrampoline::callback_closure_inner( ObjectInstance::associate_string( gobject, gjs_arg_get(&argument)); } else { - GjsAutoChar str = gjs_arg_steal(&argument); - gjs_arg_set(&argument, g_intern_string(str)); + Gjs::AutoChar str{gjs_arg_steal(&argument)}; + gjs_arg_set(&argument, g_intern_string(str)); } } @@ -600,18 +600,18 @@ bool GjsCallbackTrampoline::callback_closure_inner( elem_idx++; } - for (int i = 0; i < n_args; i++) { - GIArgInfo arg_info; - g_callable_info_load_arg(m_info, i, &arg_info); - if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) + for (unsigned i = 0; i < n_args; i++) { + GI::StackArgInfo arg_info; + m_info.load_arg(i, &arg_info); + if (arg_info.direction() == GI_DIRECTION_IN) continue; if (!JS_GetElement(context, out_array, elem_idx, &elem)) return false; if (!gjs_value_to_callback_out_arg( - context, elem, &arg_info, - get_argument_for_arg_info(&arg_info, args, + context, elem, arg_info, + get_argument_for_arg_info(arg_info, args, i + c_args_offset))) return false; @@ -622,49 +622,48 @@ 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); + for (unsigned i = 0; i < n_args; i++) { + GI::StackArgInfo arg_info; + m_info.load_arg(i, &arg_info); + GITransfer transfer = arg_info.ownership_transfer(); if (transfer == GI_TRANSFER_NOTHING) continue; - if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN) + if (arg_info.direction() != 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); + GI::StackTypeInfo type_info; + arg_info.load_type(&type_info); - if (!gjs_gi_argument_release(context, transfer, &type_info, arg)) + if (!gjs_gi_argument_release(context, transfer, type_info, arg)) return false; continue; } struct InvalidateData { - GIArgInfo arg_info; + GI::StackArgInfo arg_info; GIArgument arg; }; - auto* data = new InvalidateData({arg_info, *arg}); + auto* data = new InvalidateData({std::move(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); + GITransfer transfer = data->arg_info.ownership_transfer(); - GITypeInfo type_info; - g_arg_info_load_type(&data->arg_info, &type_info); + GI::StackTypeInfo type_info; + data->arg_info.load_type(&type_info); if (!gjs_gi_argument_release(self->context(), transfer, - &type_info, &data->arg)) { + type_info, &data->arg)) { gjs_throw(self->context(), "Impossible to release closure argument '%s'", - g_base_info_get_name(&data->arg_info)); + data->arg_info.name()); } }); } @@ -673,8 +672,9 @@ bool GjsCallbackTrampoline::callback_closure_inner( } GjsCallbackTrampoline* GjsCallbackTrampoline::create( - JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, - GIScopeType scope, bool has_scope_object, bool is_vfunc) { + JSContext* cx, JS::HandleObject callable, + const GI::CallableInfo callable_info, GIScopeType scope, + bool has_scope_object, bool is_vfunc) { g_assert(JS::IsCallable(callable) && "tried to create a callback trampoline for a non-callable object"); @@ -693,35 +693,31 @@ decltype(GjsCallbackTrampoline::s_forever_closure_list) GjsCallbackTrampoline::s_forever_closure_list; GjsCallbackTrampoline::GjsCallbackTrampoline( - JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, - GIScopeType scope, bool has_scope_object, bool is_vfunc) + // optional? + JSContext* cx, JS::HandleObject callable, + const GI::CallableInfo callable_info, GIScopeType scope, + bool has_scope_object, bool is_vfunc) // The rooting rule is: // - notify callbacks in GObject methods are traced from the scope object // - async and call callbacks, and other notify callbacks, are rooted // - vfuncs are traced from the GObject prototype : Closure(cx, callable, scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object, - g_base_info_get_name(callable_info)), - m_info(callable_info, GjsAutoTakeOwnership()), - m_param_types(std::make_unique( - g_callable_info_get_n_args(callable_info))), + callable_info.name()), + m_info(callable_info), + m_param_types(std::make_unique(callable_info.n_args())), m_scope(scope), m_is_vfunc(is_vfunc) { add_finalize_notifier(); } GjsCallbackTrampoline::~GjsCallbackTrampoline() { - if (m_info && m_closure) { -#if GI_CHECK_VERSION(1, 71, 0) - g_callable_info_destroy_closure(m_info, m_closure); -#else - g_callable_info_free_closure(m_info, m_closure); -#endif - } + if (m_closure) + m_info.destroy_closure(m_closure); } void GjsCallbackTrampoline::mark_forever() { - s_forever_closure_list.emplace_back(this, GjsAutoTakeOwnership{}); + s_forever_closure_list.emplace_back(this, Gjs::TakeOwnership{}); } void GjsCallbackTrampoline::prepare_shutdown() { @@ -732,17 +728,13 @@ ffi_closure* GjsCallbackTrampoline::create_closure() { auto callback = [](ffi_cif*, void* result, void** ffi_args, void* data) { auto** args = reinterpret_cast(ffi_args); g_assert(data && "Trampoline data is not set"); - Gjs::Closure::Ptr trampoline(static_cast(data), - GjsAutoTakeOwnership()); + Gjs::Closure::Ptr trampoline{static_cast(data), + Gjs::TakeOwnership{}}; trampoline.as()->callback_closure(args, result); }; -#if GI_CHECK_VERSION(1, 71, 0) - return g_callable_info_create_closure(m_info, &m_cif, callback, this); -#else - return g_callable_info_prepare_closure(m_info, &m_cif, callback, this); -#endif + return m_info.create_closure(&m_cif, callback, this); } bool GjsCallbackTrampoline::initialize() { @@ -751,21 +743,19 @@ bool GjsCallbackTrampoline::initialize() { /* Analyze param types and directions, similarly to * init_cached_function_data */ - int n_param_types = g_callable_info_get_n_args(m_info); - for (int i = 0; i < n_param_types; i++) { - GIDirection direction; - GIArgInfo arg_info; - GITypeInfo type_info; - GITypeTag type_tag; + unsigned n_param_types = m_info.n_args(); + for (unsigned i = 0; i < n_param_types; i++) { + GI::StackArgInfo arg_info; + GI::StackTypeInfo type_info; if (m_param_types[i] == PARAM_SKIPPED) continue; - g_callable_info_load_arg(m_info, i, &arg_info); - g_arg_info_load_type(&arg_info, &type_info); + m_info.load_arg(i, &arg_info); + arg_info.load_type(&type_info); - direction = g_arg_info_get_direction(&arg_info); - type_tag = g_type_info_get_tag(&type_info); + GIDirection direction = arg_info.direction(); + GITypeTag type_tag = type_info.tag(); if (direction != GI_DIRECTION_IN) { /* INOUT and OUT arguments are handled differently. */ @@ -773,10 +763,7 @@ bool GjsCallbackTrampoline::initialize() { } if (type_tag == GI_TYPE_TAG_INTERFACE) { - GjsAutoBaseInfo interface_info = - g_type_info_get_interface(&type_info); - GIInfoType interface_type = interface_info.type(); - if (interface_type == GI_INFO_TYPE_CALLBACK) { + if (type_info.interface().is_callback()) { gjs_throw(context(), "%s %s accepts another callback as a parameter. This " "is not supported", @@ -784,18 +771,17 @@ bool GjsCallbackTrampoline::initialize() { return false; } } else if (type_tag == GI_TYPE_TAG_ARRAY) { - if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) { - int array_length_pos = g_type_info_get_array_length(&type_info); - - if (array_length_pos < 0) + if (type_info.array_type() == GI_ARRAY_TYPE_C) { + Maybe array_length_pos = + type_info.array_length_index(); + if (!array_length_pos) continue; - if (array_length_pos < n_param_types) { - GIArgInfo length_arg_info; + if (*array_length_pos < n_param_types) { + GI::StackArgInfo length_arg_info; + m_info.load_arg(*array_length_pos, &length_arg_info); - g_callable_info_load_arg(m_info, array_length_pos, - &length_arg_info); - if (g_arg_info_get_direction(&length_arg_info) != direction) { + if (length_arg_info.direction() != direction) { gjs_throw(context(), "%s %s has an array with different-direction " "length argument. This is not supported", @@ -804,7 +790,7 @@ bool GjsCallbackTrampoline::initialize() { return false; } - m_param_types[array_length_pos] = PARAM_SKIPPED; + m_param_types[*array_length_pos] = PARAM_SKIPPED; m_param_types[i] = PARAM_ARRAY; } } @@ -817,13 +803,13 @@ bool GjsCallbackTrampoline::initialize() { // Intended for error messages std::string Gjs::Function::format_name() { - bool is_method = g_callable_info_is_method(m_info); + bool is_method = m_info.is_method(); std::string retval = is_method ? "method" : "function"; retval += ' '; retval += m_info.ns(); retval += '.'; if (is_method) { - retval += g_base_info_get_name(g_base_info_get_container(m_info)); + retval += m_info.container()->name(); retval += '.'; } retval += m_info.name(); @@ -833,12 +819,14 @@ std::string Gjs::Function::format_name() { namespace Gjs { static void* get_return_ffi_pointer_from_gi_argument( - GITypeTag tag, GITypeInfo* return_type, GIFFIReturnValue* return_value) { - if (return_type && g_type_info_is_pointer(return_type)) + Maybe return_tag, GIFFIReturnValue* return_value) { + if (!return_tag) + return nullptr; + if (return_tag->is_pointer()) return &gjs_arg_member(return_value); - switch (tag) { + switch (return_tag->tag()) { case GI_TYPE_TAG_VOID: - return nullptr; + g_assert_not_reached(); case GI_TYPE_TAG_INT8: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INT16: @@ -852,7 +840,7 @@ static void* get_return_ffi_pointer_from_gi_argument( case GI_TYPE_TAG_UINT32: return &gjs_arg_member(return_value); case GI_TYPE_TAG_BOOLEAN: - return &gjs_arg_member(return_value); + return &gjs_arg_member(return_value); case GI_TYPE_TAG_UNICHAR: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INT64: @@ -864,20 +852,9 @@ static void* get_return_ffi_pointer_from_gi_argument( case GI_TYPE_TAG_DOUBLE: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INTERFACE: { - if (!return_type) - return nullptr; - - GjsAutoBaseInfo info = g_type_info_get_interface(return_type); - - switch (info.type()) { - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - return &gjs_arg_member( - return_value); - default: - return &gjs_arg_member(return_value); - } - break; + if (return_tag->is_enum_or_flags_interface()) + return &gjs_arg_member(return_value); + [[fallthrough]]; } default: return &gjs_arg_member(return_value); @@ -894,7 +871,6 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, g_assert((args.isConstructing() || !this_obj) && "If not a constructor, then pass the 'this' object via CallArgs"); - void* return_value_p; // will point inside the return GIArgument union GIFFIReturnValue return_value; unsigned ffi_argc = m_invoker.cif.nargs; @@ -956,7 +932,8 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, GIArgument* in_value = state.instance(); JS::RootedValue in_js_value(context, JS::ObjectValue(*obj)); - if (!m_arguments.instance()->in(context, &state, in_value, in_js_value)) + if (!m_arguments.instance().value()->in(context, &state, in_value, + in_js_value)) return false; ffi_arg_pointers[ffi_arg_pos] = in_value; @@ -964,22 +941,22 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, // Callback lifetimes will be attached to the instance object if it is // a GObject or GInterface - GType gtype = m_arguments.instance_type(); - if (gtype != G_TYPE_NONE) { - if (g_type_is_a(gtype, G_TYPE_OBJECT) || - g_type_is_a(gtype, G_TYPE_INTERFACE)) + Maybe gtype = m_arguments.instance_type(); + if (gtype) { + if (g_type_is_a(*gtype, G_TYPE_OBJECT) || + g_type_is_a(*gtype, G_TYPE_INTERFACE)) state.instance_object = obj; - if (g_type_is_a(gtype, G_TYPE_OBJECT)) { + if (g_type_is_a(*gtype, G_TYPE_OBJECT)) { auto* o = ObjectBase::for_js(context, obj); - dynamicString = o->format_name(); + dynamicString = + GJS_PROFILER_DYNAMIC_STRING(context, o->format_name()); } } } - - dynamicString += '.'; - dynamicString += format_name(); - AutoProfilerLabel label(context, "", dynamicString.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + context, dynamicString + "." + format_name())}; + AutoProfilerLabel label{context, "", full_name}; g_assert(ffi_arg_pos + state.gi_argc < std::numeric_limits::max()); @@ -1000,13 +977,13 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, ffi_arg_pointers[ffi_arg_pos] = in_value; if (!gjs_arg) { - GIArgInfo arg_info; - g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info); + GI::StackArgInfo arg_info; + m_info.load_arg(gi_arg_pos, &arg_info); gjs_throw(context, "Error invoking %s: impossible to determine what to pass " "to the '%s' argument. It may be that the function is " "unsupported, or there may be a bug in its annotations.", - format_name().c_str(), g_base_info_get_name(&arg_info)); + format_name().c_str(), arg_info.name()); state.failed = true; break; } @@ -1046,10 +1023,11 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc); g_assert_cmpuint(gi_arg_pos, ==, state.gi_argc); - GITypeTag return_tag = m_arguments.return_tag(); - GITypeInfo* return_type = m_arguments.return_type(); - return_value_p = get_return_ffi_pointer_from_gi_argument( - return_tag, return_type, &return_value); + Maybe return_tag = m_arguments.return_tag(); + // return_value_p will point inside the return GIFFIReturnValue union if the + // C function has a non-void return type + void* return_value_p = + get_return_ffi_pointer_from_gi_argument(return_tag, &return_value); ffi_call(&m_invoker.cif, FFI_FN(m_invoker.native_address), return_value_p, ffi_arg_pointers.get()); @@ -1059,21 +1037,17 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, if (!r_value) args.rval().setUndefined(); - if (return_type) { - gi_type_info_extract_ffi_return_value(return_type, &return_value, - state.return_value()); - } else if (return_tag != GI_TYPE_TAG_VOID) { - g_assert(GI_TYPE_TAG_IS_BASIC(return_tag)); - gi_type_tag_extract_ffi_return_value(return_tag, GI_INFO_TYPE_INVALID, - &return_value, - state.return_value()); + if (return_tag) { + gi_type_tag_extract_ffi_return_value( + return_tag->tag(), return_tag->interface_gtype(), &return_value, + state.return_value()); } // Process out arguments and return values. This loop is skipped if we fail // the type conversion above, or if state.did_throw_gerror is true. js_arg_pos = 0; for (gi_arg_pos = -1; gi_arg_pos < state.gi_argc; gi_arg_pos++) { - Argument* gjs_arg; + Maybe gjs_arg; GIArgument* out_value; if (gi_arg_pos == -1) { @@ -1081,39 +1055,37 @@ bool Function::invoke(JSContext* context, const JS::CallArgs& args, gjs_arg = m_arguments.return_value(); } else { out_value = &state.out_cvalue(gi_arg_pos); - gjs_arg = m_arguments.argument(gi_arg_pos); + gjs_arg = Some(m_arguments.argument(gi_arg_pos)); } - gjs_debug_marshal(GJS_DEBUG_GFUNCTION, - "Marshalling argument '%s' out, %d/%d GI args", - gjs_arg ? gjs_arg->arg_name() : "", - gi_arg_pos, state.gi_argc); + gjs_debug_marshal( + GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' out, %d/%d GI args", + gjs_arg.map(std::mem_fn(&Argument::arg_name)).valueOr(""), + gi_arg_pos, state.gi_argc); JS::RootedValue js_out_arg(context); if (!r_value) { if (!gjs_arg && gi_arg_pos >= 0) { - GIArgInfo arg_info; - g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info); + GI::StackArgInfo arg_info; + m_info.load_arg(gi_arg_pos, &arg_info); gjs_throw( context, - "Error invoking %s.%s: impossible to determine what " - "to pass to the out '%s' argument. It may be that the " - "function is unsupported, or there may be a bug in " - "its annotations.", - m_info.ns(), m_info.name(), - g_base_info_get_name(&arg_info)); + "Error invoking %s: impossible to determine what to pass " + "to the out '%s' argument. It may be that the function is " + "unsupported, or there may be a bug in its annotations.", + format_name().c_str(), arg_info.name()); state.failed = true; break; } if (gjs_arg && - !gjs_arg->out(context, &state, out_value, &js_out_arg)) { + !(*gjs_arg)->out(context, &state, out_value, &js_out_arg)) { state.failed = true; break; } } - if (gjs_arg && !gjs_arg->skip_out()) { + if (gjs_arg && !(*gjs_arg)->skip_out()) { if (!r_value) { if (!state.return_values.append(js_out_arg)) { JS_ReportOutOfMemory(context); @@ -1149,7 +1121,7 @@ bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args, for (int gi_arg_pos = -(state->first_arg_offset()); gi_arg_pos < state->gi_argc && ffi_arg_pos < ffi_arg_max; gi_arg_pos++, ffi_arg_pos++) { - Argument* gjs_arg; + Maybe gjs_arg; GIArgument* in_value = nullptr; GIArgument* out_value = nullptr; @@ -1162,7 +1134,7 @@ bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args, } else { in_value = &state->in_cvalue(gi_arg_pos); out_value = &state->out_cvalue(gi_arg_pos); - gjs_arg = m_arguments.argument(gi_arg_pos); + gjs_arg = Some(m_arguments.argument(gi_arg_pos)); } if (!gjs_arg) @@ -1171,11 +1143,11 @@ bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args, gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Releasing argument '%s', %d/%d GI args, %u/%u C args", - gjs_arg->arg_name(), gi_arg_pos, state->gi_argc, ffi_arg_pos, + (*gjs_arg)->arg_name(), gi_arg_pos, state->gi_argc, ffi_arg_pos, state->processed_c_args); // Only process in or inout arguments if we failed, the rest is garbage - if (state->failed && gjs_arg->skip_in()) + if (state->failed && (*gjs_arg)->skip_in()) continue; // Save the return GIArgument if it was requested @@ -1184,7 +1156,7 @@ bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args, continue; } - if (!gjs_arg->release(cx, state, in_value, out_value)) { + if (!(*gjs_arg)->release(cx, state, in_value, out_value)) { postinvoke_release_failed = true; // continue with the release even if we fail, to avoid leaks } @@ -1236,7 +1208,7 @@ bool Function::call(JSContext* context, unsigned js_argc, JS::Value* vp) { } Function::~Function() { - g_function_invoker_destroy(&m_invoker); + gi_function_invoker_clear(&m_invoker); GJS_DEC_COUNTER(function); } @@ -1257,9 +1229,8 @@ bool Function::get_length(JSContext* cx, unsigned argc, JS::Value* vp) { bool Function::get_name(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, rec, this_obj, Function, priv); - if (priv->m_info.type() == GI_INFO_TYPE_FUNCTION) - return gjs_string_from_utf8(cx, g_function_info_get_symbol(priv->m_info), - rec.rval()); + if (auto func_info = priv->m_info.as()) + return gjs_string_from_utf8(cx, func_info->symbol(), rec.rval()); return gjs_string_from_utf8(cx, priv->format_name().c_str(), rec.rval()); } @@ -1272,7 +1243,7 @@ bool Function::to_string(JSContext* context, unsigned argc, JS::Value* vp) { bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) { int i, n_jsargs; - int n_args = g_callable_info_get_n_args(m_info); + int n_args = m_info.n_args(); n_jsargs = 0; std::string arg_names; for (i = 0; i < n_args; i++) { @@ -1287,12 +1258,11 @@ bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) { arg_names += gjs_arg->arg_name(); } - GjsAutoChar descr; - if (m_info.type() == GI_INFO_TYPE_FUNCTION) { + AutoChar descr; + if (auto func_info = m_info.as()) { descr = g_strdup_printf( "%s(%s) {\n\t/* wrapper for native symbol %s() */\n}", - format_name().c_str(), arg_names.c_str(), - g_function_info_get_symbol(m_info)); + format_name().c_str(), arg_names.c_str(), func_info->symbol()); } else { descr = g_strdup_printf("%s(%s) {\n\t/* wrapper for native symbol */\n}", @@ -1329,28 +1299,29 @@ const JSFunctionSpec Function::proto_funcs[] = { bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) { guint8 i; - 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) { - if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND) - return gjs_throw_gerror(context, error); + if (auto func_info = m_info.as()) { + GErrorResult<> result = func_info->prep_invoker(&m_invoker); + if (result.isErr()) + return gjs_throw_gerror(context, result.unwrapErr()); + } else if (auto vfunc_info = m_info.as()) { + Gjs::GErrorResult result = vfunc_info->address(gtype); + if (!result.isOk()) { + if (result.inspectErr()->code != GI_INVOKE_ERROR_SYMBOL_NOT_FOUND) + return gjs_throw_gerror(context, result.unwrapErr()); gjs_throw(context, "Virtual function not implemented: %s", - error->message); + result.inspectErr()->message); return false; } - if (!g_function_invoker_new_for_address(addr, m_info, &m_invoker, - &error)) - return gjs_throw_gerror(context, error); + GErrorResult<> result2 = + m_info.init_function_invoker(result.unwrap(), &m_invoker); + if (result2.isErr()) + return gjs_throw_gerror(context, result2.unwrapErr()); } - uint8_t n_args = g_callable_info_get_n_args(m_info); + uint8_t n_args = m_info.n_args(); if (!m_arguments.initialize(context, m_info)) return false; @@ -1365,17 +1336,16 @@ bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) { for (i = 0; i < n_args; i++) { Argument* gjs_arg = m_arguments.argument(i); - GIDirection direction; - GIArgInfo arg_info; + GI::StackArgInfo arg_info; if (gjs_arg && (gjs_arg->skip_in() || gjs_arg->skip_out())) { continue; } - g_callable_info_load_arg(m_info, i, &arg_info); - direction = g_arg_info_get_direction(&arg_info); + m_info.load_arg(i, &arg_info); + GIDirection direction = arg_info.direction(); - m_arguments.build_arg(i, direction, &arg_info, m_info, &inc_counter); + m_arguments.build_arg(i, direction, arg_info, m_info, &inc_counter); if (inc_counter) { switch (direction) { @@ -1398,7 +1368,7 @@ bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) { } JSObject* Function::create(JSContext* context, GType gtype, - GICallableInfo* info) { + const GI::CallableInfo info) { JS::RootedObject proto(context, Function::create_prototype(context)); if (!proto) return nullptr; @@ -1425,26 +1395,19 @@ JSObject* Function::create(JSContext* context, GType gtype, } // namespace Gjs GJS_JSAPI_RETURN_CONVENTION -JSObject* -gjs_define_function(JSContext *context, - JS::HandleObject in_object, - GType gtype, - GICallableInfo *info) -{ - GIInfoType info_type; +JSObject* gjs_define_function(JSContext* context, JS::HandleObject in_object, + GType gtype, const GI::CallableInfo info) { std::string name; - info_type = g_base_info_get_type((GIBaseInfo *)info); - JS::RootedObject function(context, Gjs::Function::create(context, gtype, info)); if (!function) return NULL; - if (info_type == GI_INFO_TYPE_FUNCTION) { - name = g_base_info_get_name(info); - } else if (info_type == GI_INFO_TYPE_VFUNC) { - name = "vfunc_" + std::string(g_base_info_get_name(info)); + if (info.is_function()) { + name = info.name(); + } else if (info.is_vfunc()) { + name = "vfunc_" + std::string(info.name()); } else { g_assert_not_reached (); } @@ -1458,7 +1421,8 @@ gjs_define_function(JSContext *context, return function; } -bool gjs_invoke_constructor_from_c(JSContext* context, GIFunctionInfo* info, +bool gjs_invoke_constructor_from_c(JSContext* context, + const GI::FunctionInfo info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { diff --git a/gi/function.h b/gi/function.h index 38787b044..a9ff59f5e 100644 --- a/gi/function.h +++ b/gi/function.h @@ -14,8 +14,7 @@ #include #include -#include -#include // for g_callable_info_get_closure_native_address +#include #include #include @@ -23,9 +22,12 @@ #include #include #include +#include #include "gi/closure.h" -#include "cjs/jsapi-util.h" +#include "gi/info.h" +#include "cjs/auto.h" +#include "cjs/gerror-result.h" #include "cjs/macros.h" namespace JS { @@ -40,23 +42,15 @@ typedef enum { PARAM_UNKNOWN, } GjsParamType; -using GjsAutoGClosure = - GjsAutoPointer; - struct GjsCallbackTrampoline : public Gjs::Closure { - GJS_JSAPI_RETURN_CONVENTION static GjsCallbackTrampoline* create( - JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, - GIScopeType scope, bool has_scope_object, bool is_vfunc); + GJS_JSAPI_RETURN_CONVENTION + static GjsCallbackTrampoline* create(JSContext*, JS::HandleObject callable, + const GI::CallableInfo, GIScopeType, + bool has_scope_object, bool is_vfunc); ~GjsCallbackTrampoline(); - void* closure() const { -#if GI_CHECK_VERSION(1, 71, 0) - return g_callable_info_get_closure_native_address(m_info, m_closure); -#else - return m_closure; -#endif - } + void* closure() const { return m_info.closure_native_address(m_closure); } ffi_closure* get_ffi_closure() const { return m_closure; @@ -69,22 +63,23 @@ struct GjsCallbackTrampoline : public Gjs::Closure { private: ffi_closure* create_closure(); GJS_JSAPI_RETURN_CONVENTION bool initialize(); - GjsCallbackTrampoline(JSContext* cx, JS::HandleObject callable, - GICallableInfo* callable_info, GIScopeType scope, + GjsCallbackTrampoline(JSContext*, JS::HandleObject callable, + const GI::CallableInfo, GIScopeType, bool has_scope_object, bool is_vfunc); void callback_closure(GIArgument** args, void* result); GJS_JSAPI_RETURN_CONVENTION bool callback_closure_inner(JSContext* cx, JS::HandleObject this_object, GObject* gobject, JS::MutableHandleValue rval, - GIArgument** args, GITypeInfo* ret_type, - int n_args, int c_args_offset, void* result); + GIArgument** args, const GI::TypeInfo ret_type, + unsigned n_args, unsigned c_args_offset, + void* result); void warn_about_illegal_js_callback(const char* when, const char* reason, bool dump_stack); - static std::vector s_forever_closure_list; + static std::vector s_forever_closure_list; - GjsAutoCallableInfo m_info; + GI::AutoCallableInfo m_info; ffi_closure* m_closure = nullptr; std::unique_ptr m_param_types; ffi_cif m_cif; @@ -95,30 +90,30 @@ struct GjsCallbackTrampoline : public Gjs::Closure { // Stack allocation only! class GjsFunctionCallState { - GjsAutoCppPointer m_in_cvalues; - GjsAutoCppPointer m_out_cvalues; - GjsAutoCppPointer m_inout_original_cvalues; + Gjs::AutoCppPointer m_in_cvalues; + Gjs::AutoCppPointer m_out_cvalues; + Gjs::AutoCppPointer m_inout_original_cvalues; public: std::unordered_set ignore_release; JS::RootedObject instance_object; JS::RootedVector return_values; - GjsAutoError local_error; - GICallableInfo* info; + Gjs::AutoError local_error; + const GI::CallableInfo info; uint8_t gi_argc = 0; uint8_t processed_c_args = 0; bool failed : 1; bool can_throw_gerror : 1; bool is_method : 1; - GjsFunctionCallState(JSContext* cx, GICallableInfo* callable) + GjsFunctionCallState(JSContext* cx, const GI::CallableInfo callable) : instance_object(cx), return_values(cx), info(callable), - gi_argc(g_callable_info_get_n_args(callable)), + gi_argc(callable.n_args()), failed(false), - can_throw_gerror(g_callable_info_can_throw_gerror(callable)), - is_method(g_callable_info_is_method(callable)) { + can_throw_gerror(callable.can_throw_gerror()), + is_method(callable.is_method()) { int size = gi_argc + first_arg_offset(); m_in_cvalues = new GIArgument[size]; m_out_cvalues = new GIArgument[size]; @@ -159,28 +154,23 @@ class GjsFunctionCallState { return first_arg_offset() + processed_c_args; } - [[nodiscard]] GjsAutoChar display_name() { - GIBaseInfo* container = g_base_info_get_container(info); // !owned + [[nodiscard]] Gjs::AutoChar display_name() { + mozilla::Maybe container = info.container(); if (container) { - return g_strdup_printf( - "%s.%s.%s", g_base_info_get_namespace(container), - g_base_info_get_name(container), g_base_info_get_name(info)); + return g_strdup_printf("%s.%s.%s", container->ns(), + container->name(), info.name()); } - return g_strdup_printf("%s.%s", g_base_info_get_namespace(info), - g_base_info_get_name(info)); + return g_strdup_printf("%s.%s", info.ns(), info.name()); } }; GJS_JSAPI_RETURN_CONVENTION -JSObject *gjs_define_function(JSContext *context, - JS::HandleObject in_object, - GType gtype, - GICallableInfo *info); +JSObject* gjs_define_function(JSContext*, JS::HandleObject in_object, GType, + const GI::CallableInfo); GJS_JSAPI_RETURN_CONVENTION -bool gjs_invoke_constructor_from_c(JSContext* cx, GIFunctionInfo* info, +bool gjs_invoke_constructor_from_c(JSContext*, const GI::FunctionInfo, JS::HandleObject this_obj, - const JS::CallArgs& args, - GIArgument* rvalue); + const JS::CallArgs&, GIArgument* rvalue); #endif // GI_FUNCTION_H_ diff --git a/gi/fundamental.cpp b/gi/fundamental.cpp index 52d074ef3..1e198d7a2 100644 --- a/gi/fundamental.cpp +++ b/gi/fundamental.cpp @@ -5,7 +5,7 @@ #include -#include +#include #include #include @@ -24,6 +24,7 @@ #include "gi/arg.h" #include "gi/function.h" #include "gi/fundamental.h" +#include "gi/info.h" #include "gi/repo.h" #include "gi/value.h" #include "gi/wrapperutils.h" @@ -38,6 +39,8 @@ namespace JS { class CallArgs; } +using mozilla::Maybe, mozilla::Some; + FundamentalInstance::FundamentalInstance(FundamentalPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { @@ -70,24 +73,15 @@ bool FundamentalInstance::associate_js_instance(JSContext* cx, JSObject* object, /**/ /* Find the first constructor */ -[[nodiscard]] static GIFunctionInfo* find_fundamental_constructor( - GIObjectInfo* info) { - int i, n_methods; - - n_methods = g_object_info_get_n_methods(info); - - for (i = 0; i < n_methods; ++i) { - GjsAutoFunctionInfo func_info; - GIFunctionInfoFlags flags; - - func_info = g_object_info_get_method(info, i); - - flags = g_function_info_get_flags(func_info); - if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) - return func_info.release(); +[[nodiscard]] +static Maybe find_fundamental_constructor( + const GI::ObjectInfo info) { + for (GI::AutoFunctionInfo func_info : info.methods()) { + if (func_info.is_constructor()) + return Some(func_info); } - return nullptr; + return {}; } /**/ @@ -102,19 +96,17 @@ bool FundamentalPrototype::resolve_interface(JSContext* cx, ret = true; interfaces = g_type_interfaces(gtype(), &n_interfaces); + GI::Repository repo; for (i = 0; i < n_interfaces; i++) { - GjsAutoInterfaceInfo iface_info = - g_irepository_find_by_gtype(nullptr, interfaces[i]); - + Maybe iface_info{ + repo.find_by_gtype(interfaces[i])}; if (!iface_info) continue; - GjsAutoFunctionInfo method_info = - g_interface_info_find_method(iface_info, name); + Maybe method_info{iface_info->method(name)}; - if (method_info && - g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { - if (gjs_define_function(cx, obj, gtype(), method_info)) { + if (method_info && method_info->is_method()) { + if (gjs_define_function(cx, obj, gtype(), method_info.ref())) { *resolved = true; } else { ret = false; @@ -138,29 +130,26 @@ bool FundamentalPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, } /* We are the prototype, so look for methods and other class properties */ - GjsAutoFunctionInfo method_info = - g_object_info_find_method(info(), prop_name.get()); + Maybe method_info{info().method(prop_name.get())}; if (method_info) { -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(method_info); -#endif - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { + method_info->log_usage(); + if (method_info->is_method()) { /* we do not define deprecated methods in the prototype */ - if (g_base_info_is_deprecated(method_info)) { + if (method_info->is_deprecated()) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Ignoring definition of deprecated method %s in " - "prototype %s.%s", - method_info.name(), ns(), name()); + "prototype %s", + method_info->name(), format_name().c_str()); *resolved = false; return true; } gjs_debug(GJS_DEBUG_GFUNDAMENTAL, - "Defining method %s in prototype for %s.%s", - method_info.name(), ns(), name()); + "Defining method %s in prototype for %s", + method_info->name(), format_name().c_str()); - if (!gjs_define_function(cx, obj, gtype(), method_info)) + if (!gjs_define_function(cx, obj, gtype(), *method_info)) return false; *resolved = true; @@ -183,14 +172,15 @@ bool FundamentalInstance::invoke_constructor(JSContext* context, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { - GIFunctionInfo* constructor_info = get_prototype()->constructor_info(); + Maybe constructor_info = + get_prototype()->constructor_info(); if (!constructor_info) { - gjs_throw(context, "Couldn't find a constructor for type %s.%s", ns(), - name()); + gjs_throw(context, "Couldn't find a constructor for type %s", + format_name().c_str()); return false; } - return gjs_invoke_constructor_from_c(context, constructor_info, obj, args, + return gjs_invoke_constructor_from_c(context, *constructor_info, obj, args, rvalue); } @@ -199,18 +189,20 @@ bool FundamentalInstance::constructor_impl(JSContext* cx, JS::HandleObject object, const JS::CallArgs& argv) { GIArgument ret_value; - GITypeInfo return_info; if (!invoke_constructor(cx, object, argv, &ret_value) || !associate_js_instance(cx, object, gjs_arg_get(&ret_value))) return false; - GICallableInfo* constructor_info = get_prototype()->constructor_info(); - g_callable_info_load_return_type(constructor_info, &return_info); + Maybe constructor_info = + get_prototype()->constructor_info(); + g_assert(constructor_info); - return gjs_gi_argument_release( - cx, g_callable_info_get_caller_owns(constructor_info), &return_info, - &ret_value); + GI::StackTypeInfo return_info; + constructor_info->load_return_type(&return_info); + + return gjs_gi_argument_release(cx, constructor_info->caller_owns(), + return_info, &ret_value); } FundamentalInstance::~FundamentalInstance(void) { @@ -221,12 +213,13 @@ FundamentalInstance::~FundamentalInstance(void) { GJS_DEC_COUNTER(fundamental_instance); } -FundamentalPrototype::FundamentalPrototype(GIObjectInfo* info, GType gtype) +FundamentalPrototype::FundamentalPrototype(const GI::ObjectInfo info, + GType gtype) : GIWrapperPrototype(info, gtype), - m_ref_function(g_object_info_get_ref_function_pointer(info)), - m_unref_function(g_object_info_get_unref_function_pointer(info)), - m_get_value_function(g_object_info_get_get_value_function_pointer(info)), - m_set_value_function(g_object_info_get_set_value_function_pointer(info)), + m_ref_function(info.ref_function_pointer()), + m_unref_function(info.unref_function_pointer()), + m_get_value_function(info.get_value_function_pointer()), + m_set_value_function(info.set_value_function_pointer()), m_constructor_info(find_fundamental_constructor(info)) { GJS_INC_COUNTER(fundamental_prototype); } @@ -256,22 +249,14 @@ const struct JSClass FundamentalBase::klass = { }; // clang-format on +// FIXME: assume info is non-null on main? Is it possible to have hidden +// fundamental types? GJS_JSAPI_RETURN_CONVENTION -static JSObject * -gjs_lookup_fundamental_prototype(JSContext *context, - GIObjectInfo *info, - GType gtype) -{ - JS::RootedObject in_object(context); - const char *constructor_name; - - if (info) { - in_object = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); - constructor_name = g_base_info_get_name((GIBaseInfo*) info); - } else { - in_object = gjs_lookup_private_namespace(context); - constructor_name = g_type_name(gtype); - } +static JSObject* gjs_lookup_fundamental_prototype(JSContext* context, + const GI::ObjectInfo info) { + JS::RootedObject in_object{context, + gjs_lookup_namespace_object(context, info)}; + const char* constructor_name = info.name(); if (G_UNLIKELY (!in_object)) return nullptr; @@ -315,20 +300,19 @@ gjs_lookup_fundamental_prototype(JSContext *context, } GJS_JSAPI_RETURN_CONVENTION -static JSObject* -gjs_lookup_fundamental_prototype_from_gtype(JSContext *context, - GType gtype) -{ - GjsAutoObjectInfo info; +static JSObject* gjs_lookup_fundamental_prototype_from_gtype(JSContext* cx, + GType gtype) { + Maybe info; + GI::Repository repo; /* A given gtype might not have any definition in the introspection * data. If that's the case, try to look for a definition of any of the * parent type. */ while (gtype != G_TYPE_INVALID && - !(info = g_irepository_find_by_gtype(nullptr, gtype))) + !(info = repo.find_by_gtype(gtype))) gtype = g_type_parent(gtype); - return gjs_lookup_fundamental_prototype(context, info, gtype); + return gjs_lookup_fundamental_prototype(cx, info.ref()); } // Overrides GIWrapperPrototype::get_parent_proto(). @@ -347,7 +331,7 @@ bool FundamentalPrototype::get_parent_proto( // Overrides GIWrapperPrototype::constructor_nargs(). unsigned FundamentalPrototype::constructor_nargs(void) const { if (m_constructor_info) - return g_callable_info_get_n_args(m_constructor_info); + return m_constructor_info->n_args(); return 0; } @@ -363,23 +347,19 @@ unsigned FundamentalPrototype::constructor_nargs(void) const { */ bool FundamentalPrototype::define_class(JSContext* cx, JS::HandleObject in_object, - GIObjectInfo* info, + const GI::ObjectInfo info, JS::MutableHandleObject constructor) { - GType gtype; - - gtype = g_registered_type_info_get_g_type (info); - JS::RootedObject prototype(cx); FundamentalPrototype* priv = FundamentalPrototype::create_class( - cx, in_object, info, gtype, constructor, &prototype); + cx, in_object, info, info.gtype(), constructor, &prototype); if (!priv) return false; - if (g_object_info_get_n_fields(info) > 0) { + if (info.fields().size() > 0) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, - "Fundamental type '%s.%s' apparently has accessible fields. " - "Gjs has no support for this yet, ignoring these.", - priv->ns(), priv->name()); + "Fundamental type '%s' apparently has accessible fields. GJS " + "has no support for this yet, ignoring these.", + priv->format_name().c_str()); } return true; diff --git a/gi/fundamental.h b/gi/fundamental.h index 05ff1a952..69566850f 100644 --- a/gi/fundamental.h +++ b/gi/fundamental.h @@ -8,14 +8,15 @@ #include -#include +#include #include #include +#include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/wrapperutils.h" -#include "cjs/jsapi-util.h" // for GjsAutoCallableInfo #include "cjs/macros.h" #include "util/log.h" @@ -55,9 +56,11 @@ class FundamentalBase class FundamentalPrototype : public GIWrapperPrototype { + FundamentalInstance, GI::AutoObjectInfo, + GI::ObjectInfo> { friend class GIWrapperPrototype; + FundamentalInstance, GI::AutoObjectInfo, + GI::ObjectInfo>; friend class GIWrapperBase; @@ -65,20 +68,19 @@ class FundamentalPrototype GIObjectInfoUnrefFunction m_unref_function; GIObjectInfoGetValueFunction m_get_value_function; GIObjectInfoSetValueFunction m_set_value_function; - GjsAutoCallableInfo m_constructor_info; + mozilla::Maybe m_constructor_info; - explicit FundamentalPrototype(GIObjectInfo* info, GType gtype); + explicit FundamentalPrototype(const GI::ObjectInfo, GType); ~FundamentalPrototype(void); - static constexpr InfoType::Tag info_type_tag = InfoType::Object; - public: GJS_JSAPI_RETURN_CONVENTION static FundamentalPrototype* for_gtype(JSContext* cx, GType gtype); // Accessors - [[nodiscard]] GICallableInfo* constructor_info() const { + [[nodiscard]] + mozilla::Maybe constructor_info() const { return m_constructor_info; } @@ -131,7 +133,7 @@ class FundamentalPrototype public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, - GIObjectInfo* info, + const GI::ObjectInfo, JS::MutableHandleObject constructor); }; diff --git a/gi/gerror.cpp b/gi/gerror.cpp index 8dbf886b5..16de1912f 100644 --- a/gi/gerror.cpp +++ b/gi/gerror.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include #include @@ -25,23 +25,29 @@ #include #include // for InformalValueTypeName, JS_GetClassObject #include // for JSProtoKey, JSProto_Error, JSProt... +#include #include "gi/arg-inl.h" #include "gi/boxed.h" #include "gi/enumeration.h" #include "gi/gerror.h" +#include "gi/info.h" #include "gi/repo.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/error-types.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" -ErrorPrototype::ErrorPrototype(GIEnumInfo* info, GType gtype) +using mozilla::Maybe; + +ErrorPrototype::ErrorPrototype(const GI::EnumInfo info, GType gtype) : GIWrapperPrototype(info, gtype), - m_domain(g_quark_from_string(g_enum_info_get_error_domain(info))) { + m_domain(g_quark_from_string(info.error_domain())) { GJS_INC_COUNTER(gerror_prototype); } @@ -126,12 +132,12 @@ bool ErrorBase::get_code(JSContext* cx, unsigned argc, JS::Value* vp) { bool ErrorBase::to_string(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, self); - GjsAutoChar descr; + Gjs::AutoChar descr; // An error created via `new GLib.Error` will have a Boxed* private pointer, // not an Error*, so we can't call regular to_string() on it. - if (BoxedBase::typecheck(context, self, nullptr, G_TYPE_ERROR, - GjsTypecheckNoThrow())) { + if (BoxedBase::typecheck(context, self, G_TYPE_ERROR, + GjsTypecheckNoThrow{})) { auto* gerror = BoxedBase::to_c_ptr(context, self); if (!gerror) return false; @@ -150,9 +156,9 @@ bool ErrorBase::to_string(JSContext* context, unsigned argc, JS::Value* vp) { hiding some useful information */ if (priv->is_prototype()) { - descr = g_strdup_printf("%s.%s", priv->ns(), priv->name()); + descr = g_strdup(priv->format_name().c_str()); } else { - descr = g_strdup_printf("%s.%s: %s", priv->ns(), priv->name(), + descr = g_strdup_printf("%s: %s", priv->format_name().c_str(), priv->to_instance()->message()); } @@ -195,7 +201,8 @@ const struct JSClassOps ErrorBase::class_ops = { const struct JSClass ErrorBase::klass = { "GLib_Error", - JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, + JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE | + JSCLASS_IS_DOMJSCLASS, // needed for Error.isError() &ErrorBase::class_ops }; @@ -217,17 +224,19 @@ JSFunctionSpec ErrorBase::static_methods[] = { // Overrides GIWrapperPrototype::get_parent_proto(). bool ErrorPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const { - g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), - nullptr); - GjsAutoStructInfo glib_error_info = - g_irepository_find_by_name(nullptr, "GLib", "Error"); + GI::Repository repo{}; + repo.require("GLib", "2.0").unwrap(); + + GI::AutoBaseInfo glib_error_info{ + repo.find_by_name("GLib", "Error").value()}; + proto.set(gjs_lookup_generic_prototype(cx, glib_error_info)); return !!proto; } bool ErrorPrototype::define_class(JSContext* context, JS::HandleObject in_object, - GIEnumInfo* info) { + const GI::EnumInfo info) { JS::RootedObject prototype(context), constructor(context); if (!ErrorPrototype::create_class(context, in_object, info, G_TYPE_ERROR, &constructor, &prototype)) @@ -243,32 +252,26 @@ bool ErrorPrototype::define_class(JSContext* context, gjs_define_enum_values(context, constructor, info); } -[[nodiscard]] static GIEnumInfo* find_error_domain_info(GQuark domain) { - GIEnumInfo *info; - +[[nodiscard]] +static Maybe find_error_domain_info( + const GI::Repository& repo, GQuark domain) { /* first an attempt without loading extra libraries */ - info = g_irepository_find_by_error_domain(nullptr, domain); + Maybe info = repo.find_by_error_domain(domain); if (info) return info; /* load standard stuff */ - g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), - nullptr); - g_irepository_require(nullptr, "GObject", "2.0", GIRepositoryLoadFlags(0), - nullptr); - g_irepository_require(nullptr, "Gio", "2.0", GIRepositoryLoadFlags(0), - nullptr); - info = g_irepository_find_by_error_domain(nullptr, domain); + repo.require("GLib", "2.0").unwrap(); + repo.require("GObject", "2.0").unwrap(); + repo.require("Gio", "2.0").unwrap(); + info = repo.find_by_error_domain(domain); if (info) return info; /* last attempt: load GIRepository (for invoke errors, rarely needed) */ - g_irepository_require(nullptr, "GIRepository", "2.0", - GIRepositoryLoadFlags(0), nullptr); - info = g_irepository_find_by_error_domain(nullptr, domain); - - return info; + repo.require("GIRepository", "3.0").unwrap(); + return repo.find_by_error_domain(domain); } /* define properties that JS Error() expose, such as @@ -352,30 +355,28 @@ gjs_error_from_js_gerror(JSContext *cx, } JSObject* ErrorInstance::object_for_c_ptr(JSContext* context, GError* gerror) { - GIEnumInfo *info; - if (!gerror) return nullptr; if (gerror->domain == GJS_JS_ERROR) return gjs_error_from_js_gerror(context, gerror); - info = find_error_domain_info(gerror->domain); + GI::Repository repo; + Maybe info = find_error_domain_info(repo, gerror->domain); if (!info) { /* We don't have error domain metadata */ /* Marshal the error as a plain GError */ - GjsAutoBaseInfo glib_boxed = - g_irepository_find_by_name(nullptr, "GLib", "Error"); + GI::AutoStructInfo glib_boxed{ + repo.find_by_name("GLib", "Error").value()}; return BoxedInstance::new_for_c_struct(context, glib_boxed, gerror); } - gjs_debug_marshal(GJS_DEBUG_GBOXED, - "Wrapping struct %s with JSObject", - g_base_info_get_name((GIBaseInfo *)info)); + gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s with JSObject", + info->name()); - JS::RootedObject obj(context, - gjs_new_object_with_generic_prototype(context, info)); + JS::RootedObject obj{context, + gjs_new_object_with_generic_prototype(context, *info)}; if (!obj) return nullptr; @@ -389,8 +390,7 @@ GError* ErrorBase::to_c_ptr(JSContext* cx, JS::HandleObject obj) { /* If this is a plain GBoxed (i.e. a GError without metadata), delegate marshalling. */ - if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, - GjsTypecheckNoThrow())) + if (BoxedBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{})) return BoxedBase::to_c_ptr(cx, obj); return GIWrapperBase::to_c_ptr(cx, obj); @@ -404,7 +404,7 @@ bool ErrorBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, "transfer_to_gi_argument() must choose between in or out"); if (!ErrorBase::typecheck(cx, obj)) { - gjs_arg_unset(arg); + gjs_arg_unset(arg); return false; } @@ -427,17 +427,16 @@ bool ErrorBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, // Overrides GIWrapperBase::typecheck() bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj) { - if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, - GjsTypecheckNoThrow())) + if (BoxedBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{})) return true; - return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR); + return GIWrapperBase::typecheck(cx, obj, G_TYPE_ERROR); } bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow no_throw) { - if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw)) + if (BoxedBase::typecheck(cx, obj, G_TYPE_ERROR, no_throw)) return true; - return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw); + return GIWrapperBase::typecheck(cx, obj, G_TYPE_ERROR, no_throw); } GJS_JSAPI_RETURN_CONVENTION @@ -475,7 +474,7 @@ static GError* gerror_from_error_impl(JSContext* cx, JS::HandleObject obj) { if (!message) return nullptr; - GjsAutoTypeClass klass(GJS_TYPE_JS_ERROR); + Gjs::AutoTypeClass klass{GJS_TYPE_JS_ERROR}; const GEnumValue *value = g_enum_get_value_by_name(klass, name.get()); int code; if (value) @@ -527,13 +526,13 @@ GError* gjs_gerror_make_from_thrown_value(JSContext* cx) { /* * gjs_throw_gerror: * - * Converts a GError into a JavaScript exception, and frees the GError. + * Converts a GError into a JavaScript exception. * Differently from gjs_throw(), it will overwrite an existing exception, as it * is used to report errors from C functions. * * Returns: false, for convenience in returning from the calling function. */ -bool gjs_throw_gerror(JSContext* cx, GjsAutoError const& error) { +bool gjs_throw_gerror(JSContext* cx, Gjs::AutoError 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); diff --git a/gi/gerror.h b/gi/gerror.h index db3d73766..1265c42e8 100644 --- a/gi/gerror.h +++ b/gi/gerror.h @@ -7,7 +7,7 @@ #include -#include +#include #include #include @@ -15,8 +15,10 @@ #include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/wrapperutils.h" -#include "cjs/jsapi-util.h" // for GjsAutoPointer operators +#include "cjs/auto.h" // for Gjs::AutoPointer operators +#include "cjs/gerror-result.h" #include "cjs/macros.h" #include "util/log.h" @@ -53,7 +55,12 @@ class ErrorBase static constexpr const char* DEBUG_TAG = "gerror"; static const struct JSClassOps class_ops; + + public: + // public in order to implement Error.isError() static const struct JSClass klass; + + protected: static JSPropertySpec proto_properties[]; static JSFunctionSpec static_methods[]; @@ -98,17 +105,16 @@ class ErrorBase GjsTypecheckNoThrow); }; -class ErrorPrototype : public GIWrapperPrototype { +class ErrorPrototype + : public GIWrapperPrototype { friend class GIWrapperPrototype; + GI::AutoEnumInfo, GI::EnumInfo>; friend class GIWrapperBase; GQuark m_domain; - static constexpr InfoType::Tag info_type_tag = InfoType::Enum; - - explicit ErrorPrototype(GIEnumInfo* info, GType gtype); + explicit ErrorPrototype(const GI::EnumInfo, GType); ~ErrorPrototype(void); GJS_JSAPI_RETURN_CONVENTION @@ -119,7 +125,7 @@ class ErrorPrototype : public GIWrapperPrototypename); + Gjs::AutoChar underscore_name{gjs_hyphen_to_underscore(pspec->name)}; if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) { unsigned flags = GJS_MODULE_PROP_FLAGS | JSPROP_READONLY; - GjsAutoChar camel_name = gjs_hyphen_to_camel(pspec->name); + Gjs::AutoChar camel_name{gjs_hyphen_to_camel(pspec->name)}; if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) { JS::Rooted> jsprop(cx); @@ -201,7 +202,7 @@ static void gjs_object_set_gproperty(GObject* object, unsigned property_id [[maybe_unused]], const GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); - if (!priv) { + if (!priv || !priv->wrapper()) { g_warning("Wrapper for GObject %p was disposed, cannot set property %s", object, g_param_spec_get_name(pspec)); return; @@ -220,7 +221,7 @@ static void gjs_object_get_gproperty(GObject* object, unsigned property_id [[maybe_unused]], GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); - if (!priv) { + if (!priv || !priv->wrapper()) { g_warning("Wrapper for GObject %p was disposed, cannot get property %s", object, g_param_spec_get_name(pspec)); return; @@ -232,7 +233,7 @@ static void gjs_object_get_gproperty(GObject* object, JS::RootedValue jsvalue(cx); JSAutoRealm ar(cx, js_obj); - GjsAutoChar underscore_name = gjs_hyphen_to_underscore(pspec->name); + Gjs::AutoChar underscore_name{gjs_hyphen_to_underscore(pspec->name)}; if (!JS_GetProperty(cx, js_obj, underscore_name, &jsvalue)) { gjs_log_exception_uncaught(cx); return; @@ -254,7 +255,7 @@ static void gjs_object_class_init(void* class_pointer, void*) { return; unsigned i = 0; - for (GjsAutoParam& pspec : properties) { + for (Gjs::AutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_class_install_property(klass, ++i, pspec); @@ -294,7 +295,7 @@ static void gjs_interface_init(void* g_iface, void*) { if (!pop_class_init_properties(gtype, &properties)) return; - for (GjsAutoParam& pspec : properties) { + for (Gjs::AutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_interface_install_property(g_iface, pspec); diff --git a/gi/gobject.h b/gi/gobject.h index f336fd604..5cdd982a5 100644 --- a/gi/gobject.h +++ b/gi/gobject.h @@ -11,9 +11,9 @@ #include -#include "cjs/jsapi-util.h" +#include "cjs/auto.h" -using AutoParamArray = std::vector; +using AutoParamArray = std::vector; extern const GTypeInfo gjs_gobject_class_info; extern const GTypeInfo gjs_gobject_interface_info; diff --git a/gi/gtype.cpp b/gi/gtype.cpp index 1cc024217..c4b422e38 100644 --- a/gi/gtype.cpp +++ b/gi/gtype.cpp @@ -23,6 +23,7 @@ #include "gi/cwrapper.h" #include "gi/gtype.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" @@ -71,8 +72,8 @@ class GTypeObj : public CWrapper { if (gtype == 0) return false; - GjsAutoChar strval = - g_strdup_printf("[object GType for '%s']", g_type_name(gtype)); + Gjs::AutoChar strval{ + g_strdup_printf("[object GType for '%s']", g_type_name(gtype))}; return gjs_string_from_utf8(cx, strval, rec.rval()); } diff --git a/gi/info.h b/gi/info.h new file mode 100644 index 000000000..ac407346f --- /dev/null +++ b/gi/info.h @@ -0,0 +1,1810 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Philip Chimento + +#pragma once + +#include + +#include +#include + +#include // for nullptr_t +#include +#include // for pair, make_pair, move + +#if GJS_VERBOSE_ENABLE_GI_USAGE +# include +# include +#endif + +#include +#include +#include +#include +#include + +#include // for IgnoreGCPolicy +#include +#include +#include +#include + +#include "cjs/auto.h" +#include "cjs/gerror-result.h" +#include "util/log.h" + +// This file is a C++ wrapper for libgirepository that attempts to be more +// null-safe and type-safe. +// Each introspection info type has the methods of the C API's GIFooInfo, but +// indicates whether the return value is owned by the caller (GI::AutoFooInfo) +// or unowned (GI::FooInfo), and uses Maybe to indicate when it is nullable. +// There are also GI::StackArgInfo and GI::StackTypeInfo for use with the +// CallableInfo.load_arg(), CallableInfo.load_return_type(), and +// ArgInfo.load_type() methods, for performance. + +// COMPAT: We use Mozilla's Maybe, Result, and Span types because they are more +// complete than the C++ standard library types. +// std::optional does not have transform(), and_then(), etc., until C++23. +// std::expected does not appear until C++23. +// std::span does not appear until C++20. + +// Note, only the methods actually needed in GJS are wrapped here. So if one is +// missing, that's not for any particular reason unless noted otherwise; it just +// was never needed yet. + +using BoolResult = mozilla::Result; + +namespace GI { + +enum class InfoTag : unsigned { + ARG, + BASE, + CALLABLE, + CALLBACK, + CONSTANT, + ENUM, + FIELD, + FLAGS, + FUNCTION, + INTERFACE, + OBJECT, + PROPERTY, + REGISTERED_TYPE, + SIGNAL, + STRUCT, + TYPE, + UNION, + VALUE, + VFUNC, +}; + +namespace detail { +template +struct InfoTraits {}; +template <> +struct InfoTraits { + using CStruct = GIArgInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIBaseInfo; +}; +template <> +struct InfoTraits { + using CStruct = GICallableInfo; +}; +template <> +struct InfoTraits { + using CStruct = GICallbackInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIConstantInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIEnumInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIFieldInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIFlagsInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIFunctionInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIInterfaceInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIObjectInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIPropertyInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIRegisteredTypeInfo; +}; +template <> +struct InfoTraits { + using CStruct = GISignalInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIStructInfo; +}; +template <> +struct InfoTraits { + using CStruct = GITypeInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIUnionInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIValueInfo; +}; +template <> +struct InfoTraits { + using CStruct = GIVFuncInfo; +}; + +using GTypeFunc = GType (*)(); +static constexpr const GTypeFunc gtype_funcs[] = { + gi_arg_info_get_type, + gi_base_info_get_type, + gi_callable_info_get_type, + gi_callback_info_get_type, + gi_constant_info_get_type, + gi_enum_info_get_type, + gi_field_info_get_type, + gi_flags_info_get_type, + gi_function_info_get_type, + gi_interface_info_get_type, + gi_object_info_get_type, + gi_property_info_get_type, + gi_registered_type_info_get_type, + gi_signal_info_get_type, + gi_struct_info_get_type, + gi_type_info_get_type, + gi_union_info_get_type, + gi_value_info_get_type, + gi_vfunc_info_get_type, +}; + +constexpr GTypeFunc gtype_func(InfoTag tag) { return gtype_funcs[size_t(tag)]; } + +} // namespace detail + +template +class InfoOperations {}; + +class StackArgInfo; +class StackTypeInfo; + +template +class OwnedInfo; + +template +class UnownedInfo; + +namespace detail { +// We want the underlying pointer to be inaccessible. However, the three storage +// classes sometimes have to interact with each others' pointers. It's easier to +// put all of those operations into detail::Pointer and have the classes be +// friends of it, than it is to expose all the pointer operations via friend +// declarations individually. +struct Pointer { + template + using CStruct = typename InfoTraits::CStruct; + + template + [[nodiscard]] + static constexpr + typename detail::InfoTraits::CStruct* cast(GIBaseInfo* ptr) { + // (the following is a GI_TAG_INFO() cast but written out) + return reinterpret_cast::CStruct*>( + g_type_check_instance_cast(reinterpret_cast(ptr), + gtype_func(TAG)())); + } + + template + static constexpr CStruct* get_from(const OwnedInfo& owned) { + return const_cast*>(owned.m_info); + } + + template + static constexpr CStruct* get_from(const UnownedInfo& unowned) { + return const_cast*>(unowned.m_info); + } + + // Defined out-of-line because they are not templates and so StackArgInfo + // and StackTypeInfo need to be complete types. + static constexpr GIArgInfo* get_from(const StackArgInfo& stack); + static constexpr GITypeInfo* get_from(const StackTypeInfo& stack); + + template + static constexpr OwnedInfo to_owned(CStruct* ptr) { + return OwnedInfo{ptr}; + } + + template + static constexpr UnownedInfo to_unowned(CStruct* ptr) { + return UnownedInfo{ptr}; + } + + // Same, defined out of line so StackTypeInfo is not incomplete. + static void to_stack(GITypeInfo* ptr, StackTypeInfo* stack); + + template + static constexpr mozilla::Maybe> nullable( + CStruct* ptr) { + return ptr ? mozilla::Some(OwnedInfo{ptr}) : mozilla::Nothing{}; + } + + template + static constexpr mozilla::Maybe> nullable_unowned( + CStruct* ptr) { + return ptr ? mozilla::Some(UnownedInfo{ptr}) : mozilla::Nothing{}; + } + + template + [[nodiscard]] + static constexpr bool typecheck(GIBaseInfo* ptr) { + return G_TYPE_CHECK_INSTANCE_TYPE(ptr, gtype_func(TAG)()); + } +}; +} // namespace detail + +///// UNOWNED INTROSPECTION INFO /////////////////////////////////////////////// + +template +class UnownedInfo : public InfoOperations, TAG> { + friend struct detail::Pointer; + + using CStruct = typename detail::InfoTraits::CStruct; + CStruct* m_info; + UnownedInfo() = delete; + explicit UnownedInfo(std::nullptr_t) = delete; + // No need to delete move constructor; declaring a copy constructor prevents + // it from being generated. + + explicit UnownedInfo(CStruct* info) : m_info(info) { validate(); } + [[nodiscard]] CStruct* ptr() const { return m_info; } + + void validate() const { + static_assert(sizeof(CStruct*) == sizeof(UnownedInfo), + "UnownedInfo should be byte-compatible with T*"); + +#ifndef G_DISABLE_CAST_CHECKS + g_assert(m_info && "Info pointer cannot be null"); + g_assert(detail::Pointer::typecheck(GI_BASE_INFO(m_info)) && + "Info type must match"); +#endif // G_DISABLE_CAST_CHECKS + } + + public: + // Copying is cheap, UnownedInfo just consists of a pointer. + constexpr UnownedInfo(const UnownedInfo& other) : m_info(other.m_info) {} + UnownedInfo& operator=(const UnownedInfo& other) { + m_info = other.m_info; + return *this; + } + + // Caller must take care that the lifetime of UnownedInfo does not exceed + // the lifetime of the StackInfo. Do not store the UnownedInfo, or try to + // take ownership. + UnownedInfo(const StackArgInfo& other) // NOLINT(runtime/explicit) + : UnownedInfo(detail::Pointer::get_from(other)) { + static_assert(TAG == InfoTag::ARG); + } + UnownedInfo(const StackTypeInfo& other) // NOLINT(runtime/explicit) + : UnownedInfo(detail::Pointer::get_from(other)) { + static_assert(TAG == InfoTag::TYPE); + } + + // Caller must take care that the lifetime of UnownedInfo does not exceed + // the lifetime of the originating OwnedInfo. That means, if you store it, + // only store it as an OwnedInfo, adding another reference. + UnownedInfo(const OwnedInfo& other) // NOLINT(runtime/explicit) + : UnownedInfo(detail::Pointer::get_from(other)) {} +}; + +using ArgInfo = UnownedInfo; +using BaseInfo = UnownedInfo; +using CallableInfo = UnownedInfo; +using CallbackInfo = UnownedInfo; +using ConstantInfo = UnownedInfo; +using EnumInfo = UnownedInfo; +using FieldInfo = UnownedInfo; +using FlagsInfo = UnownedInfo; +using FunctionInfo = UnownedInfo; +using InterfaceInfo = UnownedInfo; +using ObjectInfo = UnownedInfo; +using RegisteredTypeInfo = UnownedInfo; +using StructInfo = UnownedInfo; +using TypeInfo = UnownedInfo; +using UnionInfo = UnownedInfo; +using ValueInfo = UnownedInfo; +using VFuncInfo = UnownedInfo; + +///// OWNED INTROSPECTION INFO ///////////////////////////////////////////////// + +template +class OwnedInfo : public InfoOperations, TAG> { + friend struct detail::Pointer; + + using CStruct = typename detail::InfoTraits::CStruct; + CStruct* m_info; + + OwnedInfo() = delete; + explicit OwnedInfo(std::nullptr_t) = delete; + explicit OwnedInfo(CStruct* info) : m_info(info) { + static_assert(sizeof(CStruct*) == sizeof(OwnedInfo), + "OwnedInfo should be byte-compatible with T*"); +#ifndef G_DISABLE_CAST_CHECKS + g_assert(m_info && "Info pointer cannot be null"); + g_assert(detail::Pointer::typecheck(GI_BASE_INFO(m_info)) && + "Info type must match"); +#endif // G_DISABLE_CAST_CHECKS + } + + [[nodiscard]] CStruct* ptr() const { return m_info; } + + public: + // Copy OwnedInfo from another OwnedInfo. Explicit because it takes a + // reference. + explicit OwnedInfo(const OwnedInfo& other) : OwnedInfo(other.m_info) { + gi_base_info_ref(m_info); + } + // Move another OwnedInfo into this one + OwnedInfo(OwnedInfo&& other) : OwnedInfo(other.m_info) { + other.m_info = nullptr; + } + OwnedInfo& operator=(const OwnedInfo& other) { + m_info = other.m_info; + gi_base_info_ref(m_info); + return *this; + } + OwnedInfo& operator=(OwnedInfo&& other) { + std::swap(m_info, other.m_info); + return *this; + } + ~OwnedInfo() { g_clear_pointer(&m_info, gi_base_info_unref); } + + // Copy OwnedInfo from UnownedInfo, which also comes down to just taking a + // reference. Explicit because it takes a reference. However, make sure the + // UnownedInfo is not borrowed from a StackInfo! + explicit OwnedInfo(const UnownedInfo& other) + : OwnedInfo(detail::Pointer::get_from(other)) { + gi_base_info_ref(m_info); + } + + // Do not try to take ownership of a StackInfo. + // (cpplint false positive: https://github.com/cpplint/cpplint/issues/386) + OwnedInfo(const StackArgInfo& other) = delete; // NOLINT(runtime/explicit) + OwnedInfo(const StackTypeInfo& other) = delete; // NOLINT(runtime/explicit) +}; + +using AutoArgInfo = OwnedInfo; +using AutoBaseInfo = OwnedInfo; +using AutoCallableInfo = OwnedInfo; +using AutoCallbackInfo = OwnedInfo; +using AutoEnumInfo = OwnedInfo; +using AutoFieldInfo = OwnedInfo; +using AutoFunctionInfo = OwnedInfo; +using AutoInterfaceInfo = OwnedInfo; +using AutoObjectInfo = OwnedInfo; +using AutoPropertyInfo = OwnedInfo; +using AutoRegisteredTypeInfo = OwnedInfo; +using AutoSignalInfo = OwnedInfo; +using AutoStructInfo = OwnedInfo; +using AutoTypeInfo = OwnedInfo; +using AutoUnionInfo = OwnedInfo; +using AutoValueInfo = OwnedInfo; +using AutoVFuncInfo = OwnedInfo; + +// The various specializations of InfoOperations are used to ensure that the +// OwnedInfo and UnownedInfo specializations for a particular GIFooInfo type +// (and the stack-allocated class, if applicable) have the same methods. So, for +// example, AutoTypeInfo, TypeInfo, and StackTypeInfo all inherit from +// InfoOperations. + +template +class InfoOperations { + protected: + [[nodiscard]] + GIBaseInfo* ptr() const { + return GI_BASE_INFO( + detail::Pointer::get_from(*static_cast(this))); + } + + // Helper for adapting GLib-style error reporting into GErrorResult + [[nodiscard]] + static Gjs::GErrorResult<> bool_gerror(bool ok, GError* error) { + if (!ok) + return mozilla::Err(error); + return mozilla::Ok{}; + } + + // Helper for adapting C-style success/failure result into mozilla::Result. + // Used when there is no GError out parameter. + [[nodiscard]] + static BoolResult bool_to_result(bool ok) { + if (!ok) + return Err(mozilla::Nothing{}); + return mozilla::Ok{}; + } + + public: + template + bool operator==(const OwnedInfo& other) const { + return gi_base_info_equal( + ptr(), GI_BASE_INFO(detail::Pointer::get_from(other))); + } + template + bool operator==(const UnownedInfo& other) const { + return gi_base_info_equal( + ptr(), GI_BASE_INFO(detail::Pointer::get_from(other))); + } + template + bool operator!=(const OwnedInfo& other) const { + return !(*this == other); + } + template + bool operator!=(const UnownedInfo& other) const { + return !(*this == other); + } + + template + [[nodiscard]] + mozilla::Maybe> container() const { + return detail::Pointer::nullable_unowned( + detail::Pointer::cast(gi_base_info_get_container(ptr()))); + } + [[nodiscard]] + bool is_deprecated() const { + return gi_base_info_is_deprecated(ptr()); + } + [[nodiscard]] + const char* name() const { + return gi_base_info_get_name(ptr()); + } + [[nodiscard]] + const char* ns() const { + return gi_base_info_get_namespace(ptr()); + } + [[nodiscard]] + const char* type_string() const { + return g_type_name_from_instance( + reinterpret_cast(ptr())); + } + + // Type-checking methods + + [[nodiscard]] + bool is_callback() const { + return GI_IS_CALLBACK_INFO(ptr()); + } + [[nodiscard]] + bool is_enum_or_flags() const { + return GI_IS_ENUM_INFO(ptr()); + } + [[nodiscard]] bool is_flags() const { return GI_IS_FLAGS_INFO(ptr()); } + [[nodiscard]] + bool is_function() const { + return GI_IS_FUNCTION_INFO(ptr()); + } + [[nodiscard]] + bool is_interface() const { + return GI_IS_INTERFACE_INFO(ptr()); + } + [[nodiscard]] bool is_object() const { return GI_IS_OBJECT_INFO(ptr()); } + [[nodiscard]] + bool is_registered_type() const { + return GI_IS_REGISTERED_TYPE_INFO(ptr()); + } + [[nodiscard]] bool is_struct() const { return GI_IS_STRUCT_INFO(ptr()); } + [[nodiscard]] bool is_union() const { return GI_IS_UNION_INFO(ptr()); } + [[nodiscard]] + bool is_unresolved() const { + // We don't have a wrapper for GIUnresolvedInfo because it has no + // methods, but you can check whether a BaseInfo is one. + return GI_IS_UNRESOLVED_INFO(ptr()); + } + [[nodiscard]] bool is_vfunc() const { return GI_IS_VFUNC_INFO(ptr()); } + // Don't enumerate types which GJS doesn't define on namespaces. + // See gjs_define_info(). + [[nodiscard]] + bool is_enumerable() const { + return GI_IS_REGISTERED_TYPE_INFO(ptr()) || + GI_IS_FUNCTION_INFO(ptr()) || GI_IS_CONSTANT_INFO(ptr()); + } + + // Having this casting function be a template is slightly inconsistent with + // all the is_X() type-checking methods above. But if we were to make + // separate as_X() methods, C++ can't easily deal with all the forward decls + // of UnownedInfo instantiating the template. + template + [[nodiscard]] + mozilla::Maybe> as() const { + if (!detail::Pointer::typecheck(ptr())) + return {}; + auto* checked_ptr = detail::Pointer::cast(ptr()); + return mozilla::Some(detail::Pointer::to_unowned(checked_ptr)); + } + + void log_usage() const { +#if GJS_VERBOSE_ENABLE_GI_USAGE + mozilla::Maybe parent = container(); + gjs_debug_gi_usage( + "{ GIInfoType %s, \"%s\", \"%s\", \"%s\" }", type_string(), ns(), + parent.map(std::mem_fn(&GI::BaseInfo::name)).valueOr(""), name()); +#endif // GJS_VERBOSE_ENABLE_GI_USAGE + } +}; + +template +using BaseInfoOperations = InfoOperations; + +// The following InfoIterator class is a C++ iterator implementation that's used +// to implement the C iteration pattern: +// +// unsigned n_bars = gi_foo_info_get_n_bars(info); +// for (unsigned ix = 0; ix < n_bars; ix++) { +// GIBarInfo* bar = gi_foo_info_get_bar(info, ix); +// do_stuff(bar); +// gi_base_info_unref(bar); +// } +// +// as a more idiomatic C++ pattern: +// +// for (AutoBarInfo bar : info.bars()) +// do_stuff(bar); + +template +using NInfosFunc = unsigned (*)(T); + +template +using GetInfoFunc = typename detail::InfoTraits::CStruct* (*)(T, unsigned); + +template get_n_infos, + GetInfoFunc get_info> +class InfoIterator { + T m_obj; + int m_ix; + + InfoIterator(T obj, int ix) : m_obj(obj), m_ix(ix) {} + + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = int; + using value_type = OwnedInfo; + using pointer = value_type*; + using reference = value_type&; + + explicit InfoIterator(T info) : InfoIterator(info, 0) {} + + OwnedInfo operator*() const { + return detail::Pointer::to_owned(get_info(m_obj, m_ix)); + } + InfoIterator& operator++() { + m_ix++; + return *this; + } + InfoIterator operator++(int) { + InfoIterator tmp = *this; + m_ix++; + return tmp; + } + bool operator==(const InfoIterator& other) const { + return m_obj == other.m_obj && m_ix == other.m_ix; + } + bool operator!=(const InfoIterator& other) const { + return m_obj != other.m_obj || m_ix != other.m_ix; + } + + [[nodiscard]] + mozilla::Maybe> operator[](size_t ix) const { + return detail::Pointer::nullable(get_info(m_obj, ix)); + } + + [[nodiscard]] InfoIterator begin() const { return InfoIterator{m_obj, 0}; } + [[nodiscard]] + InfoIterator end() const { + int n_fields = get_n_infos(m_obj); + return InfoIterator{m_obj, n_fields}; + } + [[nodiscard]] size_t size() const { return get_n_infos(m_obj); } +}; + +// These are used to delete the type-checking and casting methods from +// InfoOperations specializations for subtypes of GIBaseInfo, as appropriate. +// So, for example, if you have AutoCallableInfo, you still want to be able to +// check is_callback, is_function, and is_vfunc, but not is_boxed etc. + +#define DELETE_CALLABLE_TYPECHECK_METHODS \ + bool is_callback() const = delete; \ + bool is_function() const = delete; \ + bool is_vfunc() const = delete; + +#define DELETE_REGISTERED_TYPE_TYPECHECK_METHODS \ + bool is_boxed() const = delete; \ + bool is_enum_or_flags() const = delete; \ + bool is_flags() const = delete; \ + bool is_interface() const = delete; \ + bool is_object() const = delete; \ + bool is_struct() const = delete; \ + bool is_union() const = delete; + +#define DELETE_SUPERCLASS_TYPECHECK_METHODS \ + bool is_registered_type() const = delete; \ + bool is_unresolved() const = delete; + +#define DELETE_CAST_METHOD \ + template \ + mozilla::Maybe> as() const = delete; + +#define DELETE_ALL_TYPECHECK_METHODS \ + DELETE_SUPERCLASS_TYPECHECK_METHODS \ + DELETE_CALLABLE_TYPECHECK_METHODS \ + DELETE_REGISTERED_TYPE_TYPECHECK_METHODS \ + DELETE_CAST_METHOD + +// Needs to come first, because InfoOperations and InfoOperations +// instantiate the template by having methods with GI::StackTypeInfo* parameters +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GITypeInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + // Private, because we don't use this directly. Use the more semantic + // versions below (element_type() for GSLIST, GLIST, and ARRAY type tags; + // key_type() and value_type() for GHASH.) + [[nodiscard]] + AutoTypeInfo param_type(int n) const { + return detail::Pointer::to_owned( + gi_type_info_get_param_type(ptr(), n)); + } + + public: + [[nodiscard]] + mozilla::Maybe array_length_index() const { + unsigned out; + if (!gi_type_info_get_array_length_index(ptr(), &out)) + return {}; + return mozilla::Some(out); + } + [[nodiscard]] + mozilla::Maybe array_fixed_size() const { + size_t out; + if (!gi_type_info_get_array_fixed_size(ptr(), &out)) + return {}; + return mozilla::Some(out); + } + [[nodiscard]] + GIArrayType array_type() const { + return gi_type_info_get_array_type(ptr()); + } + void argument_from_hash_pointer(void* hash_pointer, GIArgument* arg) const { + gi_type_info_argument_from_hash_pointer(ptr(), hash_pointer, arg); + } + [[nodiscard]] + void* hash_pointer_from_argument(GIArgument* arg) const { + return gi_type_info_hash_pointer_from_argument(ptr(), arg); + } + // Unlike the libgirepository API, this doesn't return null. Only call it on + // TypeInfo with GI_TYPE_TAG_INTERFACE tag. + [[nodiscard]] + AutoBaseInfo interface() const { + g_assert(tag() == GI_TYPE_TAG_INTERFACE); + return detail::Pointer::to_owned( + gi_type_info_get_interface(ptr())); + } + [[nodiscard]] + bool is_pointer() const { + return gi_type_info_is_pointer(ptr()); + } + [[nodiscard]] + bool is_zero_terminated() const { + return gi_type_info_is_zero_terminated(ptr()); + } + [[nodiscard]] + GITypeTag storage_type() const { + return gi_type_info_get_storage_type(ptr()); + } + [[nodiscard]] GITypeTag tag() const { return gi_type_info_get_tag(ptr()); } + void extract_ffi_return_value(GIFFIReturnValue* ffi_value, + GIArgument* arg) const { + gi_type_info_extract_ffi_return_value(ptr(), ffi_value, arg); + } + + // Methods not present in GIRepository + + [[nodiscard]] bool can_be_allocated_directly() const; + [[nodiscard]] bool direct_allocation_has_pointers() const; + [[nodiscard]] + const char* display_string() const { + GITypeTag type_tag = tag(); + if (type_tag == GI_TYPE_TAG_INTERFACE) + return interface().type_string(); + return gi_type_tag_to_string(type_tag); + } + + [[nodiscard]] + bool is_string_type() const { + GITypeTag t = tag(); + return t == GI_TYPE_TAG_FILENAME || t == GI_TYPE_TAG_UTF8; + } + + [[nodiscard]] + bool is_basic() const { + GITypeTag t = tag(); + if (t == GI_TYPE_TAG_VOID && is_pointer()) + return false; // void* is not a basic type + return GI_TYPE_TAG_IS_BASIC(t); + } + + // More semantic versions of param_type(), that are only intended to be + // called on TypeInfos where the result is known not to be null + + [[nodiscard]] + AutoTypeInfo element_type() const { + g_assert(tag() == GI_TYPE_TAG_ARRAY || tag() == GI_TYPE_TAG_GLIST || + tag() == GI_TYPE_TAG_GSLIST); + return param_type(0); + } + + [[nodiscard]] + AutoTypeInfo key_type() const { + g_assert(tag() == GI_TYPE_TAG_GHASH); + return param_type(0); + } + + [[nodiscard]] + AutoTypeInfo value_type() const { + g_assert(tag() == GI_TYPE_TAG_GHASH); + return param_type(1); + } +}; + +// Needs to come after InfoOperations but before InfoOperations +// since this class instantiates the GI::StackTypeInfo template, but +// InfoOperations instantiates this one. +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIArgInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + [[nodiscard]] + bool caller_allocates() const { + return gi_arg_info_is_caller_allocates(ptr()); + } + [[nodiscard]] + mozilla::Maybe closure_index() const { + unsigned out; + if (!gi_arg_info_get_closure_index(ptr(), &out)) + return {}; + return mozilla::Some(out); + } + [[nodiscard]] + mozilla::Maybe destroy_index() const { + unsigned out; + if (!gi_arg_info_get_destroy_index(ptr(), &out)) + return {}; + return mozilla::Some(out); + } + [[nodiscard]] + GIDirection direction() const { + return gi_arg_info_get_direction(ptr()); + } + void load_type(StackTypeInfo* type) const { + gi_arg_info_load_type_info(ptr(), detail::Pointer::get_from(*type)); + } + [[nodiscard]] + bool is_optional() const { + return gi_arg_info_is_optional(ptr()); + } + [[nodiscard]] + bool is_return_value() const { + return gi_arg_info_is_return_value(ptr()); + } + [[nodiscard]] + bool may_be_null() const { + return gi_arg_info_may_be_null(ptr()); + } + [[nodiscard]] + GITransfer ownership_transfer() const { + return gi_arg_info_get_ownership_transfer(ptr()); + } + [[nodiscard]] + GIScopeType scope() const { + return gi_arg_info_get_scope(ptr()); + } +}; + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_SUPERCLASS_TYPECHECK_METHODS; + DELETE_REGISTERED_TYPE_TYPECHECK_METHODS; + + [[nodiscard]] + GICallableInfo* ptr() const { + return GI_CALLABLE_INFO( + detail::Pointer::get_from(*static_cast(this))); + } + + public: + using ArgsIterator = + InfoIterator; + [[nodiscard]] + ArgsIterator args() const { + return ArgsIterator{ptr()}; + } + [[nodiscard]] + AutoArgInfo arg(unsigned n) const { + g_assert(n < n_args()); + return detail::Pointer::to_owned( + gi_callable_info_get_arg(ptr(), n)); + } + [[nodiscard]] + unsigned n_args() const { + return gi_callable_info_get_n_args(ptr()); + } + + [[nodiscard]] + GITransfer caller_owns() const { + return gi_callable_info_get_caller_owns(ptr()); + } + [[nodiscard]] + bool can_throw_gerror() const { + return gi_callable_info_can_throw_gerror(ptr()); + } + [[nodiscard]] + void** closure_native_address(ffi_closure* closure) const { + return gi_callable_info_get_closure_native_address(ptr(), closure); + } + [[nodiscard]] + ffi_closure* create_closure(ffi_cif* cif, GIFFIClosureCallback callback, + void* user_data) const { + return gi_callable_info_create_closure(ptr(), cif, callback, user_data); + } + void destroy_closure(ffi_closure* closure) const { + gi_callable_info_destroy_closure(ptr(), closure); + } + [[nodiscard]] + Gjs::GErrorResult<> init_function_invoker( + void* address, GIFunctionInvoker* invoker) const { + GError* error = nullptr; + return this->bool_gerror(gi_function_invoker_new_for_address( + address, ptr(), invoker, &error), + error); + } + [[nodiscard]] + GITransfer instance_ownership_transfer() const { + return gi_callable_info_get_instance_ownership_transfer(ptr()); + } + [[nodiscard]] + bool is_method() const { + return gi_callable_info_is_method(ptr()); + } + void load_arg(unsigned n, StackArgInfo* arg) const { + g_assert(n < n_args()); + gi_callable_info_load_arg(ptr(), n, detail::Pointer::get_from(*arg)); + } + void load_return_type(StackTypeInfo* type) const { + gi_callable_info_load_return_type(ptr(), + detail::Pointer::get_from(*type)); + } + [[nodiscard]] + bool may_return_null() const { + return gi_callable_info_may_return_null(ptr()); + } + [[nodiscard]] + bool skip_return() const { + return gi_callable_info_skip_return(ptr()); + } + + // Methods not in GIRepository + + void log_usage() { +#if GJS_VERBOSE_ENABLE_GI_USAGE + std::ostringstream out; + +# define DIRECTION_STRING(d) \ + (((d) == GI_DIRECTION_IN) ? "IN" \ + : ((d) == GI_DIRECTION_OUT) ? "OUT" \ + : "INOUT") +# define TRANSFER_STRING(t) \ + (((t) == GI_TRANSFER_NOTHING) ? "NOTHING" \ + : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" \ + : "EVERYTHING") + + out << ".details = { .func = { .retval_transfer = GI_TRANSFER_" + << TRANSFER_STRING(caller_owns()) << ", .n_args = " << n_args() + << ", .args = { "; + + ArgsIterator iter = args(); + std::for_each(iter.begin(), iter.end(), [&out](AutoArgInfo arg_info) { + out << "{ GI_DIRECTION_" << DIRECTION_STRING(arg_info.direction()) + << ", GI_TRANSFER_" + << TRANSFER_STRING(arg_info.ownership_transfer()) << " }, "; + }); + out.seekp(-2, std::ios_base::end); // Erase trailing comma + +# undef DIRECTION_STRING +# undef TRANSFER_STRING + + out << " } } }"; + std::string details{out.str()}; + + using Base = BaseInfoOperations; + mozilla::Maybe parent = Base::container(); + gjs_debug_gi_usage( + "{ GIInfoType %s, \"%s\", \"%s\", \"%s\", %s }", + Base::type_string(), Base::ns(), + parent.map(std::mem_fn(&GI::BaseInfo::name)).valueOr(""), + Base::name(), details.c_str()); +#endif // GJS_VERBOSE_ENABLE_GI_USAGE + } +}; + +template +using CallableInfoOperations = InfoOperations; + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_SUPERCLASS_TYPECHECK_METHODS; + DELETE_CALLABLE_TYPECHECK_METHODS; + + [[nodiscard]] + GIRegisteredTypeInfo* ptr() const { + return GI_REGISTERED_TYPE_INFO( + detail::Pointer::get_from(*static_cast(this))); + } + + public: + [[nodiscard]] + GType gtype() const { + return gi_registered_type_info_get_g_type(ptr()); + } + + // Methods not in GIRepository + + [[nodiscard]] + bool is_gdk_atom() const { + return strcmp("Atom", this->name()) == 0 && + strcmp("Gdk", this->ns()) == 0; + } + [[nodiscard]] + bool is_g_value() const { + return g_type_is_a(gtype(), G_TYPE_VALUE); + } + + operator const BaseInfo() const { + return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); + } +}; + +template +using RegisteredTypeInfoOperations = + InfoOperations; + +template +class InfoOperations + : public CallableInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GICallbackInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + operator const BaseInfo() const { + return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); + } + + operator const CallableInfo() const { + return detail::Pointer::to_unowned( + GI_CALLABLE_INFO(ptr())); + } +}; + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIConstantInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + void free_value(GIArgument* arg) const { + gi_constant_info_free_value(ptr(), arg); + } + int load_value(GIArgument* arg) const { + return gi_constant_info_get_value(ptr(), arg); + } + [[nodiscard]] + AutoTypeInfo type_info() const { + return detail::Pointer::to_owned( + gi_constant_info_get_type_info(ptr())); + } +}; + +// Must come before any use of MethodsIterator +template +class InfoOperations + : public CallableInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIFunctionInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + [[nodiscard]] + GIFunctionInfoFlags flags() const { + return gi_function_info_get_flags(ptr()); + } + + public: + [[nodiscard]] + Gjs::GErrorResult<> invoke(const mozilla::Span& in_args, + const mozilla::Span& out_args, + GIArgument* return_value) const { + g_assert(in_args.size() <= G_MAXINT); + g_assert(out_args.size() <= G_MAXINT); + GError* error = nullptr; + return this->bool_gerror( + gi_function_info_invoke(ptr(), in_args.data(), in_args.size(), + out_args.data(), out_args.size(), + return_value, &error), + error); + } + [[nodiscard]] + Gjs::GErrorResult<> prep_invoker(GIFunctionInvoker* invoker) const { + GError* error = nullptr; + return this->bool_gerror( + gi_function_info_prep_invoker(ptr(), invoker, &error), error); + } + [[nodiscard]] + const char* symbol() const { + return gi_function_info_get_symbol(ptr()); + } + + // Has to be defined later because there's a chicken-and-egg loop between + // AutoPropertyInfo and AutoFunctionInfo + [[nodiscard]] + mozilla::Maybe property() const; + + // Methods not in GIRepository + + [[nodiscard]] + bool is_method() const { + return flags() & GI_FUNCTION_IS_METHOD; + } + [[nodiscard]] + bool is_constructor() const { + return flags() & GI_FUNCTION_IS_CONSTRUCTOR; + } + + operator const CallableInfo() const { + return detail::Pointer::to_unowned( + GI_CALLABLE_INFO(ptr())); + } +}; + +template +class InfoOperations + : public RegisteredTypeInfoOperations { + DELETE_REGISTERED_TYPE_TYPECHECK_METHODS; + + [[nodiscard]] + GIEnumInfo* ptr() const { + return GI_ENUM_INFO( + detail::Pointer::get_from(*static_cast(this))); + } + + public: + using ValuesIterator = + InfoIterator; + [[nodiscard]] + ValuesIterator values() const { + return ValuesIterator{ptr()}; + } + + using MethodsIterator = + InfoIterator; + [[nodiscard]] + MethodsIterator methods() const { + return MethodsIterator{ptr()}; + } + [[nodiscard]] + mozilla::Maybe method(const char* name) const { + return detail::Pointer::nullable( + gi_enum_info_find_method(ptr(), name)); + } + + [[nodiscard]] + const char* error_domain() const { + return gi_enum_info_get_error_domain(ptr()); + } + [[nodiscard]] + GITypeTag storage_type() const { + return gi_enum_info_get_storage_type(ptr()); + } + + // Methods not in GIRepository + + [[nodiscard]] + bool uses_signed_type() const { + switch (storage_type()) { + case GI_TYPE_TAG_INT8: + case GI_TYPE_TAG_INT16: + case GI_TYPE_TAG_INT32: + case GI_TYPE_TAG_INT64: + return true; + default: + return false; + } + } + + // This is hacky - gi_function_info_invoke() and + // gi_field_info_get/set_field() expect the enum value in + // gjs_arg_member(arg) and depend on all flags and enumerations being + // passed on the stack in a 32-bit field. See FIXME comment in + // gi_field_info_get_field(). The same assumption of enums cast to 32-bit + // signed integers is found in g_value_set_enum() / g_value_set_flags(). + [[nodiscard]] + int64_t enum_from_int(int int_value) const { + if (uses_signed_type()) + return int64_t{int_value}; + else + return int64_t{static_cast(int_value)}; + } + + // Here for symmetry, but result is the same for the two cases + [[nodiscard]] + int enum_to_int(int64_t value) const { + return static_cast(value); + } +}; + +template +using EnumInfoOperations = InfoOperations; + +template +class InfoOperations + : public EnumInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; +}; + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIFieldInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + // Use the various is_FLAG() methods instead. + [[nodiscard]] + GIFieldInfoFlags flags() const { + return gi_field_info_get_flags(ptr()); + } + + public: + [[nodiscard]] size_t offset() const { + return gi_field_info_get_offset(ptr()); + } + [[nodiscard]] + BoolResult read(void* blob, GIArgument* value_out) const { + return this->bool_to_result( + gi_field_info_get_field(ptr(), blob, value_out)); + } + [[nodiscard]] + AutoTypeInfo type_info() const { + return detail::Pointer::to_owned( + gi_field_info_get_type_info(ptr())); + } + [[nodiscard]] + BoolResult write(void* blob, const GIArgument* value) const { + return this->bool_to_result( + gi_field_info_set_field(ptr(), blob, value)); + } + + // Methods not in GIRepository + + [[nodiscard]] + bool is_readable() const { + return flags() & GI_FIELD_IS_READABLE; + } + [[nodiscard]] + bool is_writable() const { + return flags() & GI_FIELD_IS_WRITABLE; + } +}; + +template +class InfoOperations + : public CallableInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GISignalInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } +}; + +template +class InfoOperations + : public RegisteredTypeInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIStructInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + using FieldsIterator = + InfoIterator; + [[nodiscard]] + FieldsIterator fields() const { + return FieldsIterator{ptr()}; + } + + using MethodsIterator = + InfoIterator; + [[nodiscard]] + MethodsIterator methods() const { + return MethodsIterator{ptr()}; + } + [[nodiscard]] + mozilla::Maybe method(const char* name) const { + return detail::Pointer::nullable( + gi_struct_info_find_method(ptr(), name)); + } + + [[nodiscard]] + bool is_foreign() const { + return gi_struct_info_is_foreign(ptr()); + } + [[nodiscard]] + bool is_gtype_struct() const { + return gi_struct_info_is_gtype_struct(ptr()); + } + [[nodiscard]] size_t size() const { return gi_struct_info_get_size(ptr()); } + + operator const BaseInfo() const { + return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); + } +}; + +template +class InfoOperations + : public RegisteredTypeInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIUnionInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + using MethodsIterator = + InfoIterator; + [[nodiscard]] + MethodsIterator methods() const { + return MethodsIterator{ptr()}; + } + [[nodiscard]] + mozilla::Maybe method(const char* name) const { + return detail::Pointer::nullable( + gi_union_info_find_method(ptr(), name)); + } + + [[nodiscard]] size_t size() const { return gi_union_info_get_size(ptr()); } +}; + +template +class InfoOperations + : public CallableInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIVFuncInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + [[nodiscard]] + Gjs::GErrorResult address(GType implementor_gtype) const { + Gjs::AutoError error; // Cannot use GError*, distinguish from void* + void* address = + gi_vfunc_info_get_address(ptr(), implementor_gtype, error.out()); + if (!address) + return mozilla::Err(std::move(error)); + return address; + } + + [[nodiscard]] operator const CallableInfo() const { + return detail::Pointer::to_unowned( + GI_CALLABLE_INFO(ptr())); + } +}; + +template +class InfoOperations + : public RegisteredTypeInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIInterfaceInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + using MethodsIterator = InfoIterator; + [[nodiscard]] + MethodsIterator methods() const { + return MethodsIterator{ptr()}; + } + [[nodiscard]] + mozilla::Maybe method(const char* name) const { + return detail::Pointer::nullable( + gi_interface_info_find_method(ptr(), name)); + } + + using PropertiesIterator = InfoIterator; + [[nodiscard]] + PropertiesIterator properties() const { + return PropertiesIterator{ptr()}; + } + + [[nodiscard]] + mozilla::Maybe iface_struct() const { + return detail::Pointer::nullable( + gi_interface_info_get_iface_struct(ptr())); + } + [[nodiscard]] + mozilla::Maybe signal(const char* name) const { + return detail::Pointer::nullable( + gi_interface_info_find_signal(ptr(), name)); + } + [[nodiscard]] + mozilla::Maybe vfunc(const char* name) const { + return detail::Pointer::nullable( + gi_interface_info_find_vfunc(ptr(), name)); + } +}; + +template +class InfoOperations + : public RegisteredTypeInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIObjectInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + using FieldsIterator = + InfoIterator; + [[nodiscard]] + FieldsIterator fields() const { + return FieldsIterator{ptr()}; + } + + using InterfacesIterator = InfoIterator; + [[nodiscard]] + InterfacesIterator interfaces() const { + return InterfacesIterator{ptr()}; + } + + using MethodsIterator = + InfoIterator; + [[nodiscard]] + MethodsIterator methods() const { + return MethodsIterator{ptr()}; + } + [[nodiscard]] + mozilla::Maybe method(const char* name) const { + return detail::Pointer::nullable( + gi_object_info_find_method(ptr(), name)); + } + + using PropertiesIterator = InfoIterator; + [[nodiscard]] + PropertiesIterator properties() const { + return PropertiesIterator{ptr()}; + } + + [[nodiscard]] + mozilla::Maybe class_struct() const { + return detail::Pointer::nullable( + gi_object_info_get_class_struct(ptr())); + } + [[nodiscard]] + mozilla::Maybe> + find_method_using_interfaces(const char* name) const { + GIBaseInfo* declarer_ptr = nullptr; + GIFunctionInfo* method_ptr = + gi_object_info_find_method_using_interfaces(ptr(), name, + &declarer_ptr); + + if (!method_ptr) { + g_assert(!declarer_ptr); + return {}; + } + + AutoFunctionInfo method{ + detail::Pointer::to_owned(method_ptr)}; + AutoRegisteredTypeInfo declarer{ + detail::Pointer::to_owned( + GI_REGISTERED_TYPE_INFO(declarer_ptr))}; + g_assert(declarer.is_object() || declarer.is_interface()); + return mozilla::Some(std::make_pair(method, declarer)); + } + [[nodiscard]] + mozilla::Maybe> + find_vfunc_using_interfaces(const char* name) const { + GIBaseInfo* declarer_ptr = nullptr; + GIVFuncInfo* vfunc_ptr = gi_object_info_find_vfunc_using_interfaces( + ptr(), name, &declarer_ptr); + + if (!vfunc_ptr) { + g_assert(!declarer_ptr); + return {}; + } + + AutoVFuncInfo vfunc{ + detail::Pointer::to_owned(vfunc_ptr)}; + AutoRegisteredTypeInfo declarer{ + detail::Pointer::to_owned( + GI_REGISTERED_TYPE_INFO(declarer_ptr))}; + g_assert(declarer.is_object() || declarer.is_interface()); + return mozilla::Some(std::make_pair(vfunc, declarer)); + } + [[nodiscard]] + GIObjectInfoGetValueFunction get_value_function_pointer() const { + return gi_object_info_get_get_value_function_pointer(ptr()); + } + [[nodiscard]] + mozilla::Maybe parent() const { + return detail::Pointer::nullable( + gi_object_info_get_parent(ptr())); + } + [[nodiscard]] + GIObjectInfoRefFunction ref_function_pointer() const { + return gi_object_info_get_ref_function_pointer(ptr()); + } + [[nodiscard]] + GIObjectInfoSetValueFunction set_value_function_pointer() const { + return gi_object_info_get_set_value_function_pointer(ptr()); + } + [[nodiscard]] + mozilla::Maybe signal(const char* name) const { + return detail::Pointer::nullable( + gi_object_info_find_signal(ptr(), name)); + } + [[nodiscard]] + GIObjectInfoUnrefFunction unref_function_pointer() const { + return gi_object_info_get_unref_function_pointer(ptr()); + } + [[nodiscard]] + mozilla::Maybe vfunc(const char* name) const { + return detail::Pointer::nullable( + gi_object_info_find_vfunc(ptr(), name)); + } + + [[nodiscard]] operator const BaseInfo() const { + return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); + } +}; + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIPropertyInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + [[nodiscard]] + GParamFlags flags() const { + return gi_property_info_get_flags(ptr()); + } + + public: + [[nodiscard]] + mozilla::Maybe getter() const { + return detail::Pointer::nullable( + gi_property_info_get_getter(ptr())); + } + [[nodiscard]] + mozilla::Maybe setter() const { + return detail::Pointer::nullable( + gi_property_info_get_setter(ptr())); + } + [[nodiscard]] + AutoTypeInfo type_info() const { + return detail::Pointer::to_owned( + gi_property_info_get_type_info(ptr())); + } + + // Methods not in GIRepository + + [[nodiscard]] + bool has_deprecated_param_flag() const { + // Note, different from is_deprecated(). It's possible that the property + // has the deprecated GParamSpec flag, but is not marked deprecated in + // the GIR doc comment. + return flags() & G_PARAM_DEPRECATED; + } +}; + +// Out-of-line definition to avoid chicken-and-egg loop between AutoFunctionInfo +// and AutoPropertyInfo +template +inline mozilla::Maybe +InfoOperations::property() const { + return detail::Pointer::nullable( + gi_function_info_get_property(ptr())); +} + +template +class InfoOperations + : public BaseInfoOperations { + DELETE_ALL_TYPECHECK_METHODS; + + [[nodiscard]] + GIValueInfo* ptr() const { + return detail::Pointer::get_from(*static_cast(this)); + } + + public: + [[nodiscard]] + int64_t value() const { + return gi_value_info_get_value(ptr()); + } +}; + +// In order to avoid having to create an OwnedInfo or UnownedInfo from a pointer +// anywhere except in these wrappers, we also wrap GIRepository. +// (ArgCache::HasTypeInfo is the one exception.) +class Repository { + Gjs::AutoUnref m_ptr = gi_repository_dup_default(); + + // Helper object for iterating the introspection info objects of a + // namespace. Unlike the other introspection info iterators, this requires + // two parameters, the GIRepository* and the namespace string, so we need + // this helper object to adapt InfoIterator. + struct IterableNamespace { + GIRepository* repo; + const char* ns; + + static unsigned get_n_infos(const IterableNamespace obj) { + return gi_repository_get_n_infos(obj.repo, obj.ns); + } + + static GIBaseInfo* get_info(const IterableNamespace obj, unsigned ix) { + return gi_repository_get_info(obj.repo, obj.ns, ix); + } + + bool operator==(const IterableNamespace& other) const { + return repo == other.repo && strcmp(ns, other.ns) == 0; + } + + bool operator!=(const IterableNamespace& other) const { + return !(*this == other); + } + }; + + public: + using Iterator = InfoIterator; + [[nodiscard]] + Iterator infos(const char* ns) const { + return Iterator{{m_ptr, ns}}; + } + + [[nodiscard]] + Gjs::AutoStrv enumerate_versions(const char* ns, size_t* n_versions) const { + return gi_repository_enumerate_versions(m_ptr, ns, n_versions); + } + [[nodiscard]] + mozilla::Maybe find_by_error_domain(GQuark domain) const { + return detail::Pointer::nullable( + gi_repository_find_by_error_domain(m_ptr, domain)); + } + template + [[nodiscard]] + mozilla::Maybe> find_by_gtype(GType gtype) const { + return detail::Pointer::nullable(detail::Pointer::cast( + gi_repository_find_by_gtype(m_ptr, gtype))); + } + template + [[nodiscard]] + mozilla::Maybe> find_by_name(const char* ns, + const char* name) const { + return detail::Pointer::nullable(detail::Pointer::cast( + gi_repository_find_by_name(m_ptr, ns, name))); + } + [[nodiscard]] + const char* get_version(const char* ns) const { + return gi_repository_get_version(m_ptr, ns); + } + [[nodiscard]] + bool is_registered(const char* ns, const char* version) const { + return gi_repository_is_registered(m_ptr, ns, version); + } + [[nodiscard]] + mozilla::Span object_get_gtype_interfaces( + GType gtype) const { + InterfaceInfo* interfaces; + size_t n_interfaces; + gi_repository_get_object_gtype_interfaces( + m_ptr, gtype, &n_interfaces, + reinterpret_cast(&interfaces)); + return {interfaces, n_interfaces}; + } + void prepend_search_path(const char* path) { + gi_repository_prepend_search_path(m_ptr, path); + } + [[nodiscard]] + Gjs::GErrorResult require( + const char* ns, const char* version, + GIRepositoryLoadFlags flags = {}) const { + GError* error = nullptr; + GITypelib* typelib = + gi_repository_require(m_ptr, ns, version, flags, &error); + if (!typelib) + return mozilla::Err(error); + return typelib; + } +}; + +///// STACK-ALLOCATED INTROSPECTION INFO /////////////////////////////////////// + +// Introspection info allocated directly on the stack. This is used only in a +// few cases, for performance reasons. In C, the stack-allocated struct is +// filled in by a function such as gi_arg_info_load_type_info(). +// Needs to appear at the end, due to FIXME. + +class StackArgInfo : public InfoOperations { + friend struct detail::Pointer; + + GIArgInfo m_info = {}; + + [[nodiscard]] + constexpr GIArgInfo* ptr() const { + return detail::Pointer::get_from(*this); + } + + public: + constexpr StackArgInfo() {} + ~StackArgInfo() { gi_base_info_clear(&m_info); } + // Moving is okay, we copy the contents of the GIArgInfo struct and reset + // the existing one + StackArgInfo(StackArgInfo&& other) : m_info(other.m_info) { + gi_base_info_clear(&other.m_info); + } + StackArgInfo& operator=(StackArgInfo&& other) { + m_info = other.m_info; + gi_base_info_clear(&other.m_info); + return *this; + } + // Prefer moving to copying + StackArgInfo(const StackArgInfo&) = delete; + StackArgInfo& operator=(const StackArgInfo&) = delete; +}; + +class StackTypeInfo : public InfoOperations { + friend struct detail::Pointer; + + GITypeInfo m_info = {}; + + [[nodiscard]] + constexpr GITypeInfo* ptr() const { + return detail::Pointer::get_from(*this); + } + + public: + constexpr StackTypeInfo() {} + ~StackTypeInfo() { gi_base_info_clear(&m_info); } + // Moving is okay, we copy the contents of the GITypeInfo struct and reset + // the existing one + StackTypeInfo(StackTypeInfo&& other) : m_info(other.m_info) { + gi_base_info_clear(&other.m_info); + } + StackTypeInfo& operator=(StackTypeInfo&& other) { + m_info = other.m_info; + gi_base_info_clear(&other.m_info); + return *this; + } + // Prefer moving to copying + StackTypeInfo(const StackTypeInfo&) = delete; + StackTypeInfo& operator=(const StackTypeInfo&) = delete; +}; + +namespace detail { +constexpr inline GIArgInfo* Pointer::get_from(const StackArgInfo& stack) { + return const_cast(&stack.m_info); +} +constexpr inline GITypeInfo* Pointer::get_from(const StackTypeInfo& stack) { + return const_cast(&stack.m_info); +} +inline void Pointer::to_stack(GITypeInfo* ptr, StackTypeInfo* stack) { + stack->m_info = std::move(*ptr); + // Hacky: Reproduce gi_info_init() and mark the copied GITypeInfo as + // stack-allocated. Unfortunately, GI_TYPE_TYPE_INFO makes this function + // unable to be constexpr. + GIBaseInfoStack* stack_ptr = &stack->m_info.parent; + stack_ptr->parent_instance.g_class = + static_cast(g_type_class_ref(GI_TYPE_TYPE_INFO)); + stack_ptr->dummy0 = 0x7fff'ffff; +} +} // namespace detail + +static_assert(sizeof(StackArgInfo) == sizeof(GIArgInfo), + "StackArgInfo should be byte-compatible with GIArgInfo"); +static_assert(sizeof(StackTypeInfo) == sizeof(GITypeInfo), + "StackTypeInfo should be byte-compatible with GITypeInfo"); + +} // namespace GI + +/* For use of GI::OwnedInfo in GC hash maps */ +namespace JS { +template +struct GCPolicy> + : public IgnoreGCPolicy> {}; +} // namespace JS diff --git a/gi/interface.cpp b/gi/interface.cpp index 82fc13907..6ad53ed84 100644 --- a/gi/interface.cpp +++ b/gi/interface.cpp @@ -5,8 +5,6 @@ #include -#include - #include #include // for JS_ReportOutOfMemory #include // for MutableHandleIdVector @@ -15,6 +13,7 @@ #include // for UniqueChars #include "gi/function.h" +#include "gi/info.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/repo.h" @@ -23,7 +22,10 @@ #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" -InterfacePrototype::InterfacePrototype(GIInterfaceInfo* info, GType gtype) +using mozilla::Maybe, mozilla::Nothing; + +InterfacePrototype::InterfacePrototype(Maybe info, + GType gtype) : GIWrapperPrototype(info, gtype), m_vtable( static_cast(g_type_default_interface_ref(gtype))) { @@ -41,17 +43,15 @@ bool InterfacePrototype::new_enumerate_impl( if (!info()) return true; - int n_methods = g_interface_info_get_n_methods(info()); + GI::InterfaceInfo::MethodsIterator methods = info()->methods(); + int n_methods = methods.size(); if (!properties.reserve(properties.length() + n_methods)) { JS_ReportOutOfMemory(cx); return false; } - for (int i = 0; i < n_methods; i++) { - GjsAutoFunctionInfo meth_info = g_interface_info_get_method(info(), i); - GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); - - if (flags & GI_FUNCTION_IS_METHOD) { + for (GI::AutoFunctionInfo meth_info : methods) { + if (meth_info.is_method()) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) @@ -82,18 +82,13 @@ bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj, return true; // not resolved, but no error } - GjsAutoFunctionInfo method_info = - g_interface_info_find_method(m_info, prop_name.get()); + Maybe method_info{m_info->method(prop_name.get())}; - if (method_info) { - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { - if (!gjs_define_function(context, obj, m_gtype, method_info)) - return false; + if (method_info && method_info->is_method()) { + if (!gjs_define_function(context, obj, m_gtype, method_info.ref())) + return false; - *resolved = true; - } else { - *resolved = false; - } + *resolved = true; } else { *resolved = false; } @@ -137,8 +132,8 @@ bool InterfacePrototype::has_instance_impl(JSContext* cx, } JS::RootedObject instance(cx, &args[0].toObject()); - bool isinstance = ObjectBase::typecheck(cx, instance, nullptr, m_gtype, - GjsTypecheckNoThrow()); + bool isinstance = + ObjectBase::typecheck(cx, instance, m_gtype, GjsTypecheckNoThrow{}); args.rval().setBoolean(isinstance); return true; } @@ -171,16 +166,16 @@ gjs_lookup_interface_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p) { - JSObject *constructor; - - GjsAutoInterfaceInfo interface_info = gjs_lookup_gtype(nullptr, gtype); + GI::Repository repo; + Maybe interface_info{repo.find_by_gtype(gtype)}; if (!interface_info) { gjs_throw(context, "Cannot expose non introspectable interface %s", g_type_name(gtype)); return false; } - constructor = gjs_lookup_generic_constructor(context, interface_info); + JSObject* constructor = + gjs_lookup_generic_constructor(context, interface_info.ref()); if (G_UNLIKELY(!constructor)) return false; diff --git a/gi/interface.h b/gi/interface.h index 393cfd713..78752ccc0 100644 --- a/gi/interface.h +++ b/gi/interface.h @@ -8,7 +8,6 @@ #include -#include #include #include @@ -16,8 +15,10 @@ #include #include #include +#include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -71,9 +72,13 @@ class InterfaceBase : public GIWrapperBase { + InterfaceInstance, + mozilla::Maybe, + mozilla::Maybe> { friend class GIWrapperPrototype; + InterfaceInstance, + mozilla::Maybe, + mozilla::Maybe>; friend class GIWrapperBase; friend class InterfaceBase; // for has_instance_impl @@ -81,9 +86,7 @@ class InterfacePrototype // the GTypeInterface vtable wrapped by this JS object GTypeInterface* m_vtable; - static constexpr InfoType::Tag info_type_tag = InfoType::Interface; - - explicit InterfacePrototype(GIInterfaceInfo* info, GType gtype); + explicit InterfacePrototype(mozilla::Maybe, GType); ~InterfacePrototype(void); // JSClass operations diff --git a/gi/js-value-inl.h b/gi/js-value-inl.h index 11533be90..756c28d7e 100644 --- a/gi/js-value-inl.h +++ b/gi/js-value-inl.h @@ -11,137 +11,108 @@ #include // for isnan #include #include +#include +#include // for move -#include #include #include #include +#include // for JS_EncodeStringToUTF8 #include +#include // for JSExnType #include #include #include // for UniqueChars #include // for CanonicalizeNaN +#include "gi/arg-types-inl.h" #include "gi/gtype.h" #include "gi/value.h" +#include "cjs/auto.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" namespace Gjs { -template -struct TypeWrapper { - constexpr TypeWrapper() : m_value(0) {} - explicit constexpr TypeWrapper(T v) : m_value(v) {} - constexpr operator T() const { return m_value; } - constexpr operator T() { return m_value; } +// There are two ways you can unpack a C value from a JSValue. +// ContainingType means storing the unpacked value in the most appropriate C +// type that can contain it. Implicit conversion may be performed and the value +// may need to be checked to make sure it is in range. +// PackType, on the other hand, means storing it in the C type that is exactly +// equivalent to how JSValue stores it, so no implicit conversion is performed +// unless the JSValue contains a pointer to a GC-thing, like BigInt. +enum HolderMode { ContainingType, PackType }; - private: - T m_value; -}; - -namespace JsValueHolder { - -template -constexpr bool comparable_types() { - return std::is_arithmetic_v == std::is_arithmetic_v && - std::is_signed_v == std::is_signed_v; +template +constexpr bool type_has_js_getter() { + if constexpr (MODE == HolderMode::PackType) { + return std::is_same_v, Tag::JSValuePackT>; + } else { + return std::is_same_v, Tag::JSValueContainingT>; + } } -template -constexpr bool type_fits() { - if constexpr (comparable_types()) { - return (std::is_integral_v == std::is_integral_v && - std::numeric_limits::max() <= - std::numeric_limits::max() && - std::numeric_limits::lowest() >= - std::numeric_limits::lowest()); - } +/* Avoid implicit conversions */ +template +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*, + JS::HandleValue, + UnpackT*) = delete; - return false; +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, int32_t* out) { + return JS::ToInt32(cx, value, out); } -/* The tag is needed to disambiguate types such as gboolean and GType - * which are in fact typedef's of other generic types. - * Setting a tag for a type allows to perform proper specialization. */ -template -constexpr auto get_strict() { - if constexpr (TAG != GI_TYPE_TAG_VOID) { - if constexpr (std::is_same_v && TAG == GI_TYPE_TAG_GTYPE) - return GType{}; - else if constexpr (std::is_same_v && - TAG == GI_TYPE_TAG_BOOLEAN) - return gboolean{}; - else - return; - } else { - if constexpr (std::is_same_v) - return char32_t{}; - else if constexpr (type_fits()) - return int32_t{}; - else if constexpr (type_fits()) - return uint32_t{}; - else if constexpr (type_fits()) - return int64_t{}; - else if constexpr (type_fits()) - return uint64_t{}; - else if constexpr (type_fits()) - return double{}; - else - return T{}; - } +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool + js_value_to_c // NOLINT(runtime/int) + (JSContext* cx, JS::HandleValue value, int32_t* out) { + return JS::ToInt32(cx, value, out); } -template -constexpr auto get_relaxed() { - if constexpr (std::is_same_v || std::is_same_v) - return TypeWrapper{}; - else if constexpr (type_fits()) - return int32_t{}; - else if constexpr (type_fits()) - return uint32_t{}; - else if constexpr (std::is_arithmetic_v) - return double{}; - else - return T{}; +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, int32_t* out) { + return JS::ToInt32(cx, value, out); } -template -using Strict = decltype(JsValueHolder::get_strict()); - -template -using Relaxed = decltype(JsValueHolder::get_relaxed()); - -} // namespace JsValueHolder - - -template > -constexpr bool type_has_js_getter() { - return std::is_same_v; +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, int32_t* out) { + return JS::ToInt32(cx, value, out); } -/* Avoid implicit conversions */ -template -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*, - const JS::HandleValue&, - T*) = delete; +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, uint32_t* out) { + return JS::ToUint32(cx, value, out); +} template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, int32_t* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool + js_value_to_c // NOLINT(runtime/int) + (JSContext* cx, JS::HandleValue value, int32_t* out) { return JS::ToInt32(cx, value, out); } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, uint32_t* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool + js_value_to_c // NOLINT(runtime/int) + (JSContext* cx, JS::HandleValue value, uint32_t* out) { + return JS::ToUint32(cx, value, out); +} + +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, uint32_t* out) { return JS::ToUint32(cx, value, out); } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, char32_t* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, char32_t* out) { uint32_t tmp; bool retval = JS::ToUint32(cx, value, &tmp); *out = tmp; @@ -149,8 +120,8 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, int64_t* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, int64_t* out) { if (value.isBigInt()) { *out = JS::ToBigInt64(value.toBigInt()); return true; @@ -159,8 +130,8 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, uint64_t* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, uint64_t* out) { if (value.isBigInt()) { *out = JS::ToBigUint64(value.toBigInt()); return true; @@ -169,21 +140,33 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, double* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, double* out) { + return JS::ToNumber(cx, value, out); +} + +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, double* out) { return JS::ToNumber(cx, value, out); } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext*, const JS::HandleValue& value, gboolean* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, double* out) { + return JS::ToNumber(cx, value, out); +} + +template <> +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext*, JS::HandleValue value, gboolean* out) { *out = !!JS::ToBoolean(value); return true; } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, GType* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, GType* out) { if (!value.isObject()) return false; @@ -200,21 +183,27 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, GValue* out) { +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, GValue* out) { *out = G_VALUE_INIT; return gjs_value_to_g_value(cx, value, out); } template <> -GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( - JSContext* cx, const JS::HandleValue& value, char** out) { - JS::UniqueChars tmp_result = gjs_string_to_utf8(cx, value); +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( + JSContext* cx, JS::HandleValue value, char** out) { + if (value.isNull()) { + *out = nullptr; + return true; + } - if (!tmp_result) + if (!value.isString()) return false; - *out = g_strdup(tmp_result.get()); + JS::RootedString str{cx, value.toString()}; + JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str); + + *out = js_chars_to_glib(std::move(utf8)).release(); return true; } @@ -231,9 +220,12 @@ template return std::numeric_limits::lowest(); } -template +template , TAG>>, + typename U> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( - JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) { + JSContext* cx, JS::HandleValue value, U* out, bool* out_of_range) { + using T = Tag::RealT; static_assert(std::numeric_limits::max() >= std::numeric_limits::max() && std::numeric_limits::lowest() <= @@ -307,32 +299,60 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( } } -template +template , T>>> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( - JSContext* cx, const JS::HandleValue& value, TypeWrapper* out, + JSContext* cx, JS::HandleValue value, T* out, bool* out_of_range) { + return js_value_to_c_checked(cx, value, out, + out_of_range); +} + +template +GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( + JSContext* cx, JS::HandleValue value, TypeWrapper* out, bool* out_of_range) { static_assert(std::is_integral_v); - WantedType wanted_out; - if (!js_value_to_c_checked(cx, value, &wanted_out, - out_of_range)) - return false; + if constexpr (std::is_same_v) { + WantedType wanted_out; + if (!js_value_to_c_checked(cx, value, &wanted_out, + out_of_range)) + return false; - *out = TypeWrapper{wanted_out}; + *out = TypeWrapper{wanted_out}; - return true; + return true; + } + + // Handle the cases resulting from TypeWrapper and + // TypeWrapper not being convertible on macOS + if constexpr (!std::is_same_v && // NOLINT(runtime/int) + std::is_same_v && // NOLINT(runtime/int) + std::is_same_v) { + return js_value_to_c_checked(cx, value, out, + out_of_range); + } + + if constexpr (!std::is_same_v && // NOLINT(runtime/int) + std::is_same_v && // NOLINT(runtime/int) + std::is_same_v) { + return js_value_to_c_checked(cx, value, out, + out_of_range); + // https://trac.cppcheck.net/ticket/10731 + // cppcheck-suppress missingReturn + } } -template +template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js( - JSContext* cx [[maybe_unused]], T value, + JSContext* cx [[maybe_unused]], Tag::RealT value, JS::MutableHandleValue js_value_p) { - if constexpr (std::is_same_v) { - js_value_p.setBoolean(value); - return true; - } else if constexpr (std::is_same_v< // NOLINT(readability/braces) - T, gboolean> && - TAG == GI_TYPE_TAG_BOOLEAN) { + using T = Tag::RealT; + + if constexpr (std::is_same_v || + std::is_same_v) { js_value_p.setBoolean(value); return true; } else if constexpr (std::is_arithmetic_v) { @@ -363,10 +383,20 @@ GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js( } } -template +// Specialization for types where TAG and RealT are the same type, to allow +// inferring template parameter +template , T>>> +GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js( + JSContext* cx, T value, JS::MutableHandleValue js_value_p) { + return c_value_to_js(cx, value, js_value_p); +} + +template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked( - JSContext* cx [[maybe_unused]], T value, + JSContext* cx [[maybe_unused]], Tag::RealT value, JS::MutableHandleValue js_value_p) { + using T = Tag::RealT; if constexpr (std::is_same_v || std::is_same_v) { if (value < Gjs::min_safe_big_number() || value > Gjs::max_safe_big_number()) { @@ -377,7 +407,25 @@ GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked( } } - return c_value_to_js(cx, value, js_value_p); + if constexpr (std::is_same_v) { + if (value && !g_utf8_validate(value, -1, nullptr)) { + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, + "String from C value is invalid UTF-8 and cannot " + "be safely stored"); + return false; + } + } + + return c_value_to_js(cx, value, js_value_p); +} + +// Specialization for types where TAG and RealT are the same type, to allow +// inferring template parameter +template , T>>> +GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked( + JSContext* cx, T value, JS::MutableHandleValue js_value_p) { + return c_value_to_js_checked(cx, value, js_value_p); } } // namespace Gjs diff --git a/gi/ns.cpp b/gi/ns.cpp index e3f09e10e..f2ac24bdc 100644 --- a/gi/ns.cpp +++ b/gi/ns.cpp @@ -8,7 +8,6 @@ #include -#include #include #include @@ -23,100 +22,55 @@ #include #include // for UniqueChars #include // for JS_NewObjectWithGivenProto +#include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/ns.h" #include "gi/repo.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" +#include "cjs/deprecation.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" -#if GLIB_CHECK_VERSION(2, 79, 2) -# include "cjs/deprecation.h" -#endif // GLib >= 2.79.2 - -[[nodiscard]] static bool type_is_enumerable(GIInfoType info_type) { - switch (info_type) { - case GI_INFO_TYPE_BOXED: - case GI_INFO_TYPE_STRUCT: - case GI_INFO_TYPE_UNION: - case GI_INFO_TYPE_OBJECT: - case GI_INFO_TYPE_ENUM: - case GI_INFO_TYPE_FLAGS: - case GI_INFO_TYPE_INTERFACE: - case GI_INFO_TYPE_FUNCTION: - case GI_INFO_TYPE_CONSTANT: - return true; - // Don't enumerate types which GJS doesn't define on namespaces. - // See gjs_define_info - case GI_INFO_TYPE_INVALID: - case GI_INFO_TYPE_INVALID_0: - case GI_INFO_TYPE_CALLBACK: - case GI_INFO_TYPE_VALUE: - case GI_INFO_TYPE_SIGNAL: - case GI_INFO_TYPE_VFUNC: - case GI_INFO_TYPE_PROPERTY: - case GI_INFO_TYPE_FIELD: - case GI_INFO_TYPE_ARG: - case GI_INFO_TYPE_TYPE: - case GI_INFO_TYPE_UNRESOLVED: - default: - return false; - } +using mozilla::Maybe; + +// helper function +void platform_specific_warning_glib(JSContext* cx, const char* prefix, + const char* platform, + const char* resolved_name) { + if (!g_str_has_prefix(resolved_name, prefix)) + return; + + const char* base_name = resolved_name + strlen(prefix); + Gjs::AutoChar old_name{g_strdup_printf("GLib.%s", resolved_name)}; + Gjs::AutoChar new_name{g_strdup_printf("GLib%s.%s", platform, base_name)}; + _gjs_warn_deprecated_once_per_callsite( + cx, GjsDeprecationMessageId::PlatformSpecificTypelib, + {old_name.get(), new_name.get()}); } -class Ns : private GjsAutoChar, public CWrapper { +class Ns : private Gjs::AutoChar, public CWrapper { friend CWrapperPointerOps; friend CWrapper; -#if GLIB_CHECK_VERSION(2, 79, 2) - bool m_is_gio_or_glib : 1; -#endif // GLib >= 2.79.2 - static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_ns; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GNAMESPACE; explicit Ns(const char* ns_name) - : GjsAutoChar(const_cast(ns_name), GjsAutoTakeOwnership()) { + : Gjs::AutoChar(const_cast(ns_name), Gjs::TakeOwnership{}) { GJS_INC_COUNTER(ns); -#if GLIB_CHECK_VERSION(2, 79, 2) - m_is_gio_or_glib = - strcmp(ns_name, "Gio") == 0 || strcmp(ns_name, "GLib") == 0; -#endif // GLib >= 2.79.2 + m_is_glib = strcmp(ns_name, "GLib") == 0; } ~Ns() { GJS_DEC_COUNTER(ns); } -#if GLIB_CHECK_VERSION(2, 79, 2) - // helper function - void platform_specific_warning(JSContext* cx, const char* prefix, - const char* platform, - const char* resolved_name, - const char** exceptions = nullptr) { - if (!g_str_has_prefix(resolved_name, prefix)) - return; - - const char* base_name = resolved_name + strlen(prefix); - GjsAutoChar old_name = - g_strdup_printf("%s.%s", this->get(), resolved_name); - if (exceptions) { - for (const char** exception = exceptions; *exception; exception++) { - if (strcmp(old_name, *exception) == 0) - return; - } - } - - GjsAutoChar new_name = - g_strdup_printf("%s%s.%s", this->get(), platform, base_name); - _gjs_warn_deprecated_once_per_callsite( - cx, GjsDeprecationMessageId::PlatformSpecificTypelib, - {old_name.get(), new_name.get()}); - } -#endif // GLib >= 2.79.2 + bool m_is_glib : 1; // JSClass operations @@ -145,8 +99,8 @@ class Ns : private GjsAutoChar, public CWrapper { return true; // not resolved, but no error } - GjsAutoBaseInfo info = - g_irepository_find_by_name(nullptr, get(), name.get()); + Maybe info{ + GI::Repository{}.find_by_name(get(), name.get())}; if (!info) { *resolved = false; // No property defined, but no error either return true; @@ -154,30 +108,19 @@ class Ns : private GjsAutoChar, public CWrapper { gjs_debug(GJS_DEBUG_GNAMESPACE, "Found info type %s for '%s' in namespace '%s'", - gjs_info_type_name(info.type()), info.name(), info.ns()); - -#if GLIB_CHECK_VERSION(2, 79, 2) - static const char* unix_types_exceptions[] = { - "Gio.UnixConnection", - "Gio.UnixCredentialsMessage", - "Gio.UnixFDList", - "Gio.UnixSocketAddress", - "Gio.UnixSocketAddressType", - nullptr}; - - if (m_is_gio_or_glib) { - platform_specific_warning(cx, "Unix", "Unix", name.get(), - unix_types_exceptions); - platform_specific_warning(cx, "unix_", "Unix", name.get()); - platform_specific_warning(cx, "Win32", "Win32", name.get()); - platform_specific_warning(cx, "win32_", "Win32", name.get()); + info->type_string(), info->name(), info->ns()); + + if (m_is_glib) { + platform_specific_warning_glib(cx, "Unix", "Unix", name.get()); + platform_specific_warning_glib(cx, "unix_", "Unix", name.get()); + platform_specific_warning_glib(cx, "Win32", "Win32", name.get()); + platform_specific_warning_glib(cx, "win32_", "Win32", name.get()); } -#endif // GLib >= 2.79.2 bool defined; - if (!gjs_define_info(cx, obj, info, &defined)) { + if (!gjs_define_info(cx, obj, info.ref(), &defined)) { gjs_debug(GJS_DEBUG_GNAMESPACE, "Failed to define info '%s'", - info.name()); + info->name()); return false; } @@ -191,21 +134,17 @@ class Ns : private GjsAutoChar, public CWrapper { JS::HandleObject obj [[maybe_unused]], JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { - int n = g_irepository_get_n_infos(nullptr, get()); - if (!properties.reserve(properties.length() + n)) { + GI::Repository::Iterator infos{GI::Repository{}.infos(get())}; + if (!properties.reserve(properties.length() + infos.size())) { JS_ReportOutOfMemory(cx); return false; } - for (int k = 0; k < n; k++) { - GjsAutoBaseInfo info = g_irepository_get_info(nullptr, get(), k); - GIInfoType info_type = g_base_info_get_type(info); - if (!type_is_enumerable(info_type)) + for (GI::AutoBaseInfo info : infos) { + if (!info.is_enumerable()) continue; - const char* name = info.name(); - - jsid id = gjs_intern_string_to_id(cx, name); + jsid id = gjs_intern_string_to_id(cx, info.name()); if (id.isVoid()) return false; properties.infallibleAppend(id); @@ -230,7 +169,7 @@ class Ns : private GjsAutoChar, public CWrapper { GJS_JSAPI_RETURN_CONVENTION static bool get_version(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, Ns, priv); - const char *version = g_irepository_get_version(nullptr, priv->get()); + const char* version = GI::Repository{}.get_version(priv->get()); return gjs_string_from_utf8(cx, version, args.rval()); } diff --git a/gi/object.cpp b/gi/object.cpp index 0384b738e..439137962 100644 --- a/gi/object.cpp +++ b/gi/object.cpp @@ -9,6 +9,7 @@ #include // for memset, strcmp #include // for find +#include #include // for mem_fn #include #include @@ -17,7 +18,8 @@ #include // for move #include -#include +#include +#include #include #include @@ -27,10 +29,12 @@ #include #include #include // for JS_ReportOutOfMemory +#include // for JS_ClearPendingException #include // for JS_AddWeakPointerCompartmentCallback #include // for MutableWrappedPtrOperations #include #include // for AddAssociatedMemory, RemoveAssoci... +#include #include #include // for JSPROP_PERMANENT, JSPROP_READONLY #include @@ -42,14 +46,21 @@ #include #include // for JS_GetFunctionObject, IdVector #include // for JS_GetObjectFunction, GetFunctionNativeReserved -#include +#include +#include +#include +#include +#include #include "gi/arg-inl.h" +#include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" -#include "gi/cjs_gi_trace.h" +#include "gi/gjs_gi_trace.h" +#include "gi/info.h" +#include "gi/js-value-inl.h" // for Relaxed, c_value_to_js_checked #include "gi/object.h" #include "gi/repo.h" #include "gi/toggle.h" @@ -57,12 +68,14 @@ #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-class.h" -#include "cjs/jsapi-util.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util-root.h" +#include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "cjs/profiler-private.h" @@ -70,6 +83,9 @@ class JSTracer; +using mozilla::Err, mozilla::Maybe, mozilla::Nothing, mozilla::Ok, + mozilla::Result, mozilla::Some; + /* This is a trick to print out the sizes of the structs at compile time, in * an error message. */ // template struct Measure; @@ -92,8 +108,8 @@ decltype(ObjectInstance::s_wrapped_gobject_list) static const auto DISPOSED_OBJECT = std::numeric_limits::max(); GJS_JSAPI_RETURN_CONVENTION -static JSObject* gjs_lookup_object_prototype_from_info(JSContext*, GIBaseInfo*, - GType); +static JSObject* gjs_lookup_object_prototype_from_info( + JSContext*, Maybe, GType); // clang-format off G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type) @@ -115,15 +131,12 @@ bool ObjectBase::is_custom_js_class() { } void ObjectInstance::link() { - g_assert(std::find(s_wrapped_gobject_list.begin(), - s_wrapped_gobject_list.end(), - this) == s_wrapped_gobject_list.end()); - s_wrapped_gobject_list.push_back(this); + auto [_, done] = s_wrapped_gobject_list.insert(this); + g_assert(done); + mozilla::Unused << done; } -void ObjectInstance::unlink() { - Gjs::remove_one_from_unsorted_vector(&s_wrapped_gobject_list, this); -} +void ObjectInstance::unlink() { s_wrapped_gobject_list.erase(this); } const void* ObjectBase::jsobj_addr(void) const { if (is_prototype()) @@ -131,32 +144,18 @@ const void* ObjectBase::jsobj_addr(void) const { return to_instance()->m_wrapper.debug_addr(); } -// Overrides GIWrapperBase::typecheck(). We only override the overload that -// throws, so that we can throw our own more informative error. -bool ObjectBase::typecheck(JSContext* cx, JS::HandleObject obj, - GIObjectInfo* expected_info, GType expected_gtype) { - if (GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype)) - return true; - - gjs_throw(cx, - "This JS object wrapper isn't wrapping a GObject." - " If this is a custom subclass, are you sure you chained" - " up to the parent _init properly?"); - return false; -} - bool ObjectInstance::check_gobject_disposed_or_finalized( const char* for_what) const { if (!m_gobj_disposed) return true; g_critical( - "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.\n%s", - ns(), name(), m_ptr.get(), m_gobj_finalized ? "finalized" : "disposed", - for_what, gjs_dumpstack_string().c_str()); + "Object %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.\n%s", + format_name().c_str(), m_ptr.get(), + m_gobj_finalized ? "finalized" : "disposed", for_what, + gjs_dumpstack_string().c_str()); return false; } @@ -233,7 +232,7 @@ ObjectInstance::unset_object_qdata(void) } GParamSpec* ObjectPrototype::find_param_spec_from_id( - JSContext* cx, GjsAutoTypeClass const& object_class, + JSContext* cx, Gjs::AutoTypeClass const& object_class, JS::HandleString key) { /* First check for the ID in the cache */ @@ -241,7 +240,7 @@ GParamSpec* ObjectPrototype::find_param_spec_from_id( if (!js_prop_name) return nullptr; - GjsAutoChar gname = gjs_hyphen_from_camel(js_prop_name.get()); + Gjs::AutoChar gname{gjs_hyphen_from_camel(js_prop_name.get())}; GParamSpec* pspec = g_object_class_find_property(object_class, gname); if (!pspec) { @@ -284,14 +283,16 @@ bool ObjectInstance::add_property_impl(JSContext* cx, JS::HandleObject obj, return true; } +template bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); auto* pspec = static_cast( gjs_dynamic_property_private_slot(&args.callee()).toPrivate()); - std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"}; - AutoProfilerLabel label(cx, "property getter", fullName.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + pspec->name + "\"]")}; + AutoProfilerLabel label{cx, "property getter", full_name}; priv->debug_jsprop("Property getter", pspec->name, obj); @@ -300,9 +301,10 @@ bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) { /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ - return priv->to_instance()->prop_getter_impl(cx, pspec, args.rval()); + return priv->to_instance()->prop_getter_impl(cx, pspec, args.rval()); } +template bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param, JS::MutableHandleValue rval) { if (!check_gobject_finalized("get any property from")) { @@ -311,14 +313,8 @@ bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param, } if (param->flags & G_PARAM_DEPRECATED) { - const std::string& class_name = format_name(); - _gjs_warn_deprecated_once_per_callsite( - cx, DeprecatedGObjectProperty, {class_name.c_str(), param->name}); - } - - if ((param->flags & G_PARAM_READABLE) == 0) { - rval.setUndefined(); - return true; + _gjs_warn_deprecated_once_per_callsite(cx, DeprecatedGObjectProperty, + {format_name(), param->name}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", @@ -327,65 +323,320 @@ bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param, Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param)); g_object_get_property(m_ptr, param->name, &gvalue); - return gjs_value_from_g_value(cx, rval, &gvalue); + if constexpr (!std::is_same_v) { + if (Gjs::c_value_to_js_checked(cx, Gjs::gvalue_get(&gvalue), + rval)) + return true; + + gjs_throw(cx, "Can't convert value %s got from %s::%s property", + Gjs::gvalue_to_string(&gvalue).c_str(), + format_name().c_str(), param->name); + return false; + } else { + return gjs_value_from_g_value(cx, rval, &gvalue); + } } -[[nodiscard]] static GjsAutoFieldInfo lookup_field_info(GIObjectInfo* info, - const char* name) { - int n_fields = g_object_info_get_n_fields(info); - int ix; - GjsAutoFieldInfo retval; +bool ObjectBase::prop_getter_write_only(JSContext*, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + return true; +} - for (ix = 0; ix < n_fields; ix++) { - retval = g_object_info_get_field(info, ix); - if (strcmp(name, retval.name()) == 0) - break; - retval.reset(); +class ObjectPropertyInfoCaller { + public: + GI::AutoFunctionInfo func_info; + void* native_address; + + explicit ObjectPropertyInfoCaller(const GI::FunctionInfo info) + : func_info(info), native_address(nullptr) {} + + Gjs::GErrorResult<> init() { + GIFunctionInvoker invoker; + MOZ_TRY(func_info.prep_invoker(&invoker)); + native_address = invoker.native_address; + gi_function_invoker_clear(&invoker); + return Ok{}; } +}; - if (!retval || !(g_field_info_get_flags(retval) & GI_FIELD_IS_READABLE)) - return nullptr; +bool ObjectBase::prop_getter_func(JSContext* cx, unsigned argc, JS::Value* vp) { + GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); + + JS::RootedObject pspec_obj{ + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; + auto* info_caller = + JS::ObjectGetStashedPointer(cx, pspec_obj); + + const GI::AutoFunctionInfo& func_info = info_caller->func_info; + GI::AutoPropertyInfo property_info{func_info.property().value()}; + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + property_info.name() + "\"]")}; + AutoProfilerLabel label{cx, "property getter", full_name}; + + priv->debug_jsprop("Property getter", property_info.name(), obj); + + // Ignore silently; note that this is different from what we do for + // boxed types, for historical reasons + if (priv->is_prototype()) + return true; + + return priv->to_instance()->prop_getter_impl(cx, info_caller, args); +} + +template +[[nodiscard]] +static bool simple_getter_caller(GObject* obj, void* native_address, + GIArgument* out_arg) { + using T = Gjs::Tag::RealT; + using FuncType = T (*)(GObject*); + FuncType func = reinterpret_cast(native_address); + + gjs_arg_set(out_arg, func(obj)); + return true; +} + +[[nodiscard]] +static bool simple_getters_caller(const GI::TypeInfo type_info, GObject* obj, + void* native_address, GIArgument* out_arg) { + switch (type_info.tag()) { + case GI_TYPE_TAG_VOID: + if (type_info.is_pointer()) + return simple_getter_caller(obj, native_address, + out_arg); + return false; + case GI_TYPE_TAG_BOOLEAN: + return simple_getter_caller(obj, native_address, + out_arg); + case GI_TYPE_TAG_INT8: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_UINT8: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_INT16: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_UINT16: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_INT32: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_UINT32: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_INT64: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_UINT64: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_FLOAT: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_DOUBLE: + return simple_getter_caller(obj, native_address, out_arg); + case GI_TYPE_TAG_GTYPE: + return simple_getter_caller(obj, native_address, + out_arg); + case GI_TYPE_TAG_UNICHAR: + return simple_getter_caller(obj, native_address, out_arg); + + case GI_TYPE_TAG_INTERFACE: + { + GI::AutoBaseInfo interface_info{type_info.interface()}; + + if (interface_info.is_enum_or_flags()) { + return simple_getter_caller(obj, native_address, out_arg); + } + return simple_getter_caller(obj, native_address, out_arg); + } + + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + case GI_TYPE_TAG_GHASH: + case GI_TYPE_TAG_ERROR: + return simple_getter_caller(obj, native_address, out_arg); + } + + return false; +} + +bool ObjectInstance::prop_getter_impl(JSContext* cx, + ObjectPropertyInfoCaller* info_caller, + JS::CallArgs const& args) { + if (!check_gobject_finalized("get any property from")) { + args.rval().setUndefined(); + return true; + } + + const GI::AutoFunctionInfo& getter = info_caller->func_info; + GI::AutoPropertyInfo property_info{getter.property().value()}; + + if (property_info.has_deprecated_param_flag() || + property_info.is_deprecated() || getter.is_deprecated()) { + _gjs_warn_deprecated_once_per_callsite( + cx, DeprecatedGObjectProperty, + {format_name(), property_info.name()}); + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", + property_info.name()); + + GIArgument ret; + std::array gi_args; + gjs_arg_set(&gi_args[0], m_ptr.get()); + + GI::StackTypeInfo type_info; + getter.load_return_type(&type_info); + if (!simple_getters_caller(type_info, m_ptr, info_caller->native_address, + &ret)) { + const std::string& class_name = format_name(); + gjs_throw(cx, "Wrong type for %s::%s getter", class_name.c_str(), + property_info.name()); + return false; + } + + GITransfer transfer = getter.caller_owns(); + + if (!gjs_value_from_gi_argument(cx, args.rval(), type_info, + GJS_ARGUMENT_RETURN_VALUE, transfer, + &ret)) { + // Unlikely to happen, but we fallback to gvalue mode, just in case + JS_ClearPendingException(cx); + Gjs::AutoTypeClass klass{gtype()}; + GParamSpec* pspec = + g_object_class_find_property(klass, property_info.name()); + if (!pspec) { + const std::string& class_name = format_name(); + gjs_throw(cx, "Error converting value got from %s::%s getter", + class_name.c_str(), property_info.name()); + return false; + } + return prop_getter_impl(cx, pspec, args[0]); + } + + return gjs_gi_argument_release(cx, transfer, type_info, + GjsArgumentFlags::ARG_OUT, &ret); +} + +class ObjectPropertyPspecCaller { + public: + GParamSpec* pspec; + void* native_address; + + explicit ObjectPropertyPspecCaller(GParamSpec* param) + : pspec(param), native_address(nullptr) {} + + Gjs::GErrorResult<> init(const GI::FunctionInfo info) { + GIFunctionInvoker invoker; + MOZ_TRY(info.prep_invoker(&invoker)); + native_address = invoker.native_address; + gi_function_invoker_clear(&invoker); + return Ok{}; + } +}; + +template +bool ObjectBase::prop_getter_simple_type_func(JSContext* cx, unsigned argc, + JS::Value* vp) { + GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); + + JS::RootedObject pspec_obj( + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()); + auto* caller = + JS::ObjectGetStashedPointer(cx, pspec_obj); + + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + caller->pspec->name + "\"]")}; + AutoProfilerLabel label{cx, "property getter", full_name}; + + priv->debug_jsprop("Property getter", + gjs_intern_string_to_id(cx, caller->pspec->name), obj); + + // Ignore silently; note that this is different from what we do for + // boxed types, for historical reasons + if (priv->is_prototype()) + return true; + + return priv->to_instance()->prop_getter_impl(cx, caller, + args); +} + +template +bool ObjectInstance::prop_getter_impl(JSContext* cx, + ObjectPropertyPspecCaller* pspec_caller, + JS::CallArgs const& args) { + if (!check_gobject_finalized("get any property from")) { + args.rval().setUndefined(); + return true; + } - return retval; + if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) { + _gjs_warn_deprecated_once_per_callsite( + cx, DeprecatedGObjectProperty, + {format_name(), pspec_caller->pspec->name}); + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", + pspec_caller->pspec->name); + + using T = Gjs::Tag::RealT; + using FuncType = T (*)(GObject*); + FuncType func = reinterpret_cast(pspec_caller->native_address); + T retval = func(m_ptr); + if (!Gjs::c_value_to_js_checked(cx, retval, args.rval())) + return false; + + if constexpr (TRANSFER != GI_TRANSFER_NOTHING) { + static_assert(std::is_same_v, "Unexpected type to release"); + g_free(retval); + } + + return true; +} + +[[nodiscard]] +static Maybe lookup_field_info(const GI::ObjectInfo info, + const char* name) { + for (GI::AutoFieldInfo retval : info.fields()) { + if (strcmp(name, retval.name()) == 0) + return Some(retval); + } + return {}; } bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); - JS::RootedString name(cx, - gjs_dynamic_property_private_slot(&args.callee()).toString()); + JS::RootedObject field_info_obj{ + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; + auto const& field_info = + *JS::ObjectGetStashedPointer(cx, field_info_obj); - std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + - "]"}; - AutoProfilerLabel label(cx, "field getter", fullName.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + field_info.name() + "\"]")}; + AutoProfilerLabel label{cx, "field getter", full_name}; - priv->debug_jsprop("Field getter", name, obj); + priv->debug_jsprop("Field getter", field_info.name(), obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ - return priv->to_instance()->field_getter_impl(cx, name, args.rval()); + return priv->to_instance()->field_getter_impl(cx, field_info, args.rval()); } -bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, +bool ObjectInstance::field_getter_impl(JSContext* cx, + GI::AutoFieldInfo const& field, JS::MutableHandleValue rval) { if (!check_gobject_finalized("get any property from")) return true; - ObjectPrototype* proto_priv = get_prototype(); - GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); - GITypeTag tag; GIArgument arg = { 0 }; gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field", - gjs_debug_string(name).c_str()); + field.name()); - GjsAutoTypeInfo type = g_field_info_get_type(field); - tag = g_type_info_get_tag(type); - - switch (tag) { + GI::AutoTypeInfo type{field.type_info()}; + switch (type.tag()) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_GHASH: @@ -395,17 +646,15 @@ bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, gjs_throw(cx, "Can't get field %s; GObject introspection supports only " "fields with simple types, not %s", - gjs_debug_string(name).c_str(), - g_type_tag_to_string(tag)); + field.name(), type.display_string()); return false; default: break; } - if (!g_field_info_get_field(field, m_ptr, &arg)) { - gjs_throw(cx, "Error getting field %s from object", - gjs_debug_string(name).c_str()); + if (field.read(m_ptr, &arg).isErr()) { + gjs_throw(cx, "Error getting field %s from object", field.name()); return false; } @@ -417,14 +666,16 @@ bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, /* Dynamic setter for GObject properties. Returns false on OOM/exception. * args.rval() becomes the "stored value" for the property. */ +template bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); auto* pspec = static_cast( gjs_dynamic_property_private_slot(&args.callee()).toPrivate()); - std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"}; - AutoProfilerLabel label(cx, "property setter", fullName.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + pspec->name + "\"]")}; + AutoProfilerLabel label{cx, "property setter", full_name}; priv->debug_jsprop("Property setter", pspec->name, obj); @@ -436,48 +687,325 @@ bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) { /* Clear the JS stored value, to avoid keeping additional references */ args.rval().setUndefined(); - return priv->to_instance()->prop_setter_impl(cx, pspec, args[0]); + return priv->to_instance()->prop_setter_impl(cx, pspec, args[0]); } +template bool ObjectInstance::prop_setter_impl(JSContext* cx, GParamSpec* param_spec, JS::HandleValue value) { if (!check_gobject_finalized("set any property on")) return true; - if (!(param_spec->flags & G_PARAM_WRITABLE)) - /* 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) { - const std::string& class_name = format_name(); _gjs_warn_deprecated_once_per_callsite( - cx, DeprecatedGObjectProperty, - {class_name.c_str(), param_spec->name}); + cx, DeprecatedGObjectProperty, {format_name(), param_spec->name}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s", param_spec->name); Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param_spec)); - if (!gjs_value_to_g_value(cx, value, &gvalue)) - return false; + + using T = Gjs::Tag::RealT; + if constexpr (std::is_same_v) { + if (!gjs_value_to_g_value(cx, value, &gvalue)) + return false; + } else if constexpr (std::is_arithmetic_v< // NOLINT(readability/braces) + T> && + !Gjs::type_has_js_getter()) { + bool out_of_range = false; + + Gjs::Tag::JSValuePackT val{}; + using HolderTag = Gjs::Tag::JSValuePackTag; + if (!Gjs::js_value_to_c_checked(cx, value, &val, + &out_of_range)) { + gjs_throw(cx, "Can't convert value %s to set %s::%s property", + gjs_debug_value(value).c_str(), format_name().c_str(), + param_spec->name); + return false; + } + + if (out_of_range) { + gjs_throw(cx, "value %s is out of range for %s (type %s)", + std::to_string(val).c_str(), param_spec->name, + Gjs::static_type_name()); + return false; + } + + Gjs::gvalue_set(&gvalue, val); + } else { + T native_value; + if (!Gjs::js_value_to_c(cx, value, &native_value)) { + gjs_throw(cx, "Can't convert %s value to set %s::%s property", + gjs_debug_value(value).c_str(), format_name().c_str(), + param_spec->name); + return false; + } + + if constexpr (std::is_pointer_v) { + Gjs::gvalue_take(&gvalue, g_steal_pointer(&native_value)); + } else { + Gjs::gvalue_set(&gvalue, native_value); + } + } g_object_set_property(m_ptr, param_spec->name, &gvalue); return true; } +bool ObjectBase::prop_setter_read_only(JSContext* cx, unsigned argc, + JS::Value* vp) { + GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); + auto* pspec = static_cast( + gjs_dynamic_property_private_slot(&args.callee()).toPrivate()); + // Prevent setting the property even in JS + return gjs_wrapper_throw_readonly_field(cx, priv->to_instance()->gtype(), + pspec->name); +} + +bool ObjectBase::prop_setter_func(JSContext* cx, unsigned argc, JS::Value* vp) { + GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); + + JS::RootedObject func_obj{ + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; + auto* info_caller = + JS::ObjectGetStashedPointer(cx, func_obj); + + const GI::AutoFunctionInfo& func_info = info_caller->func_info; + GI::AutoPropertyInfo property_info{func_info.property().value()}; + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + property_info.name() + "\"]")}; + AutoProfilerLabel label{cx, "property setter", full_name}; + + priv->debug_jsprop("Property setter", property_info.name(), obj); + + // Ignore silently; note that this is different from what we do for + // boxed types, for historical reasons + if (priv->is_prototype()) + return true; + + return priv->to_instance()->prop_setter_impl(cx, info_caller, args); +} + +template +[[nodiscard]] +static bool simple_setter_caller(GIArgument* arg, GObject* obj, + void* native_address) { + using FuncType = void (*)(GObject*, Gjs::Tag::RealT); + FuncType func = reinterpret_cast(native_address); + + func(obj, gjs_arg_get(arg)); + return true; +} + +[[nodiscard]] +static bool simple_setters_caller(const GI::TypeInfo type_info, GIArgument* arg, + GObject* obj, void* native_address) { + switch (type_info.tag()) { + case GI_TYPE_TAG_VOID: + if (type_info.is_pointer()) + return simple_setter_caller(arg, obj, native_address); + return false; + case GI_TYPE_TAG_BOOLEAN: + return simple_setter_caller(arg, obj, + native_address); + case GI_TYPE_TAG_INT8: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_UINT8: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_INT16: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_UINT16: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_INT32: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_UINT32: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_INT64: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_UINT64: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_FLOAT: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_DOUBLE: + return simple_setter_caller(arg, obj, native_address); + case GI_TYPE_TAG_GTYPE: + return simple_setter_caller(arg, obj, + native_address); + case GI_TYPE_TAG_UNICHAR: + return simple_setter_caller(arg, obj, native_address); + + case GI_TYPE_TAG_INTERFACE: + { + GI::AutoBaseInfo interface_info{type_info.interface()}; + + if (interface_info.is_enum_or_flags()) { + return simple_setter_caller(arg, obj, native_address); + } + return simple_setter_caller(arg, obj, native_address); + } + + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + case GI_TYPE_TAG_GHASH: + case GI_TYPE_TAG_ERROR: + return simple_setter_caller(arg, obj, native_address); + } + + return false; +} + +bool ObjectInstance::prop_setter_impl(JSContext* cx, + ObjectPropertyInfoCaller* info_caller, + JS::CallArgs const& args) { + if (!check_gobject_finalized("set any property on")) + return true; + + const GI::AutoFunctionInfo& setter = info_caller->func_info; + GI::AutoPropertyInfo property_info{setter.property().value()}; + + if (property_info.has_deprecated_param_flag() || + property_info.is_deprecated() || setter.is_deprecated()) { + _gjs_warn_deprecated_once_per_callsite( + cx, DeprecatedGObjectProperty, + {format_name(), property_info.name()}); + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s", + property_info.name()); + + GI::StackArgInfo arg_info; + setter.load_arg(0, &arg_info); + GI::StackTypeInfo type_info; + arg_info.load_type(&type_info); + GITransfer transfer = arg_info.ownership_transfer(); + JS::RootedValue value{cx, args[0]}; + GIArgument arg; + + if (!gjs_value_to_gi_argument(cx, value, type_info, property_info.name(), + GJS_ARGUMENT_ARGUMENT, transfer, + GjsArgumentFlags::ARG_IN, &arg)) { + // Unlikely to happen, but we fallback to gvalue mode, just in case + JS_ClearPendingException(cx); + Gjs::AutoTypeClass klass{gtype()}; + GParamSpec* pspec = + g_object_class_find_property(klass, property_info.name()); + if (!pspec) { + const std::string& class_name = format_name(); + gjs_throw(cx, "Error converting value to call %s::%s setter", + class_name.c_str(), property_info.name()); + return false; + } + return prop_setter_impl(cx, pspec, value); + } + + if (!simple_setters_caller(type_info, &arg, m_ptr, + info_caller->native_address)) { + const std::string& class_name = format_name(); + gjs_throw(cx, "Wrong type for %s::%s setter", class_name.c_str(), + property_info.name()); + return false; + } + + return gjs_gi_argument_release_in_arg(cx, transfer, type_info, &arg); +} + +template +bool ObjectBase::prop_setter_simple_type_func(JSContext* cx, unsigned argc, + JS::Value* vp) { + GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); + + JS::RootedObject pspec_obj( + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()); + auto* caller = + JS::ObjectGetStashedPointer(cx, pspec_obj); + + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[" + caller->pspec->name + "]")}; + AutoProfilerLabel label{cx, "property setter", full_name}; + + priv->debug_jsprop("Property setter", caller->pspec->name, obj); + + // Ignore silently; note that this is different from what we do for + // boxed types, for historical reasons + if (priv->is_prototype()) + return true; + + return priv->to_instance()->prop_setter_impl(cx, caller, + args); +} + +template +bool ObjectInstance::prop_setter_impl(JSContext* cx, + ObjectPropertyPspecCaller* pspec_caller, + JS::CallArgs const& args) { + if (!check_gobject_finalized("set any property on")) + return true; + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s", + pspec_caller->pspec->name); + + if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) { + _gjs_warn_deprecated_once_per_callsite( + cx, DeprecatedGObjectProperty, + {format_name(), pspec_caller->pspec->name}); + } + + using T = Gjs::Tag::RealT; + using FuncType = void (*)(GObject*, T); + FuncType func = reinterpret_cast(pspec_caller->native_address); + + if constexpr (std::is_arithmetic_v && !Gjs::type_has_js_getter()) { + bool out_of_range = false; + + Gjs::Tag::JSValuePackT native_value{}; + using HolderTag = Gjs::Tag::JSValuePackTag; + if (!Gjs::js_value_to_c_checked( + cx, args[0], &native_value, &out_of_range)) + return false; + + if (out_of_range) { + gjs_throw(cx, "value %s is out of range for %s (type %s)", + std::to_string(native_value).c_str(), + pspec_caller->pspec->name, Gjs::static_type_name()); + return false; + } + + func(m_ptr, native_value); + } else { + T native_value; + if (!Gjs::js_value_to_c(cx, args[0], &native_value)) + return false; + + func(m_ptr, native_value); + + if constexpr (TRANSFER == GI_TRANSFER_NOTHING && std::is_pointer_v) { + static_assert(std::is_same_v, + "Unexpected type to release"); + g_free(native_value); + } + } + + return true; +} + bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); - JS::RootedString name(cx, - gjs_dynamic_property_private_slot(&args.callee()).toString()); + JS::RootedObject field_info_obj{ + cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; + auto const& field_info = + *JS::ObjectGetStashedPointer(cx, field_info_obj); - std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + - "]"}; - AutoProfilerLabel label(cx, "field setter", fullName.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + cx, priv->format_name() + "[\"" + field_info.name() + "\"]")}; + AutoProfilerLabel label{cx, "field setter", full_name}; - priv->debug_jsprop("Field setter", name, obj); + priv->debug_jsprop("Field setter", field_info.name(), obj); if (priv->is_prototype()) return true; @@ -490,62 +1018,60 @@ bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { * the field */ args.rval().setUndefined(); - return priv->to_instance()->field_setter_not_impl(cx, name); + return priv->to_instance()->field_setter_not_impl(cx, field_info); } bool ObjectInstance::field_setter_not_impl(JSContext* cx, - JS::HandleString name) { + GI::AutoFieldInfo const& field) { if (!check_gobject_finalized("set GObject field on")) return true; - ObjectPrototype* proto_priv = get_prototype(); - GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); - /* As far as I know, GI never exposes GObject instance struct fields as * writable, so no need to implement this for the time being */ - if (g_field_info_get_flags(field) & GI_FIELD_IS_WRITABLE) { - g_message("Field %s of a GObject is writable, but setting it is not " - "implemented", gjs_debug_string(name).c_str()); + if (field.is_writable()) { + g_message( + "Field %s of a GObject is writable, but setting it is not " + "implemented", + field.name()); return true; } - return gjs_wrapper_throw_readonly_field(cx, gtype(), - g_base_info_get_name(field)); + return gjs_wrapper_throw_readonly_field(cx, gtype(), field.name()); } -bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) { - GjsAutoError error; +bool ObjectPrototype::is_vfunc_unchanged(const GI::VFuncInfo info) const { GType ptype = g_type_parent(m_gtype); - gpointer addr1, addr2; - addr1 = g_vfunc_info_get_address(info, m_gtype, &error); - if (error) + Gjs::GErrorResult addr1 = info.address(m_gtype); + if (addr1.isErr()) return false; - addr2 = g_vfunc_info_get_address(info, ptype, &error); - if (error) + Gjs::GErrorResult addr2 = info.address(ptype); + if (addr2.isErr()) return false; - return addr1 == addr2; + return addr1.unwrap() == addr2.unwrap(); } -[[nodiscard]] static GjsAutoVFuncInfo find_vfunc_on_parents( - GIObjectInfo* info, const char* name, bool* out_defined_by_parent) { +[[nodiscard]] +static Maybe find_vfunc_on_parents( + const GI::ObjectInfo info, const char* name, bool* out_defined_by_parent) { bool defined_by_parent = false; /* ref the first info so that we don't destroy * it when unrefing parents later */ - GjsAutoObjectInfo parent(info, GjsAutoTakeOwnership()); + Maybe parent{Some(info)}; /* Since it isn't possible to override a vfunc on * an interface without reimplementing it, we don't need * to search the parent types when looking for a vfunc. */ - GjsAutoVFuncInfo vfunc = - g_object_info_find_vfunc_using_interfaces(parent, name, nullptr); + Maybe vfunc = + parent->find_vfunc_using_interfaces(name).map( + [](auto&& pair) { return std::move(pair.first); }); while (!vfunc && parent) { - parent = g_object_info_get_parent(parent); + parent = parent->parent(); if (parent) - vfunc = g_object_info_find_vfunc(parent, name); + vfunc = parent->vfunc(name); defined_by_parent = true; } @@ -557,7 +1083,7 @@ bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) { } /* Taken from GLib */ -static void canonicalize_key(const GjsAutoChar& key) { +static void canonicalize_key(const Gjs::AutoChar& key) { for (char* p = key; *p != 0; p++) { char c = *p; @@ -568,36 +1094,463 @@ static void canonicalize_key(const GjsAutoChar& key) { } /* @name must already be canonicalized */ -[[nodiscard]] static bool is_ginterface_property_name(GIInterfaceInfo* info, - const char* name) { - int n_props = g_interface_info_get_n_properties(info); - GjsAutoPropertyInfo prop_info; +[[nodiscard]] +static Maybe get_ginterface_property_by_name( + const GI::InterfaceInfo info, const char* name) { + for (GI::AutoPropertyInfo prop_info : info.properties()) { + if (strcmp(name, prop_info.name()) == 0) + return Some(std::move(prop_info)); + } - for (int ix = 0; ix < n_props; ix++) { - prop_info = g_interface_info_get_property(info, ix); + return {}; +} + +[[nodiscard]] +static Maybe get_gobject_property_info( + const GI::ObjectInfo info, const char* name) { + for (GI::AutoPropertyInfo prop_info : info.properties()) { if (strcmp(name, prop_info.name()) == 0) - break; - prop_info.reset(); + return Some(std::move(prop_info)); + } + + for (GI::AutoInterfaceInfo iface_info : info.interfaces()) { + if (Maybe prop_info = + get_ginterface_property_by_name(iface_info, name)) + return prop_info; + } + return {}; +} + +[[nodiscard]] +static JSNative get_getter_for_type(const GI::TypeInfo type_info, + GITransfer transfer) { + switch (type_info.tag()) { + case GI_TYPE_TAG_BOOLEAN: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_INT8: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_UINT8: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_INT16: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_UINT16: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_INT32: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_UINT32: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_INT64: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_UINT64: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_FLOAT: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_DOUBLE: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_GTYPE: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_UNICHAR: + return ObjectBase::prop_getter_simple_type_func; + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_UTF8: + if (transfer == GI_TRANSFER_NOTHING) { + return ObjectBase::prop_getter_simple_type_func< + const char*, GI_TRANSFER_NOTHING>; + } else { + return ObjectBase::prop_getter_simple_type_func< + char*, GI_TRANSFER_EVERYTHING>; + } + default: + return nullptr; } +} - return !!prop_info; +[[nodiscard]] static JSNative get_setter_for_type(const GI::TypeInfo type_info, + GITransfer transfer) { + switch (type_info.tag()) { + case GI_TYPE_TAG_BOOLEAN: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_INT8: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_UINT8: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_INT16: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_UINT16: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_INT32: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_UINT32: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_INT64: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_UINT64: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_FLOAT: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_DOUBLE: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_GTYPE: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_UNICHAR: + return ObjectBase::prop_setter_simple_type_func; + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_UTF8: + if (transfer == GI_TRANSFER_NOTHING) { + return ObjectBase::prop_setter_simple_type_func< + char*, GI_TRANSFER_NOTHING>; + } else { + return ObjectBase::prop_setter_simple_type_func< + char*, GI_TRANSFER_EVERYTHING>; + } + default: + return nullptr; + } +} + +// Wrap a call to JS::NewObjectWithStashedPointer() while ensuring the pointer +// is properly deleted if the call fails. +template +GJS_JSAPI_RETURN_CONVENTION static inline JSObject* +new_object_with_stashed_pointer(JSContext* cx, Ts... args) { + std::unique_ptr data = std::make_unique(args...); + JSObject* obj = JS::NewObjectWithStashedPointer( + cx, data.get(), [](T* data) { delete data; }); + if (obj) + data.release(); + return obj; +} + +GJS_JSAPI_RETURN_CONVENTION +static JSNative create_getter_invoker(JSContext* cx, GParamSpec* pspec, + const GI::FunctionInfo getter, + const GI::TypeInfo type, + JS::MutableHandleValue wrapper_out) { + JS::RootedObject wrapper{cx}; + + GITransfer transfer = getter.caller_owns(); + JSNative js_getter = get_getter_for_type(type, transfer); + + Gjs::GErrorResult<> init_result{Ok{}}; + if (js_getter) { + wrapper = new_object_with_stashed_pointer( + cx, pspec); + if (!wrapper) + return nullptr; + auto* caller = + JS::ObjectGetStashedPointer(cx, wrapper); + init_result = caller->init(getter); + } else { + wrapper = new_object_with_stashed_pointer( + cx, getter); + if (!wrapper) + return nullptr; + js_getter = &ObjectBase::prop_getter_func; + auto* caller = + JS::ObjectGetStashedPointer(cx, wrapper); + init_result = caller->init(); + } + + if (init_result.isErr()) { + gjs_throw(cx, "Impossible to create invoker for %s: %s", getter.name(), + init_result.inspectErr()->message); + return nullptr; + } + + wrapper_out.setObject(*wrapper); + return js_getter; +} + +// We cannot use g_base_info_equal because the GITypeInfo of properties is +// not marked as a pointer in GIR files, while it is marked as a pointer in the +// return type of the associated getter, or the argument type of the associated +// setter. Also, there isn't a GParamSpec for integers of specific widths, there +// is only int and long, whereas the corresponding getter may return a specific +// width of integer. +[[nodiscard]] +static bool type_info_compatible(const GI::TypeInfo func_type, + const GI::TypeInfo prop_type) { + GITypeTag tag = prop_type.tag(); + GITypeTag func_tag = func_type.tag(); + + if (GI_TYPE_TAG_IS_BASIC(tag)) { + if (func_type.is_pointer() != prop_type.is_pointer()) + return false; + } + switch (tag) { + case GI_TYPE_TAG_VOID: // g_param_spec_param + case GI_TYPE_TAG_BOOLEAN: // g_param_spec_boolean + case GI_TYPE_TAG_INT8: // g_param_spec_char + case GI_TYPE_TAG_DOUBLE: // g_param_spec_double + case GI_TYPE_TAG_FLOAT: // g_param_spec_float + case GI_TYPE_TAG_GTYPE: // g_param_spec_gtype + case GI_TYPE_TAG_UINT8: // g_param_spec_uchar + case GI_TYPE_TAG_UNICHAR: // g_param_spec_unichar + case GI_TYPE_TAG_ERROR: // would be g_param_spec_boxed? + return func_tag == tag; + case GI_TYPE_TAG_INT32: + case GI_TYPE_TAG_INT64: + // g_param_spec_int, g_param_spec_long, or g_param_spec_int64 + return func_tag == GI_TYPE_TAG_INT8 || + func_tag == GI_TYPE_TAG_INT16 || + func_tag == GI_TYPE_TAG_INT32 || + func_tag == GI_TYPE_TAG_INT64; + case GI_TYPE_TAG_UINT32: + case GI_TYPE_TAG_UINT64: + // g_param_spec_uint, g_param_spec_ulong, or g_param_spec_uint64 + return func_tag == GI_TYPE_TAG_UINT8 || + func_tag == GI_TYPE_TAG_UINT16 || + func_tag == GI_TYPE_TAG_UINT32 || + func_tag == GI_TYPE_TAG_UINT64; + case GI_TYPE_TAG_UTF8: // g_param_spec_string + return func_tag == tag || func_tag == GI_TYPE_TAG_FILENAME; + case GI_TYPE_TAG_INT16: + case GI_TYPE_TAG_UINT16: + case GI_TYPE_TAG_FILENAME: + g_return_val_if_reached(false); // never occurs as GParamSpec type + // everything else + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + return func_tag == tag && + func_type.element_type() == prop_type.element_type(); + case GI_TYPE_TAG_ARRAY: + return func_tag == tag && + func_type.element_type() == prop_type.element_type() && + func_type.is_zero_terminated() == + prop_type.is_zero_terminated() && + func_type.array_fixed_size() == + prop_type.array_fixed_size() && + func_type.array_type() == prop_type.array_type(); + case GI_TYPE_TAG_GHASH: + return func_tag == tag && + func_type.key_type() == prop_type.key_type() && + func_type.value_type() == prop_type.value_type(); + case GI_TYPE_TAG_INTERFACE: + return func_tag == tag && + func_type.interface() == prop_type.interface(); + } + g_return_val_if_reached(false); +} + +GJS_JSAPI_RETURN_CONVENTION +static JSNative get_getter_for_property( + JSContext* cx, GParamSpec* pspec, Maybe property_info, + JS::MutableHandleValue priv_out) { + if (!(pspec->flags & G_PARAM_READABLE)) { + priv_out.setUndefined(); + return &ObjectBase::prop_getter_write_only; + } + + if (property_info) { + Maybe prop_getter{property_info->getter()}; + + if (prop_getter && prop_getter->is_method() && + prop_getter->n_args() == 0 && !prop_getter->skip_return()) { + GI::StackTypeInfo return_type; + prop_getter->load_return_type(&return_type); + GI::AutoTypeInfo prop_type{property_info->type_info()}; + + if (G_LIKELY(type_info_compatible(return_type, prop_type))) { + return create_getter_invoker(cx, pspec, *prop_getter, + return_type, priv_out); + } else { + Maybe container = prop_getter->container(); + g_warning( + "Type %s of property %s.%s::%s does not match return type " + "%s of getter %s. Falling back to slow path", + prop_type.type_string(), container->ns(), container->name(), + property_info->name(), return_type.type_string(), + prop_getter->name()); + // fall back to GValue below + } + } + } + + priv_out.setPrivate(pspec); + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + return &ObjectBase::prop_getter; + case G_TYPE_INT: + return &ObjectBase::prop_getter; + case G_TYPE_UINT: + return &ObjectBase::prop_getter; + case G_TYPE_CHAR: + return &ObjectBase::prop_getter; + case G_TYPE_UCHAR: + return &ObjectBase::prop_getter; + case G_TYPE_INT64: + return &ObjectBase::prop_getter; + case G_TYPE_UINT64: + return &ObjectBase::prop_getter; + case G_TYPE_FLOAT: + return &ObjectBase::prop_getter; + case G_TYPE_DOUBLE: + return &ObjectBase::prop_getter; + case G_TYPE_STRING: + return &ObjectBase::prop_getter; + case G_TYPE_LONG: + return &ObjectBase::prop_getter; + case G_TYPE_ULONG: + return &ObjectBase::prop_getter; + default: + return &ObjectBase::prop_getter<>; + } +} + +GJS_JSAPI_RETURN_CONVENTION +static JSNative create_setter_invoker(JSContext* cx, GParamSpec* pspec, + const GI::FunctionInfo setter, + const GI::ArgInfo value_arg, + const GI::TypeInfo type, + JS::MutableHandleValue wrapper_out) { + JS::RootedObject wrapper{cx}; + + GITransfer transfer = value_arg.ownership_transfer(); + JSNative js_setter = get_setter_for_type(type, transfer); + + Gjs::GErrorResult<> init_result{Ok{}}; + if (js_setter) { + wrapper = new_object_with_stashed_pointer( + cx, pspec); + if (!wrapper) + return nullptr; + auto* caller = + JS::ObjectGetStashedPointer(cx, wrapper); + init_result = caller->init(setter); + } else { + wrapper = new_object_with_stashed_pointer( + cx, setter); + if (!wrapper) + return nullptr; + js_setter = &ObjectBase::prop_setter_func; + auto* caller = + JS::ObjectGetStashedPointer(cx, wrapper); + init_result = caller->init(); + } + + if (init_result.isErr()) { + gjs_throw(cx, "Impossible to create invoker for %s: %s", setter.name(), + init_result.inspectErr()->message); + return nullptr; + } + + wrapper_out.setObject(*wrapper); + return js_setter; +} + +GJS_JSAPI_RETURN_CONVENTION +static JSNative get_setter_for_property( + JSContext* cx, GParamSpec* pspec, Maybe property_info, + JS::MutableHandleValue priv_out) { + if (!(pspec->flags & G_PARAM_WRITABLE)) { + priv_out.setPrivate(pspec); + return &ObjectBase::prop_setter_read_only; + } + + if (property_info) { + Maybe prop_setter{property_info->setter()}; + + if (prop_setter && prop_setter->is_method() && + prop_setter->n_args() == 1) { + GI::StackArgInfo value_arg; + prop_setter->load_arg(0, &value_arg); + GI::StackTypeInfo type_info; + value_arg.load_type(&type_info); + GI::AutoTypeInfo prop_type{property_info->type_info()}; + + if (G_LIKELY(type_info_compatible(type_info, prop_type))) { + return create_setter_invoker(cx, pspec, *prop_setter, value_arg, + type_info, priv_out); + } else { + Maybe container = prop_setter->container(); + g_warning( + "Type %s of property %s.%s::%s does not match type %s of " + "first argument of setter %s. Falling back to slow path", + prop_type.type_string(), container->ns(), container->name(), + property_info->name(), type_info.type_string(), + prop_setter->name()); + // fall back to GValue below + } + } + } + + priv_out.setPrivate(pspec); + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + return &ObjectBase::prop_setter; + case G_TYPE_INT: + return &ObjectBase::prop_setter; + case G_TYPE_UINT: + return &ObjectBase::prop_setter; + case G_TYPE_CHAR: + return &ObjectBase::prop_setter; + case G_TYPE_UCHAR: + return &ObjectBase::prop_setter; + case G_TYPE_INT64: + return &ObjectBase::prop_setter; + case G_TYPE_UINT64: + return &ObjectBase::prop_setter; + case G_TYPE_FLOAT: + return &ObjectBase::prop_setter; + case G_TYPE_DOUBLE: + return &ObjectBase::prop_setter; + case G_TYPE_STRING: + return &ObjectBase::prop_setter; + case G_TYPE_LONG: + return &ObjectBase::prop_setter; + case G_TYPE_ULONG: + return &ObjectBase::prop_setter; + default: + return &ObjectBase::prop_setter<>; + } } bool ObjectPrototype::lazy_define_gobject_property( JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec* pspec, - bool* resolved, const char* name) { - bool found = false; - if (!JS_AlreadyHasOwnPropertyById(cx, obj, id, &found)) - return false; - if (found) { - /* Already defined, so *resolved = false because we didn't just - * define it */ - *resolved = false; - return true; + bool* resolved, const char* name, + Maybe property_info) { + JS::RootedId canonical_id{cx}; + JS::Rooted canonical_desc{cx}; + + // Make property configurable so that interface properties can be + // overridden by GObject.ParamSpec.override in the class that + // implements them + unsigned flags = GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT; + + if (!g_str_equal(pspec->name, name)) { + canonical_id = gjs_intern_string_to_id(cx, pspec->name); + + JS::Rooted> desc{cx}; + if (!JS_GetOwnPropertyDescriptorById(cx, obj, canonical_id, &desc)) + return false; + + if (desc.isSome()) { + debug_jsprop("Defining alias GObject property", id, obj); + canonical_desc = *desc; + if (!JS_DefinePropertyById(cx, obj, id, canonical_desc)) + return false; + + *resolved = true; + return true; + } } debug_jsprop("Defining lazy GObject property", id, obj); + if (!(pspec->flags & (G_PARAM_WRITABLE | G_PARAM_READABLE))) { + if (!JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue, + flags)) + return false; + + if (!canonical_id.isVoid() && + !JS_DefinePropertyById(cx, obj, canonical_id, + JS::UndefinedHandleValue, flags)) + return false; + + *resolved = true; + return true; + } + // Do not fetch JS overridden properties from GObject, to avoid // infinite recursion. if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) { @@ -605,16 +1558,30 @@ bool ObjectPrototype::lazy_define_gobject_property( return true; } - JS::RootedValue private_value{cx, JS::PrivateValue(pspec)}; - if (!gjs_define_property_dynamic( - cx, obj, name, id, "gobject_prop", &ObjectBase::prop_getter, - &ObjectBase::prop_setter, private_value, - // Make property configurable so that interface properties can be - // overridden by GObject.ParamSpec.override in the class that - // implements them - GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) + JS::RootedValue getter_priv{cx}; + JSNative js_getter = + get_getter_for_property(cx, pspec, property_info, &getter_priv); + if (!js_getter) + return false; + + JS::RootedValue setter_priv{cx}; + JSNative js_setter = + get_setter_for_property(cx, pspec, property_info, &setter_priv); + if (!js_setter) return false; + if (!gjs_define_property_dynamic(cx, obj, name, id, "gobject_prop", + js_getter, getter_priv, js_setter, + setter_priv, flags)) + return false; + + if G_UNLIKELY (!canonical_id.isVoid()) { + debug_jsprop("Defining alias GObject property", canonical_id, obj); + + if (!JS_DefinePropertyById(cx, obj, canonical_id, canonical_desc)) + return false; + } + *resolved = true; return true; } @@ -700,13 +1667,13 @@ static bool interface_setter(JSContext* cx, unsigned argc, JS::Value* vp) { } static bool resolve_on_interface_prototype(JSContext* cx, - GIInterfaceInfo* iface_info, + const GI::InterfaceInfo iface_info, JS::HandleId identifier, JS::HandleObject class_prototype, bool* found) { - GType gtype = g_base_info_get_type(iface_info); JS::RootedObject interface_prototype( - cx, gjs_lookup_object_prototype_from_info(cx, iface_info, gtype)); + cx, gjs_lookup_object_prototype_from_info(cx, Some(iface_info), + iface_info.gtype())); if (!interface_prototype) return false; @@ -780,10 +1747,7 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name, ResolveWhat resolve_props) { - guint n_interfaces; - guint i; - - GjsAutoChar canonical_name; + Gjs::AutoChar canonical_name; if (resolve_props == ConsiderMethodsAndProperties) { // Optimization: GObject property names must start with a letter if (g_ascii_isalpha(name[0])) { @@ -792,105 +1756,58 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj, } } - GIInterfaceInfo** interfaces; - g_irepository_get_object_gtype_interfaces(nullptr, m_gtype, &n_interfaces, - &interfaces); + mozilla::Span interfaces = + GI::Repository{}.object_get_gtype_interfaces(m_gtype); /* Fallback to GType system for non custom GObjects with no GI information */ if (canonical_name && G_TYPE_IS_CLASSED(m_gtype) && !is_custom_js_class()) { - GjsAutoTypeClass oclass(m_gtype); + Gjs::AutoTypeClass oclass{m_gtype}; if (GParamSpec* pspec = g_object_class_find_property(oclass, canonical_name)) return lazy_define_gobject_property(cx, obj, id, pspec, resolved, name); - - for (i = 0; i < n_interfaces; i++) { - GType iface_gtype = - g_registered_type_info_get_g_type(interfaces[i]); - if (!G_TYPE_IS_CLASSED(iface_gtype)) - continue; - - GjsAutoTypeClass iclass(iface_gtype); - - if (GParamSpec* pspec = - g_object_class_find_property(iclass, canonical_name)) - return lazy_define_gobject_property(cx, obj, id, pspec, - resolved, name); - } } - for (i = 0; i < n_interfaces; i++) { - GIInterfaceInfo* iface_info = interfaces[i]; - GjsAutoFunctionInfo method_info = - g_interface_info_find_method(iface_info, name); - if (method_info) { - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { - bool found = false; - if (!resolve_on_interface_prototype(cx, iface_info, id, obj, - &found)) - return false; - - // Fallback to defining the function from type info... - if (!found && - !gjs_define_function(cx, obj, m_gtype, method_info)) - return false; - - *resolved = true; - return true; - } - } + for (const GI::InterfaceInfo& iface_info : interfaces) { + Maybe method_info{iface_info.method(name)}; + if (method_info && method_info->is_method()) { + bool found = false; + if (!resolve_on_interface_prototype(cx, iface_info, id, obj, + &found)) + return false; + // Fallback to defining the function from type info... + if (!found && + !gjs_define_function(cx, obj, m_gtype, method_info.ref())) + return false; - /* If the name refers to a GObject property, lazily define the property - * in JS as we do below in the real resolve hook. We ignore fields here - * because I don't think interfaces can have fields */ - if (canonical_name && - is_ginterface_property_name(iface_info, canonical_name)) { - GjsAutoTypeClass oclass(m_gtype); - // unowned - GParamSpec* pspec = g_object_class_find_property( - oclass, canonical_name); // unowned - if (pspec && pspec->owner_type == m_gtype) { - return lazy_define_gobject_property(cx, obj, id, pspec, - resolved, name); - } + *resolved = true; + return true; } - return resolve_on_interface_prototype(cx, iface_info, id, obj, - resolved); + if (!resolve_on_interface_prototype(cx, iface_info, id, obj, resolved)) + return false; + if (*resolved) + return true; } *resolved = false; return true; } -[[nodiscard]] static GjsAutoChar get_gobject_property_name(GIObjectInfo* info, - const char* name) { +[[nodiscard]] +static Maybe find_gobject_property_info( + const GI::ObjectInfo info, const char* name) { // Optimization: GObject property names must start with a letter if (!g_ascii_isalpha(name[0])) - return nullptr; + return {}; - int n_props = g_object_info_get_n_properties(info); - int n_ifaces = g_object_info_get_n_interfaces(info); - int ix; - - GjsAutoChar canonical_name = gjs_hyphen_from_camel(name); + Gjs::AutoChar canonical_name{gjs_hyphen_from_camel(name)}; canonicalize_key(canonical_name); - for (ix = 0; ix < n_props; ix++) { - GjsAutoPropertyInfo prop_info = g_object_info_get_property(info, ix); - if (strcmp(canonical_name, prop_info.name()) == 0) - return canonical_name; - } - - for (ix = 0; ix < n_ifaces; ix++) { - GjsAutoInterfaceInfo iface_info = g_object_info_get_interface(info, ix); - if (is_ginterface_property_name(iface_info, canonical_name)) - return canonical_name; - } - return nullptr; + return get_gobject_property_info(info, canonical_name); } // Override of GIWrapperBase::id_is_never_lazy() @@ -932,6 +1849,17 @@ bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved) { + bool found = false; + if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found)) + return false; + + if (found) { + // Already defined, so *resolved = false because we didn't just define + // it + *resolved = false; + return true; + } + // If we have no GIRepository information (we're a JS GObject subclass or an // internal non-introspected class such as GLocalFile), we need to look at // exposing interfaces. Look up our interfaces through GType data, and then @@ -957,17 +1885,17 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, const char *name_without_vfunc_ = &(name[6]); /* lifetime tied to name */ bool defined_by_parent; - GjsAutoVFuncInfo vfunc = find_vfunc_on_parents( - m_info, name_without_vfunc_, &defined_by_parent); + Maybe vfunc{find_vfunc_on_parents( + m_info.ref(), name_without_vfunc_, &defined_by_parent)}; if (vfunc) { /* In the event that the vfunc is unchanged, let regular * prototypal inheritance take over. */ - if (defined_by_parent && is_vfunc_unchanged(vfunc)) { + if (defined_by_parent && is_vfunc_unchanged(vfunc.ref())) { *resolved = false; return true; } - if (!gjs_define_function(context, obj, m_gtype, vfunc)) + if (!gjs_define_function(context, obj, m_gtype, vfunc.ref())) return false; *resolved = true; @@ -978,41 +1906,31 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, * method resolution. */ } - if (auto const& canonical_name = get_gobject_property_name(m_info, name)) { - GjsAutoTypeClass gobj_class{m_gtype}; + if (Maybe property_info = + find_gobject_property_info(m_info.ref(), name)) { + Gjs::AutoTypeClass gobj_class{m_gtype}; if (GParamSpec* pspec = - g_object_class_find_property(gobj_class, canonical_name)) + g_object_class_find_property(gobj_class, property_info->name())) return lazy_define_gobject_property(context, obj, id, pspec, - resolved, name); + resolved, name, property_info); } - GjsAutoFieldInfo field_info = lookup_field_info(m_info, name); + Maybe field_info{lookup_field_info(m_info.ref(), name)}; if (field_info) { - bool found = false; - if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found)) - return false; - if (found) { - *resolved = false; - return true; - } - debug_jsprop("Defining lazy GObject field", id, obj); unsigned flags = GJS_MODULE_PROP_FLAGS; - if (!(g_field_info_get_flags(field_info) & GI_FIELD_IS_WRITABLE)) + if (!field_info->is_writable()) flags |= JSPROP_READONLY; - JS::RootedString key(context, id.toString()); - if (!m_field_cache.putNew(key, field_info.release())) { - JS_ReportOutOfMemory(context); - return false; - } - - JS::RootedValue private_id(context, JS::StringValue(key)); + JS::RootedObject rooted_field{ + context, new_object_with_stashed_pointer( + context, field_info.extract())}; + JS::RootedValue private_value{context, JS::ObjectValue(*rooted_field)}; if (!gjs_define_property_dynamic( context, obj, name, id, "gobject_field", &ObjectBase::field_getter, &ObjectBase::field_setter, - private_id, flags)) + private_value, flags)) return false; *resolved = true; @@ -1030,31 +1948,29 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, * introduces the iface) */ - GjsAutoBaseInfo implementor_info; - GjsAutoFunctionInfo method_info = - g_object_info_find_method_using_interfaces(m_info, name, - implementor_info.out()); + auto result = m_info->find_method_using_interfaces(name); /** * Search through any interfaces implemented by the GType; * See https://bugzilla.gnome.org/show_bug.cgi?id=632922 * for background on why we need to do this. */ - if (!method_info) + if (!result) return resolve_no_info(context, obj, id, resolved, name, ConsiderOnlyMethods); -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(method_info); -#endif + GI::AutoFunctionInfo method_info{result->first}; + GI::AutoRegisteredTypeInfo implementor_info{result->second}; + + method_info.log_usage(); - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { + if (method_info.is_method()) { gjs_debug(GJS_DEBUG_GOBJECT, - "Defining method %s in prototype for %s (%s.%s)", - method_info.name(), type_name(), ns(), this->name()); - if (GI_IS_INTERFACE_INFO(implementor_info)) { + "Defining method %s in prototype for %s (%s)", + method_info.name(), type_name(), format_name().c_str()); + if (auto iface_info = implementor_info.as()) { bool found = false; - if (!resolve_on_interface_prototype(context, implementor_info, id, + if (!resolve_on_interface_prototype(context, iface_info.value(), id, obj, &found)) return false; @@ -1080,30 +1996,26 @@ bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, [[maybe_unused]]) { unsigned n_interfaces; GType* interfaces = g_type_interfaces(gtype(), &n_interfaces); + GI::Repository repo; for (unsigned k = 0; k < n_interfaces; k++) { - GjsAutoInterfaceInfo iface_info = - g_irepository_find_by_gtype(nullptr, interfaces[k]); - - if (!iface_info) { + Maybe iface_info{ + repo.find_by_gtype(interfaces[k])}; + if (!iface_info) continue; - } - int n_methods = g_interface_info_get_n_methods(iface_info); - int n_properties = g_interface_info_get_n_properties(iface_info); - if (!properties.reserve(properties.length() + n_methods + - n_properties)) { + GI::InterfaceInfo::MethodsIterator meth_iter = iface_info->methods(); + GI::InterfaceInfo::PropertiesIterator props_iter = + iface_info->properties(); + if (!properties.reserve(properties.length() + meth_iter.size() + + props_iter.size())) { JS_ReportOutOfMemory(cx); return false; } // Methods - for (int i = 0; i < n_methods; i++) { - GjsAutoFunctionInfo meth_info = - g_interface_info_get_method(iface_info, i); - GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); - - if (flags & GI_FUNCTION_IS_METHOD) { + for (GI::AutoFunctionInfo meth_info : meth_iter) { + if (meth_info.is_method()) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) @@ -1113,11 +2025,8 @@ bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, } // Properties - for (int i = 0; i < n_properties; i++) { - GjsAutoPropertyInfo prop_info = - g_interface_info_get_property(iface_info, i); - - GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); + for (GI::AutoPropertyInfo prop_info : props_iter) { + Gjs::AutoChar js_name{gjs_hyphen_to_underscore(prop_info.name())}; jsid id = gjs_intern_string_to_id(cx, js_name); if (id.isVoid()) @@ -1129,20 +2038,17 @@ bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, g_free(interfaces); if (info()) { - int n_methods = g_object_info_get_n_methods(info()); - int n_properties = g_object_info_get_n_properties(info()); - if (!properties.reserve(properties.length() + n_methods + - n_properties)) { + GI::ObjectInfo::MethodsIterator meth_iter = info()->methods(); + GI::ObjectInfo::PropertiesIterator props_iter = info()->properties(); + if (!properties.reserve(properties.length() + meth_iter.size() + + props_iter.size())) { JS_ReportOutOfMemory(cx); return false; } // Methods - for (int i = 0; i < n_methods; i++) { - GjsAutoFunctionInfo meth_info = g_object_info_get_method(info(), i); - GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); - - if (flags & GI_FUNCTION_IS_METHOD) { + for (GI::AutoFunctionInfo meth_info : meth_iter) { + if (meth_info.is_method()) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) @@ -1152,11 +2058,8 @@ bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, } // Properties - for (int i = 0; i < n_properties; i++) { - GjsAutoPropertyInfo prop_info = - g_object_info_get_property(info(), i); - - GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); + for (GI::AutoPropertyInfo prop_info : props_iter) { + Gjs::AutoChar js_name{gjs_hyphen_to_underscore(prop_info.name())}; jsid id = gjs_intern_string_to_id(cx, js_name); if (id.isVoid()) return false; @@ -1170,7 +2073,7 @@ bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, /* Set properties from args to constructor (args[0] is supposed to be * a hash) */ bool ObjectPrototype::props_to_g_parameters( - JSContext* context, GjsAutoTypeClass const& object_class, + JSContext* context, Gjs::AutoTypeClass const& object_class, JS::HandleObject props, std::vector* names, AutoGValueVector* values) { size_t ix, length; @@ -1277,19 +2180,16 @@ ObjectInstance::gobj_dispose_notify(void) void ObjectInstance::remove_wrapped_gobjects_if( const ObjectInstance::Predicate& predicate, const ObjectInstance::Action& action) { - // Note: remove_if() does not actually remove elements, just reorders them - // and returns a start iterator of elements to remove - s_wrapped_gobject_list.erase( - std::remove_if(s_wrapped_gobject_list.begin(), - s_wrapped_gobject_list.end(), - ([predicate, action](ObjectInstance* link) { - if (predicate(link)) { - action(link); - return true; - } - return false; - })), - s_wrapped_gobject_list.end()); + for (auto link = s_wrapped_gobject_list.begin(), + last = s_wrapped_gobject_list.end(); + link != last;) { + if (predicate(*link)) { + action(*link); + link = s_wrapped_gobject_list.erase(link); + continue; + } + ++link; + } } /* @@ -1518,20 +2418,21 @@ ObjectInstance::release_native_object(void) GObject* ptr = m_ptr.release(); // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6289 - GjsAutoObjectInfo surface_info = - g_irepository_find_by_gtype(nullptr, gdksurface_type); - g_assert(surface_info && "Could not find introspected GdkSurface info"); - GjsAutoFunctionInfo destroy_func = - g_object_info_find_method(surface_info, "destroy"); + GI::Repository repo; + GI::AutoObjectInfo surface_info{ + repo.find_by_gtype(gdksurface_type) + .value()}; + GI::AutoFunctionInfo destroy_func{ + surface_info.method("destroy").value()}; GIArgument destroy_args; gjs_arg_set(&destroy_args, ptr); GIArgument unused_return; - GjsAutoError err; - if (!g_function_info_invoke(destroy_func, &destroy_args, 1, nullptr, - 0, &unused_return, err.out())) + auto result = + destroy_func.invoke({{destroy_args}}, {}, &unused_return); + if (result.isErr()) g_critical("Error destroying GdkSurface %p: %s", ptr, - err->message); + result.inspectErr()->message); } } @@ -1585,7 +2486,7 @@ ObjectInstance::ObjectInstance(ObjectPrototype* prototype, GJS_INC_COUNTER(object_instance); } -ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype) +ObjectPrototype::ObjectPrototype(Maybe info, GType gtype) : GIWrapperPrototype(info, gtype) { g_type_class_ref(gtype); @@ -1614,8 +2515,6 @@ void ObjectInstance::update_heap_wrapper_weak_pointers(JSTracer* trc, return instance->weak_pointer_was_finalized(trc); }, std::mem_fn(&ObjectInstance::disassociate_js_gobject)); - - s_wrapped_gobject_list.shrink_to_fit(); } bool ObjectInstance::weak_pointer_was_finalized(JSTracer* trc) { @@ -1712,8 +2611,9 @@ void ObjectInstance::ensure_uses_toggle_ref(JSContext* cx) { g_object_unref(m_ptr); } -static void invalidate_closure_vector(std::vector* closures, - void* data, GClosureNotify notify_func) { +template +static void invalidate_closure_collection(T* closures, void* data, + GClosureNotify notify_func) { g_assert(closures); g_assert(notify_func); @@ -1722,7 +2622,7 @@ static void invalidate_closure_vector(std::vector* closures, // invalidation mechanism, but adding a temporary reference to // ensure that the closure is still valid when calling invalidation // notify callbacks - GjsAutoGClosure closure(*it, GjsAutoTakeOwnership()); + Gjs::AutoGClosure closure{*it, Gjs::TakeOwnership{}}; it = closures->erase(it); // Only call the invalidate notifiers that won't touch this vector @@ -1776,7 +2676,7 @@ bool ObjectInstance::init_impl(JSContext* context, const JS::CallArgs& args, name(), args.length())) return false; - GjsAutoTypeClass object_class(gtype()); + Gjs::AutoTypeClass object_class{gtype()}; std::vector names; AutoGValueVector values; @@ -1790,8 +2690,7 @@ bool ObjectInstance::init_impl(JSContext* context, const JS::CallArgs& args, } JS::RootedObject props(context, &args[0].toObject()); - if (ObjectInstance::typecheck(context, props, nullptr, G_TYPE_NONE, - GjsTypecheckNoThrow{})) { + if (ObjectBase::for_js(context, props)) { gjs_throw(context, "Argument to the constructor of %s should be a plain JS " "object with properties to set", @@ -1911,7 +2810,6 @@ void ObjectInstance::trace_impl(JSTracer* tracer) { } void ObjectPrototype::trace_impl(JSTracer* tracer) { - m_field_cache.trace(tracer); m_unresolvable_cache.trace(tracer); for (GClosure* closure : m_vfuncs) Gjs::Closure::for_gclosure(closure)->trace(tracer); @@ -1946,8 +2844,8 @@ ObjectInstance::~ObjectInstance() { if (!had_toggle_up && had_toggle_down) { g_error( "Finalizing wrapper for an object that's scheduled to be " - "unrooted: %s.%s\n", - ns(), name()); + "unrooted: %s", + format_name().c_str()); } if (!m_gobj_disposed) @@ -1984,25 +2882,24 @@ ObjectInstance::~ObjectInstance() { } ObjectPrototype::~ObjectPrototype() { - invalidate_closure_vector(&m_vfuncs, this, &vfunc_invalidated_notify); + invalidate_closure_collection(&m_vfuncs, this, &vfunc_invalidated_notify); g_type_class_unref(g_type_class_peek(m_gtype)); GJS_DEC_COUNTER(object_prototype); } -static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context, - GIBaseInfo* info, - GType gtype) { - g_return_val_if_fail( - !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL); +static JSObject* gjs_lookup_object_constructor_from_info( + JSContext* context, Maybe info, GType gtype) { + g_return_val_if_fail(!info || info->is_object() || info->is_interface(), + nullptr); JS::RootedObject in_object(context); const char *constructor_name; if (info) { - in_object = gjs_lookup_namespace_object(context, info); - constructor_name = g_base_info_get_name(info); + in_object = gjs_lookup_namespace_object(context, info.value()); + constructor_name = info->name(); } else { in_object = gjs_lookup_private_namespace(context); constructor_name = g_type_name(gtype); @@ -2025,7 +2922,7 @@ static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context, we need to define it first. */ JS::RootedObject ignored(context); - if (!ObjectPrototype::define_class(context, in_object, nullptr, gtype, + if (!ObjectPrototype::define_class(context, in_object, Nothing{}, gtype, nullptr, 0, &constructor, &ignored)) return nullptr; } else { @@ -2041,11 +2938,10 @@ static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context, } GJS_JSAPI_RETURN_CONVENTION -static JSObject* gjs_lookup_object_prototype_from_info(JSContext* context, - GIBaseInfo* info, - GType gtype) { - g_return_val_if_fail( - !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL); +static JSObject* gjs_lookup_object_prototype_from_info( + JSContext* context, Maybe info, GType gtype) { + g_return_val_if_fail(!info || info->is_object() || info->is_interface(), + nullptr); JS::RootedObject constructor(context, gjs_lookup_object_constructor_from_info(context, info, gtype)); @@ -2067,55 +2963,12 @@ static JSObject * gjs_lookup_object_prototype(JSContext *context, GType gtype) { - GjsAutoObjectInfo info = gjs_lookup_gtype(nullptr, gtype); + GI::Repository repo; + Maybe info = repo.find_by_gtype(gtype).andThen( + std::mem_fn(&GI::AutoRegisteredTypeInfo::as)); return gjs_lookup_object_prototype_from_info(context, info, gtype); } -// Retrieves a GIFieldInfo for a field named @key. This is for use in -// field_getter_impl() and field_setter_not_impl(), where the field info *must* -// have been cached previously in resolve_impl() on this ObjectPrototype or one -// of its parent ObjectPrototypes. This will fail an assertion if there is no -// cached field info. -// -// The caller does not own the return value, and it can never be null. -GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx, - JS::HandleString key) { - if (!info()) { - // Custom JS classes can't have fields, and fields on internal classes - // are not available. We must be looking up a field on a - // GObject-introspected parent. - GType parent_gtype = g_type_parent(m_gtype); - g_assert(parent_gtype != G_TYPE_INVALID && - "Custom JS class must have parent"); - ObjectPrototype* parent_proto = - ObjectPrototype::for_gtype(parent_gtype); - - if (!parent_proto) { - JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, parent_gtype)); - parent_proto = ObjectPrototype::for_js(cx, proto); - } - - g_assert(parent_proto && - "Custom JS class's parent must have been accessed in JS"); - return parent_proto->lookup_cached_field_info(cx, key); - } - - gjs_debug_jsprop(GJS_DEBUG_GOBJECT, - "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) - return entry->value().get(); - - // We must be looking up a field defined on a parent. Look up the prototype - // object via its GIObjectInfo. - GjsAutoObjectInfo parent_info = g_object_info_get_parent(m_info); - JS::RootedObject parent_proto(cx, gjs_lookup_object_prototype_from_info( - cx, parent_info, G_TYPE_INVALID)); - ObjectPrototype* parent = ObjectPrototype::for_js(cx, parent_proto); - return parent->lookup_cached_field_info(cx, key); -} - bool ObjectInstance::associate_closure(JSContext* cx, GClosure* closure) { if (!is_prototype()) to_instance()->ensure_uses_toggle_ref(cx); @@ -2140,7 +2993,8 @@ void ObjectInstance::closure_invalidated_notify(void* data, GClosure* closure) { } void ObjectInstance::invalidate_closures() { - invalidate_closure_vector(&m_closures, this, &closure_invalidated_notify); + invalidate_closure_collection(&m_closures, this, + &closure_invalidated_notify); m_closures.shrink_to_fit(); } @@ -2207,9 +3061,10 @@ bool ObjectInstance::connect_impl(JSContext* context, const JS::CallArgs& args, return false; } - std::string dynamicString = - format_name() + '.' + func_name + "('" + signal_name.get() + "')"; - AutoProfilerLabel label(context, "", dynamicString.c_str()); + std::string dynamic_string{GJS_PROFILER_DYNAMIC_STRING( + context, + format_name() + '.' + func_name + "('" + signal_name.get() + "')")}; + AutoProfilerLabel label{context, "", dynamic_string}; if (!JS::IsCallable(callback)) { gjs_throw(context, "second arg must be a callback"); @@ -2277,9 +3132,9 @@ ObjectInstance::emit_impl(JSContext *context, "signal name", &signal_name)) return false; - std::string dynamicString = - format_name() + "emit('" + signal_name.get() + "')"; - AutoProfilerLabel label(context, "", dynamicString.c_str()); + std::string full_name{GJS_PROFILER_DYNAMIC_STRING( + context, format_name() + " emit('" + signal_name.get() + "')")}; + AutoProfilerLabel label{context, "", full_name}; if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, false)) { @@ -2317,14 +3172,13 @@ ObjectInstance::emit_impl(JSContext *context, if (!ObjectBase::info()) continue; - GjsAutoSignalInfo signal_info = g_object_info_find_signal( - ObjectBase::info(), signal_query.signal_name); + Maybe signal_info = + ObjectBase::info()->signal(signal_query.signal_name); if (!signal_info) continue; - GjsAutoArgInfo arg_info = g_callable_info_get_arg(signal_info, i); - if (g_arg_info_get_ownership_transfer(arg_info) != - GI_TRANSFER_NOTHING) { + GI::AutoArgInfo arg_info{signal_info->arg(i)}; + if (arg_info.ownership_transfer() != GI_TRANSFER_NOTHING) { // FIXME(3v1n0): As it happens in many places in gjs, we can't track // (yet) containers content, so in case of transfer container we // can only leak. @@ -2592,8 +3446,9 @@ bool ObjectBase::init_gobject(JSContext* context, unsigned argc, if (!priv->check_is_instance(context, "initialize")) return false; - std::string dynamicString = priv->format_name() + "._init"; - AutoProfilerLabel label(context, "", dynamicString.c_str()); + std::string full_name{ + GJS_PROFILER_DYNAMIC_STRING(context, priv->format_name() + "._init")}; + AutoProfilerLabel label{context, "", full_name}; return priv->to_instance()->init_impl(context, argv, obj); } @@ -2690,10 +3545,13 @@ void ObjectPrototype::set_interfaces(GType* interface_gtypes, * constructor and prototype objects as out parameters, for convenience * elsewhere. */ -bool ObjectPrototype::define_class( - JSContext* context, JS::HandleObject in_object, GIObjectInfo* info, - GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes, - JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { +bool ObjectPrototype::define_class(JSContext* context, + JS::HandleObject in_object, + Maybe info, + GType gtype, GType* interface_gtypes, + uint32_t n_interface_gtypes, + JS::MutableHandleObject constructor, + JS::MutableHandleObject prototype) { ObjectPrototype* priv = ObjectPrototype::create_class( context, in_object, info, gtype, constructor, prototype); if (!priv) @@ -2876,13 +3734,12 @@ bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, - GType expected_gtype, - GIBaseInfo* expected_info) { + GType expected_gtype) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); - if (!ObjectBase::typecheck(cx, obj, expected_info, expected_gtype)) { - gjs_arg_unset(arg); + if (!ObjectBase::typecheck(cx, obj, expected_gtype)) { + gjs_arg_unset(arg); return false; } @@ -2909,70 +3766,52 @@ bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, return true; } -// Overrides GIWrapperInstance::typecheck_impl() -bool ObjectInstance::typecheck_impl(JSContext* cx, GIBaseInfo* expected_info, - GType expected_type) const { - g_assert(m_gobj_disposed || !m_ptr || - gtype() == G_OBJECT_TYPE(m_ptr.as())); - return GIWrapperInstance::typecheck_impl(cx, expected_info, expected_type); -} - +// Returns pair of implementor_vtable pointer, maybe field info GJS_JSAPI_RETURN_CONVENTION -static bool find_vfunc_info(JSContext* context, GType implementor_gtype, - GIBaseInfo* vfunc_info, const char* vfunc_name, - void** implementor_vtable_ret, - GjsAutoFieldInfo* field_info_ret) { - GType ancestor_gtype; - int length, i; - GIBaseInfo *ancestor_info; - GjsAutoStructInfo struct_info; - bool is_interface; - - field_info_ret->reset(); - *implementor_vtable_ret = NULL; - - ancestor_info = g_base_info_get_container(vfunc_info); - ancestor_gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)ancestor_info); - - is_interface = g_base_info_get_type(ancestor_info) == GI_INFO_TYPE_INTERFACE; - - GjsAutoTypeClass implementor_class(implementor_gtype); - if (is_interface) { +static Maybe>> find_vfunc_info( + JSContext* context, GType implementor_gtype, const GI::VFuncInfo vfunc_info, + const char* vfunc_name) { + Maybe struct_info; + void* implementor_vtable_ret = nullptr; + + const GI::RegisteredTypeInfo ancestor_info = + vfunc_info.container().value(); + GType ancestor_gtype = ancestor_info.gtype(); + + Gjs::AutoTypeClass implementor_class{implementor_gtype}; + if (auto iface_info = ancestor_info.as()) { GTypeInstance *implementor_iface_class; implementor_iface_class = (GTypeInstance*) g_type_interface_peek(implementor_class, ancestor_gtype); if (implementor_iface_class == NULL) { gjs_throw (context, "Couldn't find GType of implementor of interface %s.", g_type_name(ancestor_gtype)); - return false; + return Nothing{}; } - *implementor_vtable_ret = implementor_iface_class; + implementor_vtable_ret = implementor_iface_class; - struct_info = g_interface_info_get_iface_struct((GIInterfaceInfo*)ancestor_info); + struct_info = iface_info->iface_struct(); } else { - struct_info = g_object_info_get_class_struct((GIObjectInfo*)ancestor_info); - *implementor_vtable_ret = implementor_class; + struct_info = ancestor_info.as()->class_struct(); + implementor_vtable_ret = implementor_class; } - length = g_struct_info_get_n_fields(struct_info); - for (i = 0; i < length; i++) { - GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); + for (GI::AutoFieldInfo field_info : struct_info->fields()) { if (strcmp(field_info.name(), vfunc_name) != 0) continue; - GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); - if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) { + GI::AutoTypeInfo type_info{field_info.type_info()}; + if (type_info.tag() != GI_TYPE_TAG_INTERFACE) { /* We have a field with the same name, but it's not a callback. * There's no hope of being another field with a correct name, * so just abort early. */ - return true; - } else { - *field_info_ret = std::move(field_info); - return true; + return Some(std::make_pair(implementor_vtable_ret, Nothing{})); } + return Some(std::make_pair(implementor_vtable_ret, + Some(std::move(field_info)))); } - return true; + return Some(std::make_pair(implementor_vtable_ret, Nothing{})); } bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -2986,33 +3825,35 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args) { JS::UniqueChars name; JS::RootedObject callable(cx); - if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so", "name", &name, - "callable", &callable)) + bool is_static = false; + if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so|b", "name", &name, + "function", &callable, "is_static", &is_static)) return false; args.rval().setUndefined(); /* find the first class that actually has repository information */ - GIObjectInfo *info = m_info; + GI::Repository repo; + Maybe info = m_info; GType info_gtype = m_gtype; while (!info && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); - info = g_irepository_find_by_gtype(nullptr, info_gtype); + info = repo.find_by_gtype(info_gtype); } /* If we don't have 'info', we don't have the base class (GObject). * This is awful, so abort now. */ - g_assert(info != NULL); + g_assert(info); - GjsAutoVFuncInfo vfunc = g_object_info_find_vfunc(info, name.get()); + Maybe vfunc{info->vfunc(name.get())}; // Search the parent type chain while (!vfunc && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); - info = g_irepository_find_by_gtype(nullptr, info_gtype); + info = repo.find_by_gtype(info_gtype); if (info) - vfunc = g_object_info_find_vfunc(info, name.get()); + vfunc = info->vfunc(name.get()); } // If the vfunc doesn't exist in the parent @@ -3020,14 +3861,13 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, // defined interfaces... if (!vfunc) { for (GType interface_gtype : m_interface_gtypes) { - GjsAutoInterfaceInfo interface = - g_irepository_find_by_gtype(nullptr, interface_gtype); + Maybe interface{ + repo.find_by_gtype(interface_gtype)}; // Private and dynamic interfaces (defined in JS) do not have type // info. if (interface) { - vfunc = g_interface_info_find_vfunc(interface, name.get()); - + vfunc = interface->vfunc(name.get()); if (vfunc) break; } @@ -3040,30 +3880,30 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, // case, print a more helpful error than... // "Could not find definition of virtual function" // - // See https://gitlab.gnome.org/GNOME/cjs/-/issues/89 + // See https://gitlab.gnome.org/GNOME/gjs/-/issues/89 if (!vfunc) { unsigned n_interfaces; - GjsAutoPointer interface_list = - g_type_interfaces(m_gtype, &n_interfaces); + Gjs::AutoPointer interface_list{ + g_type_interfaces(m_gtype, &n_interfaces)}; for (unsigned i = 0; i < n_interfaces; i++) { - GjsAutoInterfaceInfo interface = - g_irepository_find_by_gtype(nullptr, interface_list[i]); + Maybe interface{ + repo.find_by_gtype(interface_list[i])}; - if (interface) { - GjsAutoVFuncInfo parent_vfunc = - g_interface_info_find_vfunc(interface, name.get()); - - if (parent_vfunc) { - GjsAutoChar identifier = g_strdup_printf( - "%s.%s", interface.ns(), interface.name()); - gjs_throw(cx, - "%s does not implement %s, add %s to your " - "implements array", - g_type_name(m_gtype), identifier.get(), - identifier.get()); - return false; - } + if (!interface) + continue; + + Maybe parent_vfunc{interface->vfunc(name.get())}; + + if (parent_vfunc) { + Gjs::AutoChar identifier{g_strdup_printf( + "%s.%s", interface->ns(), interface->name())}; + gjs_throw(cx, + "%s does not implement %s, add %s to your " + "implements array", + g_type_name(m_gtype), identifier.get(), + identifier.get()); + return false; } } @@ -3073,18 +3913,24 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, return false; } - void *implementor_vtable; - GjsAutoFieldInfo field_info; - if (!find_vfunc_info(cx, m_gtype, vfunc, name.get(), &implementor_vtable, - &field_info)) + if (vfunc->is_method() != !is_static) { + gjs_throw(cx, "Invalid %s definition of %s virtual function %s", + is_static ? "static" : "non-static", + is_static ? "non-static" : "static", name.get()); + return false; + } + + auto result = find_vfunc_info(cx, m_gtype, vfunc.ref(), name.get()); + if (!result) return false; + void* implementor_vtable = result->first; + Maybe field_info = result->second; if (field_info) { - gint offset; void* method_ptr; GjsCallbackTrampoline *trampoline; - offset = g_field_info_get_offset(field_info); + int offset = field_info->offset(); method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset); if (!JS::IsCallable(callable)) { @@ -3092,7 +3938,7 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, return false; } trampoline = GjsCallbackTrampoline::create( - cx, callable, vfunc, GI_SCOPE_TYPE_NOTIFIED, true, true); + cx, callable, vfunc.ref(), GI_SCOPE_TYPE_NOTIFIED, true, !is_static); if (!trampoline) return false; @@ -3101,7 +3947,7 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(), trampoline) == m_vfuncs.end() && "This vfunc was already associated with this class"); - m_vfuncs.push_back(trampoline); + m_vfuncs.insert(trampoline); g_closure_add_invalidate_notifier( trampoline, this, &ObjectPrototype::vfunc_invalidated_notify); g_closure_add_invalidate_notifier( @@ -3117,7 +3963,7 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) { // This callback should *only* touch m_vfuncs auto* priv = static_cast(data); - Gjs::remove_one_from_unsorted_vector(&priv->m_vfuncs, closure); + priv->m_vfuncs.erase(closure); } bool @@ -3127,7 +3973,9 @@ gjs_lookup_object_constructor(JSContext *context, { JSObject *constructor; - GjsAutoObjectInfo object_info = gjs_lookup_gtype(nullptr, gtype); + GI::Repository repo; + Maybe object_info = repo.find_by_gtype(gtype).andThen( + std::mem_fn(&GI::AutoRegisteredTypeInfo::as)); constructor = gjs_lookup_object_constructor_from_info(context, object_info, gtype); diff --git a/gi/object.h b/gi/object.h index fdbb6f66c..eff6273ef 100644 --- a/gi/object.h +++ b/gi/object.h @@ -11,9 +11,10 @@ #include // for uint32_t #include +#include #include -#include +#include #include #include @@ -26,11 +27,14 @@ #include #include // for HashGeneric, HashNumber #include // for MOZ_LIKELY +#include +#include "gi/info.h" #include "gi/value.h" #include "gi/wrapperutils.h" +#include "cjs/auto.h" #include "cjs/jsapi-util-root.h" -#include "cjs/jsapi-util.h" +#include "cjs/jsapi-util.h" // for gjs_throw #include "cjs/macros.h" #include "util/log.h" @@ -46,6 +50,8 @@ struct ObjectInstance; } class ObjectInstance; class ObjectPrototype; +class ObjectPropertyInfoCaller; +class ObjectPropertyPspecCaller; /* * ObjectBase: @@ -86,8 +92,7 @@ class ObjectBase GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, - GType expected_gtype, - GIBaseInfo* expected_info = nullptr); + GType expected_gtype); private: // This is used in debug methods only. @@ -104,15 +109,26 @@ class ObjectBase [[nodiscard]] bool is_custom_js_class(); public: - GJS_JSAPI_RETURN_CONVENTION - static bool typecheck(JSContext* cx, JS::HandleObject obj, - GIObjectInfo* expected_info, GType expected_gtype); - [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, - GIObjectInfo* expected_info, - GType expected_gtype, - GjsTypecheckNoThrow no_throw) { - return GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype, - no_throw); + // Overrides GIWrapperBase::typecheck(). We only override the overload that + // throws, so that we can throw our own more informative error. + template + GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, + JS::HandleObject obj, + T expected) { + if (GIWrapperBase::typecheck(cx, obj, expected)) + return true; + + gjs_throw(cx, + "This JS object wrapper isn't wrapping a GObject." + " If this is a custom subclass, are you sure you chained" + " up to the parent _init properly?"); + return false; + } + template + [[nodiscard]] + static bool typecheck(JSContext* cx, JS::HandleObject obj, T expected, + GjsTypecheckNoThrow no_throw) { + return GIWrapperBase::typecheck(cx, obj, expected, no_throw); } /* JSClass operations */ @@ -123,12 +139,29 @@ class ObjectBase /* JS property getters/setters */ public: + template + GJS_JSAPI_RETURN_CONVENTION static bool prop_getter(JSContext*, unsigned, + JS::Value*); + GJS_JSAPI_RETURN_CONVENTION + static bool prop_getter_write_only(JSContext*, unsigned argc, + JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION - static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp); + static bool prop_getter_func(JSContext* cx, unsigned argc, JS::Value* vp); + template + GJS_JSAPI_RETURN_CONVENTION static bool prop_getter_simple_type_func( + JSContext*, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp); + template + GJS_JSAPI_RETURN_CONVENTION static bool prop_setter(JSContext*, unsigned, + JS::Value*); GJS_JSAPI_RETURN_CONVENTION - static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp); + static bool prop_setter_read_only(JSContext*, unsigned argc, JS::Value* vp); + GJS_JSAPI_RETURN_CONVENTION + static bool prop_setter_func(JSContext* cx, unsigned argc, JS::Value* vp); + template + GJS_JSAPI_RETURN_CONVENTION static bool prop_setter_simple_type_func( + JSContext*, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp); @@ -180,30 +213,27 @@ struct IdHasher { }; class ObjectPrototype - : public GIWrapperPrototype { - friend class GIWrapperPrototype; + : public GIWrapperPrototype, + mozilla::Maybe> { + friend class GIWrapperPrototype, + mozilla::Maybe>; friend class GIWrapperBase; - using FieldCache = - JS::GCHashMap, GjsAutoFieldInfo, - js::DefaultHasher, js::SystemAllocPolicy>; using NegativeLookupCache = JS::GCHashSet, IdHasher, js::SystemAllocPolicy>; - FieldCache m_field_cache; NegativeLookupCache m_unresolvable_cache; // a list of vfunc GClosures installed on this prototype, used when tracing - std::vector m_vfuncs; + std::unordered_set m_vfuncs; // a list of interface types explicitly associated with this prototype, // by gjs_add_interface std::vector m_interface_gtypes; - ObjectPrototype(GIObjectInfo* info, GType gtype); + ObjectPrototype(mozilla::Maybe info, GType gtype); ~ObjectPrototype(); - static constexpr InfoType::Tag info_type_tag = InfoType::Object; - public: [[nodiscard]] static ObjectPrototype* for_gtype(GType gtype); @@ -214,14 +244,15 @@ class ObjectPrototype GJS_JSAPI_RETURN_CONVENTION bool get_parent_constructor(JSContext* cx, JS::MutableHandleObject constructor) const; - - [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info); + [[nodiscard]] + bool is_vfunc_unchanged(const GI::VFuncInfo) const; static void vfunc_invalidated_notify(void* data, GClosure* closure); GJS_JSAPI_RETURN_CONVENTION - bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj, - JS::HandleId id, GParamSpec*, - bool* resolved, const char* name); + bool lazy_define_gobject_property( + JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec*, + bool* resolved, const char* name, + mozilla::Maybe = {}); enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties }; GJS_JSAPI_RETURN_CONVENTION @@ -237,20 +268,18 @@ class ObjectPrototype void set_type_qdata(void); GJS_JSAPI_RETURN_CONVENTION GParamSpec* find_param_spec_from_id(JSContext*, - GjsAutoTypeClass const&, + Gjs::AutoTypeClass const&, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION - GIFieldInfo* lookup_cached_field_info(JSContext* cx, JS::HandleString key); - GJS_JSAPI_RETURN_CONVENTION bool props_to_g_parameters(JSContext*, - GjsAutoTypeClass const&, + Gjs::AutoTypeClass const&, JS::HandleObject props, std::vector* names, AutoGValueVector* values); GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, - GIObjectInfo* info, GType gtype, + mozilla::Maybe, GType, GType* interface_gtypes, uint32_t n_interface_gtypes, JS::MutableHandleObject constructor, @@ -403,7 +432,7 @@ class ObjectInstance : public GIWrapperInstance s_wrapped_gobject_list; + static std::unordered_set s_wrapped_gobject_list; void link(void); void unlink(void); [[nodiscard]] static size_t num_wrapped_gobjects() { @@ -429,16 +458,29 @@ class ObjectInstance : public GIWrapperInstance + GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl( + JSContext* cx, GParamSpec*, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION - bool prop_getter_impl(JSContext* cx, GParamSpec*, - JS::MutableHandleValue rval); + bool prop_getter_impl(JSContext* cx, ObjectPropertyInfoCaller*, + JS::CallArgs const& args); + template + GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl( + JSContext*, ObjectPropertyPspecCaller*, JS::CallArgs const&); GJS_JSAPI_RETURN_CONVENTION - bool field_getter_impl(JSContext* cx, JS::HandleString name, + bool field_getter_impl(JSContext* cx, GI::AutoFieldInfo const&, JS::MutableHandleValue rval); + template + GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext*, GParamSpec*, + JS::HandleValue); GJS_JSAPI_RETURN_CONVENTION - bool prop_setter_impl(JSContext* cx, GParamSpec*, JS::HandleValue value); + bool prop_setter_impl(JSContext* cx, ObjectPropertyInfoCaller*, + JS::CallArgs const& args); + template + GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl( + JSContext*, ObjectPropertyPspecCaller*, JS::CallArgs const&); GJS_JSAPI_RETURN_CONVENTION - bool field_setter_not_impl(JSContext* cx, JS::HandleString name); + bool field_setter_not_impl(JSContext* cx, GI::AutoFieldInfo const&); // JS constructor @@ -464,9 +506,13 @@ class ObjectInstance : public GIWrapperInstance + GJS_JSAPI_RETURN_CONVENTION bool typecheck_impl(T expected) const { + g_assert(m_gobj_disposed || !m_ptr || + gtype() == G_OBJECT_TYPE(m_ptr.as())); + return GIWrapperInstance::typecheck_impl(expected); + } /* Notification callbacks */ void gobj_dispose_notify(void); diff --git a/gi/param.cpp b/gi/param.cpp index 4ad04c3c1..ca71496d1 100644 --- a/gi/param.cpp +++ b/gi/param.cpp @@ -6,7 +6,6 @@ #include // for size_t -#include #include #include @@ -21,13 +20,16 @@ #include // for UniqueChars #include #include // for JS_NewObjectForConstructor, JS_NewObjectWithG... +#include #include "gi/cwrapper.h" #include "gi/function.h" +#include "gi/info.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" @@ -35,14 +37,16 @@ #include "cjs/mem-private.h" #include "util/log.h" +using mozilla::Maybe; + extern struct JSClass gjs_param_class; // Reserved slots static const size_t POINTER = 0; -struct Param : GjsAutoParam { +struct Param : Gjs::AutoParam { explicit Param(GParamSpec* param) - : GjsAutoParam(param, GjsAutoTakeOwnership()) {} + : Gjs::AutoParam(param, Gjs::TakeOwnership{}) {} }; [[nodiscard]] static GParamSpec* param_value(JSContext* cx, @@ -79,24 +83,23 @@ param_resolve(JSContext *context, return true; /* not resolved, but no error */ } - GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); - GjsAutoFunctionInfo method_info = - g_object_info_find_method(info, name.get()); + GI::Repository repo; + GI::AutoObjectInfo info{ + repo.find_by_gtype(G_TYPE_PARAM).value()}; + Maybe method_info{info.method(name.get())}; if (!method_info) { *resolved = false; return true; } -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(method_info); -#endif + method_info->log_usage(); - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { + if (method_info->is_method()) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for GObject.ParamSpec", - method_info.name()); + method_info->name()); - if (!gjs_define_function(context, obj, G_TYPE_PARAM, method_info)) + if (!gjs_define_function(context, obj, G_TYPE_PARAM, method_info.ref())) return false; *resolved = true; /* we defined the prop in obj */ @@ -213,9 +216,10 @@ gjs_define_param_class(JSContext *context, if (!gjs_wrapper_define_gtype_prop(context, constructor, G_TYPE_PARAM)) return false; - GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); - if (!gjs_define_static_methods(context, constructor, - G_TYPE_PARAM, info)) + GI::Repository repo; + GI::AutoObjectInfo info{ + repo.find_by_gtype(G_TYPE_PARAM).value()}; + if (!gjs_define_static_methods(context, constructor, G_TYPE_PARAM, info)) return false; gjs_debug(GJS_DEBUG_GPARAM, diff --git a/gi/private.cpp b/gi/private.cpp index bc896722c..f35a637d5 100644 --- a/gi/private.cpp +++ b/gi/private.cpp @@ -21,6 +21,7 @@ #include #include #include // for JS_NewPlainObject +#include #include "gi/closure.h" #include "gi/gobject.h" @@ -32,11 +33,14 @@ #include "gi/repo.h" #include "gi/value.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" +using mozilla::Nothing; + /* gi/private.cpp - private "imports._gi" module with operations that we need * to use from JS in order to create GObject classes, but should not be exposed * to client code. @@ -67,7 +71,7 @@ static bool gjs_override_property(JSContext* cx, unsigned argc, JS::Value* vp) { pspec = g_object_interface_find_property(interface_type, name.get()); g_type_default_interface_unref(interface_type); } else { - GjsAutoTypeClass class_type(gtype); + Gjs::AutoTypeClass class_type{gtype}; pspec = g_object_class_find_property(class_type, name.get()); } @@ -77,7 +81,7 @@ static bool gjs_override_property(JSContext* cx, unsigned argc, JS::Value* vp) { return false; } - GjsAutoParam new_pspec = g_param_spec_override(name.get(), pspec); + Gjs::AutoParam new_pspec{g_param_spec_override(name.get(), pspec)}; g_param_spec_set_qdata(new_pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); @@ -211,7 +215,7 @@ static bool gjs_register_interface_impl(JSContext* cx, const char* name, &n_interfaces, &n_properties)) return false; - GjsAutoPointer iface_types = g_new(GType, n_interfaces); + Gjs::AutoPointer iface_types{g_new(GType, n_interfaces)}; /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ @@ -264,7 +268,7 @@ static bool gjs_register_interface(JSContext* cx, unsigned argc, return false; // error will have been thrown already JS::RootedObject constructor(cx), ignored_prototype(cx); - if (!InterfacePrototype::create_class(cx, module, nullptr, interface_type, + if (!InterfacePrototype::create_class(cx, module, Nothing{}, interface_type, &constructor, &ignored_prototype)) return false; @@ -295,7 +299,7 @@ static bool gjs_register_interface_with_class(JSContext* cx, unsigned argc, return false; // error will have been thrown already JS::RootedObject prototype(cx); - if (!InterfacePrototype::wrap_class(cx, module, nullptr, interface_type, + if (!InterfacePrototype::wrap_class(cx, module, Nothing{}, interface_type, klass, &prototype)) return false; @@ -331,7 +335,7 @@ static bool gjs_register_type_impl(JSContext* cx, const char* name, &n_interfaces, &n_properties)) return false; - GjsAutoPointer iface_types = g_new(GType, n_interfaces); + Gjs::AutoPointer iface_types{g_new(GType, n_interfaces)}; /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ @@ -392,7 +396,7 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) { return false; GType instance_type; - GjsAutoPointer iface_types; + Gjs::AutoPointer iface_types; uint32_t n_interfaces; if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces, properties, iface_types.out(), &n_interfaces, @@ -402,7 +406,7 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) { /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); JS::RootedObject constructor(cx), prototype(cx); - if (!ObjectPrototype::define_class(cx, module, nullptr, instance_type, + if (!ObjectPrototype::define_class(cx, module, Nothing{}, instance_type, iface_types, n_interfaces, &constructor, &prototype)) return false; @@ -431,7 +435,7 @@ static bool gjs_register_type_with_class(JSContext* cx, unsigned argc, GType instance_type; uint32_t n_interfaces; - GjsAutoPointer iface_types; + Gjs::AutoPointer iface_types; if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces, properties, iface_types.out(), &n_interfaces, &instance_type)) @@ -444,7 +448,7 @@ static bool gjs_register_type_with_class(JSContext* cx, unsigned argc, JS::RootedObject prototype(cx); ObjectPrototype* priv = ObjectPrototype::wrap_class( - cx, module, nullptr, instance_type, klass, &prototype); + cx, module, Nothing{}, instance_type, klass, &prototype); if (!priv) return false; @@ -498,7 +502,7 @@ static bool gjs_signal_new(JSContext* cx, unsigned argc, JS::Value* vp) { if (!JS::GetArrayLength(cx, params_obj, &n_parameters)) return false; - GjsAutoPointer params = g_new(GType, n_parameters); + Gjs::AutoPointer params{g_new(GType, n_parameters)}; JS::RootedValue gtype_val(cx); for (uint32_t ix = 0; ix < n_parameters; ix++) { if (!JS_GetElement(cx, params_obj, ix, >ype_val) || diff --git a/gi/repo.cpp b/gi/repo.cpp index 42403cff1..e844db956 100644 --- a/gi/repo.cpp +++ b/gi/repo.cpp @@ -6,11 +6,7 @@ #include // for strlen -#if GJS_VERBOSE_ENABLE_GI_USAGE -# include -#endif - -#include +#include #include #include @@ -31,6 +27,7 @@ #include #include #include // for JS_NewPlainObject, JS_NewObject +#include #include "gi/arg.h" #include "gi/boxed.h" @@ -38,6 +35,7 @@ #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" +#include "gi/info.h" #include "gi/interface.h" #include "gi/ns.h" #include "gi/object.h" @@ -45,6 +43,7 @@ #include "gi/repo.h" #include "gi/union.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" @@ -52,6 +51,8 @@ #include "cjs/module.h" #include "util/log.h" +using mozilla::Maybe; + GJS_JSAPI_RETURN_CONVENTION static bool lookup_override_function(JSContext *, JS::HandleId, JS::MutableHandleValue); @@ -76,8 +77,6 @@ static bool get_version_for_ns(JSContext* context, JS::HandleObject repo_obj, return gjs_object_require_property(context, versions, NULL, ns_id, version); } -static void strlist_free(GList* l) { g_list_free_full(l, g_free); } - GJS_JSAPI_RETURN_CONVENTION static bool resolve_namespace_object(JSContext* context, JS::HandleObject repo_obj, @@ -94,44 +93,44 @@ static bool resolve_namespace_object(JSContext* context, return false; } - GjsAutoPointer versions = - g_irepository_enumerate_versions(nullptr, ns_name.get()); - unsigned nversions = g_list_length(versions); + GI::Repository repo; + size_t nversions; + Gjs::AutoStrv versions{repo.enumerate_versions(ns_name.get(), &nversions)}; if (nversions > 1 && !version && - !g_irepository_is_registered(nullptr, ns_name.get(), nullptr) && + !repo.is_registered(ns_name.get(), nullptr) && !JS::WarnUTF8(context, - "Requiring %s but it has %u versions available; use " + "Requiring %s but it has %zu versions available; use " "imports.gi.versions to pick one", ns_name.get(), nversions)) return false; - GjsAutoError error; // If resolving Gio, load the platform-specific typelib first, so that // GioUnix/GioWin32 GTypes get looked up in there with higher priority, // instead of in Gio. -#if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32)) +#if (defined(G_OS_UNIX) || defined(G_OS_WIN32)) if (strcmp(ns_name.get(), "Gio") == 0) { # ifdef G_OS_UNIX const char* platform = "Unix"; # else // G_OS_WIN32 const char* platform = "Win32"; # endif // G_OS_UNIX/G_OS_WIN32 - GjsAutoChar platform_specific = - g_strconcat(ns_name.get(), platform, nullptr); - if (!g_irepository_require(nullptr, platform_specific, version.get(), - GIRepositoryLoadFlags(0), &error)) { + Gjs::AutoChar platform_specific{ + g_strconcat(ns_name.get(), platform, nullptr)}; + auto required = repo.require(platform_specific, version.get()); + if (!required.isOk()) { gjs_throw(context, "Failed to require %s %s: %s", - platform_specific.get(), version.get(), error->message); + platform_specific.get(), version.get(), + required.inspectErr()->message); return false; } } -#endif // GLib >= 2.79.2 +#endif // (defined(G_OS_UNIX) || defined(G_OS_WIN32)) - g_irepository_require(nullptr, ns_name.get(), version.get(), - GIRepositoryLoadFlags(0), &error); - if (error) { + auto required = repo.require(ns_name.get(), version.get()); + if (!required.isOk()) { gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(), - version ? version.get() : "none", error->message); + version ? version.get() : "none", + required.inspectErr()->message); return false; } @@ -242,7 +241,6 @@ repo_new(JSContext *context) JSPROP_PERMANENT)) return nullptr; -#if GLIB_CHECK_VERSION(2, 79, 2) # if defined(G_OS_UNIX) if (!JS_DefineProperty(context, versions, "GLibUnix", two_point_oh, JSPROP_PERMANENT) || @@ -256,7 +254,6 @@ repo_new(JSContext *context) JSPROP_PERMANENT)) return nullptr; # endif // G_OS_UNIX/G_OS_WIN32 -#endif // GLib >= 2.79.2 JS::RootedObject private_ns(context, JS_NewPlainObject(context)); if (!JS_DefinePropertyById(context, repo, atoms.private_ns_marker(), @@ -275,217 +272,99 @@ gjs_define_repo(JSContext *cx, } GJS_JSAPI_RETURN_CONVENTION -static bool gjs_value_from_constant_info(JSContext* cx, GIConstantInfo* info, +static bool gjs_value_from_constant_info(JSContext* cx, + const GI::ConstantInfo info, JS::MutableHandleValue value) { GIArgument garg; - g_constant_info_get_value(info, &garg); - - GjsAutoTypeInfo type_info = g_constant_info_get_type(info); + info.load_value(&garg); + auto guard = + mozilla::MakeScopeExit([&info, &garg]() { info.free_value(&garg); }); - bool ok = gjs_value_from_gi_argument(cx, value, type_info, &garg, true); - - g_constant_info_free_value(info, &garg); - return ok; + return gjs_value_from_gi_argument(cx, value, info.type_info(), &garg, true); } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_define_constant(JSContext *context, - JS::HandleObject in_object, - GIConstantInfo *info) -{ +static bool gjs_define_constant(JSContext* context, JS::HandleObject in_object, + const GI::ConstantInfo info) { JS::RootedValue value(context); - const char *name; if (!gjs_value_from_constant_info(context, info, &value)) return false; - name = g_base_info_get_name((GIBaseInfo*) info); - - return JS_DefineProperty(context, in_object, name, value, + return JS_DefineProperty(context, in_object, info.name(), value, GJS_MODULE_PROP_FLAGS); } -#if GJS_VERBOSE_ENABLE_GI_USAGE -void -_gjs_log_info_usage(GIBaseInfo *info) -{ -#define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" ) -#define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" ) - - { - char *details; - GIInfoType info_type; - GIBaseInfo *container; - - info_type = g_base_info_get_type(info); - - if (info_type == GI_INFO_TYPE_FUNCTION) { - std::string args("{ "); - int n_args; - int i; - GITransfer retval_transfer; - - n_args = g_callable_info_get_n_args((GICallableInfo*) info); - for (i = 0; i < n_args; ++i) { - GIArgInfo *arg; - GIDirection direction; - GITransfer transfer; +bool gjs_define_info(JSContext* cx, JS::HandleObject in_object, + const GI::BaseInfo info, bool* defined) { + info.log_usage(); - arg = g_callable_info_get_arg((GICallableInfo*)info, i); - direction = g_arg_info_get_direction(arg); - transfer = g_arg_info_get_ownership_transfer(arg); - - if (i > 0) - args += ", "; - - args += std::string("{ GI_DIRECTION_") + - DIRECTION_STRING(direction) + ", GI_TRANSFER_" + - TRANSFER_STRING(transfer) + " }"; + *defined = true; - g_base_info_unref((GIBaseInfo*) arg); - } + if (auto func_info = info.as()) + return gjs_define_function(cx, in_object, 0, func_info.value()); - args += " }"; + if (auto object_info = info.as()) { + GType gtype = object_info->gtype(); - retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info); + if (g_type_is_a(gtype, G_TYPE_PARAM)) + return gjs_define_param_class(cx, in_object); - details = g_strdup_printf( - ".details = { .func = { .retval_transfer = GI_TRANSFER_%s, " - ".n_args = %d, .args = %s } }", - TRANSFER_STRING(retval_transfer), n_args, args.c_str()); - } else { - details = g_strdup_printf(".details = { .nothing = {} }"); + if (g_type_is_a(gtype, G_TYPE_OBJECT)) { + JS::RootedObject ignored1{cx}, ignored2{cx}; + return ObjectPrototype::define_class(cx, in_object, object_info, + gtype, nullptr, 0, &ignored1, + &ignored2); } - container = g_base_info_get_container(info); + if (G_TYPE_IS_INSTANTIATABLE(gtype)) { + JS::RootedObject ignored{cx}; + return FundamentalPrototype::define_class( + cx, in_object, object_info.value(), &ignored); + } - gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },", - gjs_info_type_name(info_type), - g_base_info_get_namespace(info), - container ? g_base_info_get_name(container) : "", - g_base_info_get_name(info), - details); - g_free(details); + gjs_throw(cx, "Unsupported type %s, deriving from fundamental %s", + g_type_name(gtype), g_type_name(g_type_fundamental(gtype))); + return false; } -} -#endif /* GJS_VERBOSE_ENABLE_GI_USAGE */ -bool -gjs_define_info(JSContext *context, - JS::HandleObject in_object, - GIBaseInfo *info, - bool *defined) -{ -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(info); -#endif + auto struct_info = info.as(); + // We don't want GType structures in the namespace, we expose their fields + // as vfuncs and their methods as static methods + if (struct_info && struct_info->is_gtype_struct()) { + *defined = false; + return true; + } - *defined = true; + if (struct_info) + return BoxedPrototype::define_class(cx, in_object, struct_info.value()); - switch (g_base_info_get_type(info)) { - case GI_INFO_TYPE_FUNCTION: - { - JSObject *f; - f = gjs_define_function(context, in_object, 0, (GICallableInfo*) info); - if (f == NULL) - return false; - } - break; - case GI_INFO_TYPE_OBJECT: - { - GType gtype; - gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info); - - if (g_type_is_a (gtype, G_TYPE_PARAM)) { - if (!gjs_define_param_class(context, in_object)) - return false; - } else if (g_type_is_a (gtype, G_TYPE_OBJECT)) { - JS::RootedObject ignored1(context), ignored2(context); - if (!ObjectPrototype::define_class(context, in_object, info, - gtype, nullptr, 0, &ignored1, - &ignored2)) - return false; - } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { - JS::RootedObject ignored(context); - if (!FundamentalPrototype::define_class(context, in_object, - info, &ignored)) - return false; - } else { - gjs_throw (context, - "Unsupported type %s, deriving from fundamental %s", - g_type_name(gtype), g_type_name(g_type_fundamental(gtype))); - return false; - } - } - break; - case GI_INFO_TYPE_STRUCT: - /* We don't want GType structures in the namespace, - we expose their fields as vfuncs and their methods - as static methods - */ - if (g_struct_info_is_gtype_struct((GIStructInfo*) info)) { - *defined = false; - break; - } - /* Fall through */ + if (auto union_info = info.as()) + return UnionPrototype::define_class(cx, in_object, union_info.value()); - case GI_INFO_TYPE_BOXED: - if (!BoxedPrototype::define_class(context, in_object, info)) - return false; - break; - case GI_INFO_TYPE_UNION: - if (!UnionPrototype::define_class(context, in_object, - (GIUnionInfo*)info)) - return false; - break; - case GI_INFO_TYPE_ENUM: - if (g_enum_info_get_error_domain((GIEnumInfo*) info)) { + if (auto enum_info = info.as()) { + if (!info.is_flags() && enum_info->error_domain()) { /* define as GError subclass */ - if (!ErrorPrototype::define_class(context, in_object, info)) - return false; - break; + return ErrorPrototype::define_class(cx, in_object, + enum_info.value()); } - [[fallthrough]]; - case GI_INFO_TYPE_FLAGS: - if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info)) - return false; - break; - case GI_INFO_TYPE_CONSTANT: - if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info)) - return false; - break; - case GI_INFO_TYPE_INTERFACE: - { - JS::RootedObject ignored1(context), ignored2(context); - if (!InterfacePrototype::create_class( - context, in_object, info, - g_registered_type_info_get_g_type(info), &ignored1, - &ignored2)) - return false; - } - break; - case GI_INFO_TYPE_INVALID: - case GI_INFO_TYPE_INVALID_0: - case GI_INFO_TYPE_CALLBACK: - case GI_INFO_TYPE_VALUE: - case GI_INFO_TYPE_SIGNAL: - case GI_INFO_TYPE_VFUNC: - case GI_INFO_TYPE_PROPERTY: - case GI_INFO_TYPE_FIELD: - case GI_INFO_TYPE_ARG: - case GI_INFO_TYPE_TYPE: - case GI_INFO_TYPE_UNRESOLVED: - default: - gjs_throw(context, "API of type %s not implemented, cannot define %s.%s", - gjs_info_type_name(g_base_info_get_type(info)), - g_base_info_get_namespace(info), - g_base_info_get_name(info)); - return false; + return gjs_define_enumeration(cx, in_object, enum_info.value()); } - return true; + if (auto constant_info = info.as()) + return gjs_define_constant(cx, in_object, constant_info.value()); + + if (auto interface_info = info.as()) { + JS::RootedObject ignored1{cx}, ignored2{cx}; + return InterfacePrototype::create_class(cx, in_object, interface_info, + interface_info->gtype(), + &ignored1, &ignored2); + } + + gjs_throw(cx, "API of type %s not implemented, cannot define %s.%s", + info.type_string(), info.ns(), info.name()); + return false; } /* Get the "unknown namespace", which should be used for unnamespaced types */ @@ -498,17 +377,12 @@ gjs_lookup_private_namespace(JSContext *context) } /* Get the namespace object that the GIBaseInfo should be inside */ -JSObject* -gjs_lookup_namespace_object(JSContext *context, - GIBaseInfo *info) -{ - const char *ns; - - ns = g_base_info_get_namespace(info); +JSObject* gjs_lookup_namespace_object(JSContext* context, + const GI::BaseInfo info) { + const char* ns = info.ns(); if (ns == NULL) { gjs_throw(context, "%s '%s' does not have a namespace", - gjs_info_type_name(g_base_info_get_type(info)), - g_base_info_get_name(info)); + info.type_string(), info.name()); return NULL; } @@ -620,56 +494,6 @@ JSObject* gjs_lookup_namespace_object_by_name(JSContext* cx, return lookup_namespace(cx, global, ns_name); } -const char* -gjs_info_type_name(GIInfoType type) -{ - switch (type) { - case GI_INFO_TYPE_INVALID: - return "INVALID"; - case GI_INFO_TYPE_FUNCTION: - return "FUNCTION"; - case GI_INFO_TYPE_CALLBACK: - return "CALLBACK"; - case GI_INFO_TYPE_STRUCT: - return "STRUCT"; - case GI_INFO_TYPE_BOXED: - return "BOXED"; - case GI_INFO_TYPE_ENUM: - return "ENUM"; - case GI_INFO_TYPE_FLAGS: - return "FLAGS"; - case GI_INFO_TYPE_OBJECT: - return "OBJECT"; - case GI_INFO_TYPE_INTERFACE: - return "INTERFACE"; - case GI_INFO_TYPE_CONSTANT: - return "CONSTANT"; - case GI_INFO_TYPE_UNION: - return "UNION"; - case GI_INFO_TYPE_VALUE: - return "VALUE"; - case GI_INFO_TYPE_SIGNAL: - return "SIGNAL"; - case GI_INFO_TYPE_VFUNC: - return "VFUNC"; - case GI_INFO_TYPE_PROPERTY: - return "PROPERTY"; - case GI_INFO_TYPE_FIELD: - return "FIELD"; - case GI_INFO_TYPE_ARG: - return "ARG"; - case GI_INFO_TYPE_TYPE: - return "TYPE"; - case GI_INFO_TYPE_UNRESOLVED: - return "UNRESOLVED"; - case GI_INFO_TYPE_INVALID_0: - g_assert_not_reached(); - return "----INVALID0----"; - default: - return "???"; - } -} - char* gjs_hyphen_from_camel(const char *camel_name) { @@ -691,13 +515,11 @@ gjs_hyphen_from_camel(const char *camel_name) return g_string_free(s, false); } -JSObject * -gjs_lookup_generic_constructor(JSContext *context, - GIBaseInfo *info) -{ +JSObject* gjs_lookup_generic_constructor(JSContext* context, + const GI::BaseInfo info) { JS::RootedObject in_object{context, gjs_lookup_namespace_object(context, info)}; - const char* constructor_name = g_base_info_get_name(info); + const char* constructor_name = info.name(); if (G_UNLIKELY (!in_object)) return NULL; @@ -709,17 +531,15 @@ gjs_lookup_generic_constructor(JSContext *context, if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Constructor of %s.%s was the wrong type, expected an object", - g_base_info_get_namespace(info), constructor_name); + info.ns(), constructor_name); return NULL; } return &value.toObject(); } -JSObject * -gjs_lookup_generic_prototype(JSContext *context, - GIBaseInfo *info) -{ +JSObject* gjs_lookup_generic_prototype(JSContext* context, + const GI::BaseInfo info) { JS::RootedObject constructor(context, gjs_lookup_generic_constructor(context, info)); if (G_UNLIKELY(!constructor)) @@ -733,7 +553,7 @@ gjs_lookup_generic_prototype(JSContext *context, if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Prototype of %s.%s was the wrong type, expected an object", - g_base_info_get_namespace(info), g_base_info_get_name(info)); + info.ns(), info.name()); return NULL; } @@ -741,46 +561,10 @@ gjs_lookup_generic_prototype(JSContext *context, } JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, - GIBaseInfo* info) { + const GI::BaseInfo info) { JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info)); if (!proto) return nullptr; return JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto); } - -// Handle the case where g_irepository_find_by_gtype() returns a type in Gio -// that should be in GioUnix or GioWin32. This may be an interface, class, or -// boxed. This function only needs to be called if you are going to do something -// with the GIBaseInfo that involves handing a JS object to the user. Otherwise, -// use g_irepository_find_by_gtype() directly. -GIBaseInfo* gjs_lookup_gtype(GIRepository* repo, GType gtype) { - GjsAutoBaseInfo retval = g_irepository_find_by_gtype(repo, gtype); - if (!retval) - return nullptr; - -#if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32)) -# ifdef G_OS_UNIX - static const char* c_prefix = "GUnix"; - static const char* new_ns = "GioUnix"; -# else // G_OS_WIN32 - static const char* c_prefix = "GWin32"; - static const char* new_ns = "GioWin32"; -# endif - - const char* ns = g_base_info_get_namespace(retval); - if (strcmp(ns, "Gio") != 0) - return retval.release(); - - const char* gtype_name = g_type_name(gtype); - if (!g_str_has_prefix(gtype_name, c_prefix)) - return retval.release(); - - const char* new_name = gtype_name + strlen(c_prefix); - GIBaseInfo* new_info = g_irepository_find_by_name(repo, new_ns, new_name); - if (new_info) - return new_info; -#endif // GLib >= 2.79.2 - - return retval.release(); -} diff --git a/gi/repo.h b/gi/repo.h index eccfec144..00cb545a5 100644 --- a/gi/repo.h +++ b/gi/repo.h @@ -7,51 +7,38 @@ #include -#include #include #include +#include +#include "gi/info.h" #include "cjs/macros.h" -#include "util/log.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_repo(JSContext *cx, JS::MutableHandleObject repo); - -[[nodiscard]] const char* gjs_info_type_name(GIInfoType type); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_private_namespace (JSContext *context); GJS_JSAPI_RETURN_CONVENTION -JSObject* gjs_lookup_namespace_object (JSContext *context, - GIBaseInfo *info); +JSObject* gjs_lookup_namespace_object(JSContext*, const GI::BaseInfo); GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_lookup_namespace_object_by_name(JSContext *context, JS::HandleId name); GJS_JSAPI_RETURN_CONVENTION -JSObject * gjs_lookup_generic_constructor (JSContext *context, - GIBaseInfo *info); +JSObject* gjs_lookup_generic_constructor(JSContext*, const GI::BaseInfo); GJS_JSAPI_RETURN_CONVENTION -JSObject * gjs_lookup_generic_prototype (JSContext *context, - GIBaseInfo *info); +JSObject* gjs_lookup_generic_prototype(JSContext*, const GI::BaseInfo); + GJS_JSAPI_RETURN_CONVENTION -JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, - GIBaseInfo* info); +JSObject* gjs_new_object_with_generic_prototype(JSContext*, const GI::BaseInfo); GJS_JSAPI_RETURN_CONVENTION -bool gjs_define_info(JSContext *context, - JS::HandleObject in_object, - GIBaseInfo *info, - bool *defined); +bool gjs_define_info(JSContext*, JS::HandleObject in_object, const GI::BaseInfo, + bool* defined); [[nodiscard]] char* gjs_hyphen_from_camel(const char* camel_name); -[[nodiscard]] GIBaseInfo* gjs_lookup_gtype(GIRepository*, GType); - -#if GJS_VERBOSE_ENABLE_GI_USAGE -void _gjs_log_info_usage(GIBaseInfo *info); -#endif - #endif // GI_REPO_H_ diff --git a/gi/union.cpp b/gi/union.cpp index 6c56bb739..7adf6d5a9 100644 --- a/gi/union.cpp +++ b/gi/union.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include #include @@ -12,9 +12,11 @@ #include #include // for UniqueChars #include +#include #include "gi/arg-inl.h" #include "gi/function.h" +#include "gi/info.h" #include "gi/repo.h" #include "gi/union.h" #include "cjs/jsapi-util.h" @@ -22,7 +24,9 @@ #include "cjs/mem-private.h" #include "util/log.h" -UnionPrototype::UnionPrototype(GIUnionInfo* info, GType gtype) +using mozilla::Maybe; + +UnionPrototype::UnionPrototype(const GI::UnionInfo info, GType gtype) : GIWrapperPrototype(info, gtype) { GJS_INC_COUNTER(union_prototype); } @@ -54,20 +58,17 @@ bool UnionPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, } // Look for methods and other class properties - GjsAutoFunctionInfo method_info = - g_union_info_find_method(info(), prop_name.get()); + Maybe method_info{info().method(prop_name.get())}; if (method_info) { -#if GJS_VERBOSE_ENABLE_GI_USAGE - _gjs_log_info_usage(method_info); -#endif - if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { + method_info->log_usage(); + if (method_info->is_method()) { gjs_debug(GJS_DEBUG_GBOXED, - "Defining method %s in prototype for %s.%s", - method_info.name(), ns(), name()); + "Defining method %s in prototype for %s", + method_info->name(), format_name().c_str()); /* obj is union proto */ - if (!gjs_define_function(context, obj, gtype(), method_info)) + if (!gjs_define_function(context, obj, gtype(), method_info.ref())) return false; *resolved = true; /* we defined the prop in object_proto */ @@ -83,22 +84,10 @@ bool UnionPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, GJS_JSAPI_RETURN_CONVENTION static void* union_new(JSContext* context, JS::HandleObject this_obj, - const JS::CallArgs& args, GIUnionInfo* info) { - int n_methods; - int i; - + const JS::CallArgs& args, const GI::UnionInfo info) { /* Find a zero-args constructor and call it */ - - n_methods = g_union_info_get_n_methods(info); - - for (i = 0; i < n_methods; ++i) { - GIFunctionInfoFlags flags; - - GjsAutoFunctionInfo func_info = g_union_info_get_method(info, i); - - flags = g_function_info_get_flags(func_info); - if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0 && - g_callable_info_get_n_args((GICallableInfo*) func_info) == 0) { + for (GI::AutoFunctionInfo func_info : info.methods()) { + if (func_info.is_constructor() && func_info.n_args() == 0) { GIArgument rval; if (!gjs_invoke_constructor_from_c(context, func_info, this_obj, args, &rval)) @@ -108,7 +97,7 @@ static void* union_new(JSContext* context, JS::HandleObject this_obj, gjs_throw(context, "Unable to construct union type %s as its" "constructor function returned null", - g_base_info_get_name(info)); + info.name()); return nullptr; } @@ -116,8 +105,10 @@ static void* union_new(JSContext* context, JS::HandleObject this_obj, } } - gjs_throw(context, "Unable to construct union type %s since it has no zero-args , can only wrap an existing one", - g_base_info_get_name((GIBaseInfo*) info)); + gjs_throw(context, + "Unable to construct union type %s since it has no zero-args " + ", can only wrap an existing one", + info.name()); return nullptr; } @@ -155,14 +146,13 @@ const struct JSClass UnionBase::klass = { bool UnionPrototype::define_class(JSContext* context, JS::HandleObject in_object, - GIUnionInfo* info) { - GType gtype; + const GI::UnionInfo info) { JS::RootedObject prototype(context), constructor(context); /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ - gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); + GType gtype = info.gtype(); if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return false; @@ -172,25 +162,22 @@ bool UnionPrototype::define_class(JSContext* context, &constructor, &prototype); } -JSObject* UnionInstance::new_for_c_union(JSContext* context, GIUnionInfo* info, +JSObject* UnionInstance::new_for_c_union(JSContext* context, + const GI::UnionInfo info, void* gboxed) { - GType gtype; - if (!gboxed) return nullptr; /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ - gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); - if (gtype == G_TYPE_NONE) { + if (info.gtype() == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return nullptr; } - gjs_debug_marshal(GJS_DEBUG_GBOXED, - "Wrapping union %s %p with JSObject", - g_base_info_get_name((GIBaseInfo *)info), gboxed); + gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping union %s %p with JSObject", + info.name(), gboxed); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype(context, info)); diff --git a/gi/union.h b/gi/union.h index 6ec515942..e3a1e9f95 100644 --- a/gi/union.h +++ b/gi/union.h @@ -7,12 +7,12 @@ #include -#include #include #include #include "gi/cwrapper.h" +#include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/macros.h" #include "util/log.h" @@ -41,15 +41,14 @@ class UnionBase static const JSClass klass; }; -class UnionPrototype : public GIWrapperPrototype { +class UnionPrototype + : public GIWrapperPrototype { friend class GIWrapperPrototype; + GI::AutoUnionInfo, GI::UnionInfo>; friend class GIWrapperBase; - static constexpr InfoType::Tag info_type_tag = InfoType::Union; - - explicit UnionPrototype(GIUnionInfo* info, GType gtype); + explicit UnionPrototype(const GI::UnionInfo info, GType gtype); ~UnionPrototype(void); GJS_JSAPI_RETURN_CONVENTION @@ -62,7 +61,7 @@ class UnionPrototype : public GIWrapperPrototype -#include // IWYU pragma: keep (for find) -#include // IWYU pragma: keep (for swap) +#include +#include +#include #include template diff --git a/gi/value.cpp b/gi/value.cpp index 73dcc4145..174758540 100644 --- a/gi/value.cpp +++ b/gi/value.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -29,6 +29,7 @@ #include #include #include // for InformalValueTypeName, JS_Get... +#include #include "gi/arg-inl.h" #include "gi/arg.h" @@ -38,13 +39,14 @@ #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" +#include "gi/info.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" -#include "gi/repo.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" +#include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" @@ -52,12 +54,13 @@ #include "cjs/objectbox.h" #include "util/log.h" +using mozilla::Maybe, mozilla::Nothing, mozilla::Some; + GJS_JSAPI_RETURN_CONVENTION -static bool gjs_value_from_g_value_internal(JSContext*, JS::MutableHandleValue, - const GValue*, bool no_copy = false, - bool is_introspected_signal = false, - GIArgInfo* = nullptr, - GITypeInfo* = nullptr); +static bool gjs_value_from_g_value_internal( + JSContext*, JS::MutableHandleValue, const GValue*, bool no_copy = false, + bool is_introspected_signal = false, + Maybe> = {}); GJS_JSAPI_RETURN_CONVENTION static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg, @@ -79,12 +82,10 @@ static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg, gjs_arg_set(arg, g_value_get_uint(value)); return true; case G_TYPE_LONG: - gjs_arg_set( // NOLINT(runtime/int) - arg, g_value_get_long(value)); + gjs_arg_set(arg, g_value_get_long(value)); return true; case G_TYPE_ULONG: - gjs_arg_set(arg, g_value_get_ulong(value)); + gjs_arg_set(arg, g_value_get_ulong(value)); return true; case G_TYPE_INT64: gjs_arg_set(arg, int64_t{g_value_get_int64(value)}); @@ -116,18 +117,18 @@ static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg, GType gtype = G_VALUE_TYPE(value); if (g_type_is_a(gtype, G_TYPE_FLAGS)) { - gjs_arg_set(arg, g_value_get_flags(value)); + gjs_arg_set(arg, + g_value_get_flags(value)); return true; } if (g_type_is_a(gtype, G_TYPE_ENUM)) { - gjs_arg_set(arg, g_value_get_enum(value)); + gjs_arg_set(arg, g_value_get_enum(value)); return true; } if (g_type_is_a(gtype, G_TYPE_GTYPE)) { - gjs_arg_set(arg, - g_value_get_gtype(value)); + gjs_arg_set(arg, g_value_get_gtype(value)); return true; } @@ -138,15 +139,15 @@ static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg, } } - gjs_throw(cx, "No know GArgument conversion for %s", + gjs_throw(cx, "No known GIArgument conversion for %s", G_VALUE_TYPE_NAME(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool maybe_release_signal_value(JSContext* cx, - GjsAutoArgInfo const& arg_info, - GITypeInfo* type_info, + const GI::ArgInfo arg_info, + const GI::TypeInfo type_info, const GValue* gvalue, GITransfer transfer) { if (transfer == GI_TRANSFER_NOTHING) @@ -171,23 +172,23 @@ static bool maybe_release_signal_value(JSContext* cx, * only works for signals on introspected GObjects, not signals on GJS-defined * GObjects nor standalone closures. The return value must be unreffed. */ -[[nodiscard]] static GjsAutoSignalInfo get_signal_info_if_available( - GSignalQuery* signal_query) { +[[nodiscard]] +static Maybe get_signal_info_if_available( + const GI::Repository& repo, GSignalQuery* signal_query) { if (!signal_query->itype) - return nullptr; + return {}; - GjsAutoBaseInfo obj = - g_irepository_find_by_gtype(nullptr, signal_query->itype); - if (!obj) - return nullptr; + Maybe info{ + repo.find_by_gtype(signal_query->itype)}; + if (!info) + return {}; - GIInfoType info_type = obj.type(); - if (info_type == GI_INFO_TYPE_OBJECT) - return g_object_info_find_signal(obj, signal_query->signal_name); - else if (info_type == GI_INFO_TYPE_INTERFACE) - return g_interface_info_find_signal(obj, signal_query->signal_name); + if (auto object_info = info->as()) + return object_info->signal(signal_query->signal_name); + else if (auto interface_info = info->as()) + return interface_info->signal(signal_query->signal_name); - return nullptr; + return {}; } /* @@ -197,8 +198,8 @@ static bool maybe_release_signal_value(JSContext* cx, 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, - GIArgInfo* array_length_arg_info, GITypeInfo* array_length_type_info, + const GI::TypeInfo array_type_info, const GValue* array_value, + Maybe> array_length_info, const GValue* array_length_value, bool no_copy, bool is_introspected_signal) { JS::RootedValue array_length(context); @@ -207,12 +208,12 @@ static bool gjs_value_from_array_and_length_values( g_assert(G_VALUE_HOLDS_INT(array_length_value)); if (!gjs_value_from_g_value_internal( - context, &array_length, array_length_value, no_copy, is_introspected_signal, - array_length_arg_info, array_length_type_info)) + context, &array_length, array_length_value, no_copy, + is_introspected_signal, array_length_info)) return false; GIArgument array_arg; - gjs_arg_set(&array_arg, g_value_get_pointer(array_value)); + gjs_arg_set(&array_arg, Gjs::gvalue_get(array_value)); return gjs_value_from_explicit_array( context, value_p, array_type_info, @@ -237,17 +238,26 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, context = m_cx; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); - if (JS::RuntimeHeapIsCollecting()) { + if (G_UNLIKELY(!gjs->is_owner_thread()) || JS::RuntimeHeapIsCollecting()) { GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint; std::ostringstream message; - message << "Attempting to call back into JSAPI during the sweeping " + if (!gjs->is_owner_thread()) { + message << "Attempting to call back into JSAPI on a different " + "thread. This is most likely caused by an API not " + "intended to be used in JS. Because it would crash the " + "application, it has been blocked."; + } else { + 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."; + message << "\n" << gjs_dumpstack_string(); + } if (hint) { gpointer instance; g_signal_query(hint->signal_id, &signal_query); @@ -257,7 +267,6 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, << " on " << g_type_name(G_TYPE_FROM_INSTANCE(instance)) << " " << instance << "."; } - message << "\n" << gjs_dumpstack_string(); g_critical("%s", message.str().c_str()); return; } @@ -290,33 +299,37 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, */ struct ArgumentDetails { int array_len_index_for = -1; + // FIXME don't use StackInfo here? + Maybe> info; bool skip = false; - GITypeInfo type_info; - GjsAutoArgInfo arg_info; + + // Convenience methods, unsafe if there is no introspection info + GI::StackArgInfo& arg_info() { return info->first; } + GI::StackTypeInfo& type_info() { return info->second; } }; std::vector args_details(n_param_values); bool needs_cleanup = false; - GjsAutoSignalInfo signal_info = get_signal_info_if_available(&signal_query); + GI::Repository repo; + Maybe signal_info{ + get_signal_info_if_available(repo, &signal_query)}; if (signal_info) { /* Start at argument 1, skip the instance parameter */ for (i = 1; i < n_param_values; ++i) { - int array_len_pos; - 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(&arg_details.type_info); - if (array_len_pos != -1) { - args_details[array_len_pos + 1].skip = true; - arg_details.array_len_index_for = array_len_pos + 1; + arg_details.info.emplace(); + signal_info->load_arg(i - 1, &arg_details.arg_info()); + arg_details.info->first.load_type(&arg_details.type_info()); + + Maybe array_len_pos = + arg_details.type_info().array_length_index(); + if (array_len_pos) { + args_details[*array_len_pos + 1].skip = true; + arg_details.array_len_index_for = *array_len_pos + 1; } - if (!needs_cleanup && - g_arg_info_get_ownership_transfer(arg_details.arg_info) != - GI_TRANSFER_NOTHING) + if (!needs_cleanup && arg_details.arg_info().ownership_transfer() != + GI_TRANSFER_NOTHING) needs_cleanup = true; } } @@ -348,13 +361,14 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, ArgumentDetails& array_len_details = args_details[arg_details.array_len_index_for]; res = gjs_value_from_array_and_length_values( - context, &argv_to_append, &arg_details.type_info, gval, - array_len_details.arg_info, &array_len_details.type_info, - array_len_gval, no_copy, is_introspected_signal); + // FIXME: what if no type_info + context, &argv_to_append, arg_details.type_info(), gval, + array_len_details.info, array_len_gval, no_copy, + is_introspected_signal); } else { res = gjs_value_from_g_value_internal( context, &argv_to_append, gval, no_copy, is_introspected_signal, - arg_details.arg_info, &arg_details.type_info); + arg_details.info); } if (!res) { @@ -389,17 +403,15 @@ void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, if (needs_cleanup) { for (i = 0; i < n_param_values; ++i) { ArgumentDetails& arg_details = args_details[i]; - if (!arg_details.arg_info) + if (!arg_details.info) continue; - GITransfer transfer = - g_arg_info_get_ownership_transfer(arg_details.arg_info); - + GITransfer transfer = arg_details.arg_info().ownership_transfer(); if (transfer == GI_TRANSFER_NOTHING) continue; - if (!maybe_release_signal_value(context, arg_details.arg_info, - &arg_details.type_info, + if (!maybe_release_signal_value(context, arg_details.arg_info(), + arg_details.type_info(), ¶m_values[i], transfer)) { gjs_log_exception(context); return; @@ -571,14 +583,14 @@ gjs_value_to_g_value_internal(JSContext *context, * everything automatically */ if (value.isNull()) { - g_value_set_string(gvalue, NULL); + Gjs::gvalue_set(gvalue, nullptr); } else if (value.isString()) { JS::RootedString str(context, value.toString()); JS::UniqueChars utf8_string(JS_EncodeStringToUTF8(context, str)); if (!utf8_string) return false; - g_value_set_string(gvalue, utf8_string.get()); + Gjs::gvalue_set(gvalue, utf8_string.get()); } else { return throw_expect_type(context, value, "string"); } @@ -587,7 +599,7 @@ gjs_value_to_g_value_internal(JSContext *context, if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { - g_value_set_schar(gvalue, static_cast(i)); + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "char", 0, out_of_range); } @@ -596,15 +608,15 @@ gjs_value_to_g_value_internal(JSContext *context, if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { - g_value_set_uchar(gvalue, (unsigned char)i); + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "unsigned char", 0, out_of_range); } } else if (gtype == G_TYPE_INT) { gint32 i; - if (Gjs::js_value_to_c(context, value, &i)) { - g_value_set_int(gvalue, i); + if (Gjs::js_value_to_c(context, value, &i)) { + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "integer"); } @@ -613,15 +625,15 @@ gjs_value_to_g_value_internal(JSContext *context, if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { - g_value_set_int64(gvalue, i); + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "64-bit integer", 0, out_of_range); } } else if (gtype == G_TYPE_DOUBLE) { gdouble d; - if (Gjs::js_value_to_c(context, value, &d)) { - g_value_set_double(gvalue, d); + if (Gjs::js_value_to_c(context, value, &d)) { + Gjs::gvalue_set(gvalue, d); } else { return throw_expect_type(context, value, "double"); } @@ -630,14 +642,14 @@ gjs_value_to_g_value_internal(JSContext *context, if (Gjs::js_value_to_c_checked(context, value, &d, &out_of_range) && !out_of_range) { - g_value_set_float(gvalue, d); + Gjs::gvalue_set(gvalue, d); } else { return throw_expect_type(context, value, "float", 0, out_of_range); } } else if (gtype == G_TYPE_UINT) { guint32 i; - if (Gjs::js_value_to_c(context, value, &i)) { - g_value_set_uint(gvalue, i); + if (Gjs::js_value_to_c(context, value, &i)) { + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "unsigned integer"); } @@ -646,14 +658,14 @@ gjs_value_to_g_value_internal(JSContext *context, if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { - g_value_set_uint64(gvalue, i); + Gjs::gvalue_set(gvalue, i); } else { return throw_expect_type(context, value, "unsigned 64-bit integer", 0, out_of_range); } } else if (gtype == G_TYPE_BOOLEAN) { /* JS::ToBoolean() can't fail */ - g_value_set_boolean(gvalue, JS::ToBoolean(value)); + Gjs::gvalue_set(gvalue, JS::ToBoolean(value)); } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { GObject *gobj; @@ -663,7 +675,7 @@ gjs_value_to_g_value_internal(JSContext *context, /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); - if (!ObjectBase::typecheck(context, obj, nullptr, gtype) || + if (!ObjectBase::typecheck(context, obj, gtype) || !ObjectBase::to_c_ptr(context, obj, &gobj)) return false; if (!gobj) @@ -672,7 +684,7 @@ gjs_value_to_g_value_internal(JSContext *context, return throw_expect_type(context, value, "object", gtype); } - g_value_set_object(gvalue, gobj); + Gjs::gvalue_set(gvalue, gobj); } else if (gtype == G_TYPE_STRV) { if (value.isNull()) return true; @@ -759,21 +771,20 @@ gjs_value_to_g_value_internal(JSContext *context, JS::InformalValueTypeName(value)); return false; } else { - GjsAutoBaseInfo registered = - g_irepository_find_by_gtype(nullptr, gtype); + GI::Repository repo; + Maybe registered{ + repo.find_by_gtype(gtype)}; /* We don't necessarily have the typelib loaded when we first see the structure... */ if (registered) { - GIInfoType info_type = registered.type(); - - if (info_type == GI_INFO_TYPE_STRUCT && - g_struct_info_is_foreign( - registered.as())) { + if (auto struct_info = + registered->as(); + struct_info && struct_info->is_foreign()) { GIArgument arg; if (!gjs_struct_foreign_convert_to_gi_argument( - context, value, registered, nullptr, + context, value, struct_info.value(), nullptr, GJS_ARGUMENT_ARGUMENT, GI_TRANSFER_NOTHING, GjsArgumentFlags::MAY_BE_NULL, &arg)) return false; @@ -790,11 +801,11 @@ gjs_value_to_g_value_internal(JSContext *context, loading the typelib. */ if (!gboxed) { - if (UnionBase::typecheck(context, obj, nullptr, gtype, - GjsTypecheckNoThrow())) { + if (UnionBase::typecheck(context, obj, gtype, + GjsTypecheckNoThrow{})) { gboxed = UnionBase::to_c_ptr(context, obj); } else { - if (!BoxedBase::typecheck(context, obj, nullptr, gtype)) + if (!BoxedBase::typecheck(context, obj, gtype)) return false; gboxed = BoxedBase::to_c_ptr(context, obj); @@ -819,7 +830,7 @@ gjs_value_to_g_value_internal(JSContext *context, } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); - if (!BoxedBase::typecheck(context, obj, nullptr, G_TYPE_VARIANT)) + if (!BoxedBase::typecheck(context, obj, G_TYPE_VARIANT)) return false; variant = BoxedBase::to_c_ptr(context, obj); @@ -829,13 +840,13 @@ gjs_value_to_g_value_internal(JSContext *context, return throw_expect_type(context, value, "boxed type", gtype); } - g_value_set_variant (gvalue, variant); + Gjs::gvalue_set(gvalue, variant); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { int64_t value_int64; - if (Gjs::js_value_to_c(context, value, &value_int64)) { + if (Gjs::js_value_to_c(context, value, &value_int64)) { GEnumValue *v; - GjsAutoTypeClass enum_class(gtype); + Gjs::AutoTypeClass enum_class{gtype}; /* See arg.c:_gjs_enum_to_int() */ v = g_enum_get_value(enum_class, (int)value_int64); @@ -853,7 +864,7 @@ gjs_value_to_g_value_internal(JSContext *context, } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { int64_t value_int64; - if (Gjs::js_value_to_c(context, value, &value_int64)) { + if (Gjs::js_value_to_c(context, value, &value_int64)) { if (!_gjs_flags_value_is_valid(context, gtype, value_int64)) return false; @@ -889,7 +900,7 @@ gjs_value_to_g_value_internal(JSContext *context, JS::RootedObject obj(context, &value.toObject()); if (!gjs_gtype_get_actual_gtype(context, obj, &type)) return false; - g_value_set_gtype(gvalue, type); + Gjs::gvalue_set(gvalue, type); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (value.isNull()) { /* Nothing to do */ @@ -905,10 +916,10 @@ gjs_value_to_g_value_internal(JSContext *context, * e.g. ClutterUnit. */ gint32 i; - if (Gjs::js_value_to_c(context, value, &i)) { + if (Gjs::js_value_to_c(context, value, &i)) { GValue int_value = { 0, }; g_value_init(&int_value, G_TYPE_INT); - g_value_set_int(&int_value, i); + Gjs::gvalue_set(&int_value, i); g_value_transform(&int_value, gvalue); } else { return throw_expect_type(context, value, "integer"); @@ -954,7 +965,9 @@ gjs_value_to_g_value_no_copy(JSContext *context, return gjs_value_to_g_value_internal(context, value, gvalue, true); } -[[nodiscard]] static JS::Value convert_int_to_enum(GType gtype, int v) { +[[nodiscard]] +static JS::Value convert_int_to_enum(const GI::Repository& repo, GType gtype, + int64_t v) { double v_double; if (v > 0 && v < G_MAXINT) { @@ -962,7 +975,8 @@ gjs_value_to_g_value_no_copy(JSContext *context, v_double = v; } else { /* Need to distinguish between negative integers and unsigned integers */ - GjsAutoEnumInfo info = g_irepository_find_by_gtype(nullptr, gtype); + Maybe info{ + repo.find_by_gtype(gtype)}; // Native enums don't have type info, assume // they are signed to avoid crashing when @@ -970,7 +984,7 @@ gjs_value_to_g_value_no_copy(JSContext *context, if (!info) { v_double = int64_t(v); } else { - v_double = _gjs_enum_from_int(info, v); + v_double = info->enum_from_int(v); } } @@ -978,12 +992,11 @@ 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, - bool is_introspected_signal, - GIArgInfo* arg_info, - GITypeInfo* type_info) { +static bool gjs_value_from_g_value_internal( + JSContext* context, JS::MutableHandleValue value_p, const GValue* gvalue, + bool no_copy, bool is_introspected_signal, + Maybe> + introspection_info) { GType gtype; gtype = G_VALUE_TYPE(gvalue); @@ -1004,45 +1017,53 @@ static bool gjs_value_from_g_value_internal(JSContext* context, return true; } - if (gtype == G_TYPE_STRING) { - 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); - value_p.setInt32(v); - } else if (gtype == G_TYPE_UCHAR) { - unsigned char v; - v = g_value_get_uchar(gvalue); - value_p.setInt32(v); - } else if (gtype == G_TYPE_INT) { - int v; - v = g_value_get_int(gvalue); - value_p.set(JS::NumberValue(v)); - } else if (gtype == G_TYPE_UINT) { - guint v; - v = g_value_get_uint(gvalue); - value_p.setNumber(v); - } else if (gtype == G_TYPE_DOUBLE) { - double d; - d = g_value_get_double(gvalue); - value_p.setNumber(JS::CanonicalizeNaN(d)); - } else if (gtype == G_TYPE_FLOAT) { - double d; - d = g_value_get_float(gvalue); - value_p.setNumber(JS::CanonicalizeNaN(d)); - } else if (gtype == G_TYPE_BOOLEAN) { - bool v; - v = g_value_get_boolean(gvalue); - value_p.setBoolean(!!v); - } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + switch (gtype) { + case G_TYPE_CHAR: + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_UCHAR: + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_INT: + return Gjs::c_value_to_js(context, Gjs::gvalue_get(gvalue), + value_p); + case G_TYPE_UINT: + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_LONG: + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_ULONG: + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(gvalue), + value_p); + case G_TYPE_INT64: + return Gjs::c_value_to_js_checked( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_UINT64: + return Gjs::c_value_to_js_checked( + context, Gjs::gvalue_get(gvalue), value_p); + case G_TYPE_DOUBLE: + return Gjs::c_value_to_js(context, Gjs::gvalue_get(gvalue), + value_p); + case G_TYPE_FLOAT: + return Gjs::c_value_to_js(context, Gjs::gvalue_get(gvalue), + value_p); + case G_TYPE_BOOLEAN: + return Gjs::c_value_to_js(context, Gjs::gvalue_get(gvalue), + value_p); + case G_TYPE_STRING: + return Gjs::c_value_to_js(context, Gjs::gvalue_get(gvalue), + value_p); + } + + if (g_type_is_a(gtype, G_TYPE_OBJECT) || + g_type_is_a(gtype, G_TYPE_INTERFACE)) { return ObjectInstance::set_value_from_gobject( - context, static_cast(g_value_get_object(gvalue)), - value_p); + context, Gjs::gvalue_get(gvalue), value_p); } else if (gtype == G_TYPE_STRV) { - if (!gjs_array_from_strv (context, - value_p, - (const char**) g_value_get_boxed (gvalue))) { + if (!gjs_array_from_strv(context, value_p, + Gjs::gvalue_get(gvalue))) { gjs_throw(context, "Failed to convert strv to array"); return false; } @@ -1061,46 +1082,50 @@ static bool gjs_value_from_g_value_internal(JSContext* context, return true; } - if (!is_introspected_signal || !arg_info) { + if (!is_introspected_signal || !introspection_info) { gjs_throw(context, "Unknown signal"); return false; } + const GI::ArgInfo arg_info = introspection_info->first; + const GI::TypeInfo type_info = introspection_info->second; - GITransfer transfer = g_arg_info_get_ownership_transfer(arg_info); - GjsAutoTypeInfo element_info = g_type_info_get_param_type(type_info, 0); - if (!gjs_array_from_g_value_array(context, value_p, element_info, - transfer, gvalue)) { + if (!gjs_array_from_g_value_array( + context, value_p, type_info.element_type(), + arg_info.ownership_transfer(), gvalue)) { gjs_throw(context, "Failed to convert array"); return false; } } else if (gtype == G_TYPE_HASH_TABLE) { - if (!arg_info) { + if (!introspection_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)))) { + const GI::ArgInfo arg_info = introspection_info->first; + const GI::TypeInfo type_info = introspection_info->second; + + GI::AutoTypeInfo key_info{type_info.key_type()}; + GI::AutoTypeInfo value_info{type_info.value_type()}; + GITypeTag key_tag = key_info.tag(); + GITypeTag val_tag = value_info.tag(); + + auto* ghash = Gjs::gvalue_get(gvalue); + if (GI_TYPE_TAG_IS_BASIC(key_tag) && GI_TYPE_TAG_IS_BASIC(val_tag)) { + if (!gjs_value_from_basic_ghash(context, value_p, key_tag, val_tag, + ghash)) + return false; + } else if (!gjs_object_from_g_hash( + context, value_p, key_info, value_info, + arg_info.ownership_transfer(), ghash)) { 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; - if (g_type_is_a(gtype, G_TYPE_BOXED)) - gboxed = g_value_get_boxed(gvalue); - else - gboxed = g_value_get_variant(gvalue); - - if (gtype == ObjectBox::gtype()) { - obj = ObjectBox::object_for_c_ptr(context, - static_cast(gboxed)); + if (g_type_is_a(gtype, ObjectBox::gtype())) { + obj = ObjectBox::object_for_c_ptr( + context, Gjs::gvalue_get(gvalue)); if (!obj) return false; value_p.setObject(*obj); @@ -1109,8 +1134,8 @@ static bool gjs_value_from_g_value_internal(JSContext* context, /* special case GError */ if (gtype == G_TYPE_ERROR) { - obj = ErrorInstance::object_for_c_ptr(context, - static_cast(gboxed)); + obj = ErrorInstance::object_for_c_ptr( + context, Gjs::gvalue_get(gvalue)); if (!obj) return false; value_p.setObject(*obj); @@ -1120,12 +1145,13 @@ static bool gjs_value_from_g_value_internal(JSContext* context, /* special case GValue */ if (gtype == G_TYPE_VALUE) { return gjs_value_from_g_value(context, value_p, - static_cast(gboxed)); + Gjs::gvalue_get(gvalue)); } /* The only way to differentiate unions and structs is from * their g-i info as both GBoxed */ - GjsAutoBaseInfo info = gjs_lookup_gtype(nullptr, gtype); + GI::Repository repo; + Maybe info{repo.find_by_gtype(gtype)}; if (!info) { gjs_throw(context, "No introspection information found for %s", @@ -1133,57 +1159,61 @@ static bool gjs_value_from_g_value_internal(JSContext* context, return false; } - if (info.type() == GI_INFO_TYPE_STRUCT && - g_struct_info_is_foreign(info)) { - GIArgument arg; - gjs_arg_set(&arg, gboxed); - return gjs_struct_foreign_convert_from_gi_argument(context, value_p, - info, &arg); - } + void* gboxed = Gjs::gvalue_get(gvalue); + if (auto struct_info = info->as()) { + if (struct_info->is_foreign()) { + GIArgument arg; + gjs_arg_set(&arg, gboxed); + return gjs_struct_foreign_convert_from_gi_argument( + context, value_p, struct_info.value(), &arg); + } - GIInfoType type = info.type(); - if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) { - if (no_copy) - obj = BoxedInstance::new_for_c_struct(context, info, gboxed, - BoxedInstance::NoCopy()); - else - obj = BoxedInstance::new_for_c_struct(context, info, gboxed); - } else if (type == GI_INFO_TYPE_UNION) { - obj = UnionInstance::new_for_c_union(context, info, gboxed); + if (no_copy) { + obj = BoxedInstance::new_for_c_struct( + context, struct_info.value(), gboxed, + BoxedInstance::NoCopy{}); + } else { + obj = BoxedInstance::new_for_c_struct( + context, struct_info.value(), gboxed); + } + } else if (auto union_info = info->as()) { + obj = UnionInstance::new_for_c_union(context, union_info.value(), + gboxed); } else { - gjs_throw(context, "Unexpected introspection type %d for %s", - info.type(), g_type_name(gtype)); + gjs_throw(context, "Unexpected introspection type %s for %s", + info->type_string(), g_type_name(gtype)); return false; } value_p.setObjectOrNull(obj); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { - value_p.set(convert_int_to_enum(gtype, g_value_get_enum(gvalue))); + GI::Repository repo; + value_p.set(convert_int_to_enum( + repo, gtype, Gjs::gvalue_get(gvalue))); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { - GParamSpec *gparam; + GParamSpec* gparam = Gjs::gvalue_get(gvalue); JSObject *obj; - gparam = g_value_get_param(gvalue); - obj = gjs_param_from_g_param(context, gparam); value_p.setObjectOrNull(obj); } else if (is_introspected_signal && g_type_is_a(gtype, G_TYPE_POINTER)) { - if (!arg_info) { + if (!introspection_info) { gjs_throw(context, "Unknown signal."); return false; } + const GI::TypeInfo type_info = introspection_info->second; - 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(!type_info.array_length_index() && + "Check gjs_value_from_array_and_length_values() before " + "calling gjs_value_from_g_value_internal()"); GIArgument arg; - gjs_arg_set(&arg, g_value_get_pointer(gvalue)); + gjs_arg_set(&arg, Gjs::gvalue_get(gvalue)); return gjs_value_from_gi_argument(context, value_p, type_info, &arg, true); } else if (gtype == G_TYPE_GTYPE) { - GType gvalue_gtype = g_value_get_gtype(gvalue); + GType gvalue_gtype = Gjs::gvalue_get(gvalue); if (gvalue_gtype == 0) { value_p.setNull(); @@ -1197,25 +1227,23 @@ static bool gjs_value_from_g_value_internal(JSContext* context, value_p.setObject(*obj); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { - if (g_value_get_pointer(gvalue) != nullptr) { + if (Gjs::gvalue_get(gvalue) != nullptr) { gjs_throw(context, "Can't convert non-null pointer to JS value"); return false; } } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) { GValue double_value = { 0, }; - double v; g_value_init(&double_value, G_TYPE_DOUBLE); g_value_transform(gvalue, &double_value); - v = g_value_get_double(&double_value); - value_p.setNumber(v); + return Gjs::c_value_to_js( + context, Gjs::gvalue_get(&double_value), value_p); } else if (g_value_type_transformable(gtype, G_TYPE_INT)) { GValue int_value = { 0, }; - int v; g_value_init(&int_value, G_TYPE_INT); g_value_transform(gvalue, &int_value); - v = g_value_get_int(&int_value); - value_p.set(JS::NumberValue(v)); + return Gjs::c_value_to_js(context, Gjs::gvalue_get(&int_value), + value_p); } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { /* The gtype is none of the above, it should be a custom fundamental type. */ diff --git a/gi/value.h b/gi/value.h index 047430718..1f32dc93b 100644 --- a/gi/value.h +++ b/gi/value.h @@ -7,13 +7,22 @@ #include +#include + +#include // for ostringstream +#include // for string +#include #include // for move, swap #include // for vector #include +#include // for FALSE, g_clear_pointer, g_free, g_variant_... #include +#include "gi/arg-types-inl.h" +#include "gi/utils-inl.h" +#include "cjs/auto.h" #include "cjs/macros.h" namespace Gjs { @@ -59,6 +68,179 @@ struct AutoGValue : GValue { void steal() { *static_cast(this) = G_VALUE_INIT; } ~AutoGValue() { g_value_unset(this); } }; + +/* This is based on what GMarshalling does, it is an unsupported API but + * gjs can be considered a glib implementation for JS, so it is fine + * to do this, but we need to be in sync with gmarshal.c in GLib. + * https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gmarshal.c + */ + +template +inline constexpr Tag::RealT gvalue_get(const GValue* gvalue) { + if constexpr (std::is_same_v) + return gvalue->data[0].v_int != FALSE; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_int != FALSE; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_int; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_int; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_uint; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_int; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_uint; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_long; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_ulong; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_int64; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_uint64; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_long; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_ulong; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_float; + else if constexpr (std::is_same_v) + return gvalue->data[0].v_double; + else if constexpr (std::is_same_v) + return gjs_pointer_to_int(gvalue->data[0].v_pointer); + else if constexpr (!std::is_pointer_v) + static_assert(std::is_pointer_v, + "Scalar type not properly handled"); + else + return static_cast(gvalue->data[0].v_pointer); +} + +template +void gvalue_set(GValue* gvalue, Tag::RealT value) { + if constexpr (std::is_same_v) + gvalue->data[0].v_int = value != FALSE; + else if constexpr (std::is_same_v) + gvalue->data[0].v_int = value != false; + else if constexpr (std::is_same_v) + gvalue->data[0].v_int = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_int = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_uint = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_int = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_uint = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_long = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_ulong = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_int64 = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_uint64 = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_long = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_ulong = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_float = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_double = value; + else if constexpr (std::is_same_v) + gvalue->data[0].v_pointer = gjs_int_to_pointer(value); + else + static_assert(!std::is_scalar_v>, + "Scalar type not properly handled"); +} + +// Specialization for types where TAG and RealT are the same type, to allow +// inferring template parameter +template , T>>> +inline void gvalue_set(GValue* gvalue, T value) { + gvalue_set(gvalue, value); +} + +template +void gvalue_set(GValue* gvalue, T* value) = delete; + +template <> +inline void gvalue_set(GValue* gvalue, char* value) { + g_clear_pointer(&gvalue->data[0].v_pointer, g_free); + gvalue->data[0].v_pointer = g_strdup(value); +} + +template <> +inline void gvalue_set(GValue* gvalue, GObject* value) { + g_set_object(&gvalue->data[0].v_pointer, value); +} + +template <> +inline void gvalue_set(GValue* gvalue, GVariant* value) { + g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), + g_variant_unref); + gvalue->data[0].v_pointer = value ? g_variant_ref(value) : nullptr; +} + +template +void gvalue_set(GValue* gvalue, std::nullptr_t) { + if constexpr (std::is_same_v) { + g_clear_pointer(&gvalue->data[0].v_pointer, g_free); + } else if constexpr (std::is_same_v) { + g_set_object(&gvalue->data[0].v_pointer, nullptr); + } else if constexpr (std::is_same_v) { + g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), + g_variant_unref); + gvalue->data[0].v_pointer = nullptr; + } else { + static_assert(!std::is_pointer_v, "Not a known pointer type"); + } +} + +template +void gvalue_take(GValue* gvalue, Tag::RealT value) { + using T = Tag::RealT; + if constexpr (!std::is_pointer_v) { + return gvalue_set(gvalue, value); + } + + if constexpr (std::is_same_v) { + g_clear_pointer(&gvalue->data[0].v_pointer, g_free); + gvalue->data[0].v_pointer = g_steal_pointer(&value); + } else if constexpr (std::is_same_v) { + g_clear_object(&gvalue->data[0].v_pointer); + gvalue->data[0].v_pointer = g_steal_pointer(&value); + } else if constexpr (std::is_same_v) { + g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), + g_variant_unref); + gvalue->data[0].v_pointer = value; + } else { + static_assert(!std::is_pointer_v, "Not a known pointer type"); + } +} + +template +std::string gvalue_to_string(GValue* gvalue) { + auto str = + std::string("GValue of type ") + G_VALUE_TYPE_NAME(gvalue) + ": "; + + if constexpr (std::is_same_v) { + str += std::string("\"") + Gjs::gvalue_get(gvalue) + '"'; + } else if constexpr (std::is_same_v) { + AutoChar variant{g_variant_print(Gjs::gvalue_get(gvalue), true)}; + str += std::string("<") + variant.get() + '>'; + } else if constexpr (std::is_arithmetic_v) { + str += std::to_string(Gjs::gvalue_get(gvalue)); + } else { + std::ostringstream out; + out << Gjs::gvalue_get(gvalue); + str += out.str(); + } + return str; +} + } // namespace Gjs using AutoGValueVector = std::vector; diff --git a/gi/wrapperutils.cpp b/gi/wrapperutils.cpp index 4243404d0..bdd60d085 100644 --- a/gi/wrapperutils.cpp +++ b/gi/wrapperutils.cpp @@ -6,22 +6,26 @@ #include -#include #include #include +#include #include "gi/function.h" +#include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" +using mozilla::Maybe; + /* Default spidermonkey toString is worthless. Replace it * with something that gives us both the introspection name * and a memory address. */ bool gjs_wrapper_to_string_func(JSContext* context, JSObject* this_obj, - const char* objtype, GIBaseInfo* info, - GType gtype, const void* native_address, + const char* objtype, + Maybe info, GType gtype, + const void* native_address, JS::MutableHandleValue rval) { std::ostringstream out; out << '[' << objtype; @@ -31,8 +35,7 @@ bool gjs_wrapper_to_string_func(JSContext* context, JSObject* this_obj, out << " instance wrapper"; if (info) { - out << " GIName:" << g_base_info_get_namespace(info) << "." - << g_base_info_get_name(info); + out << " GIName:" << info->ns() << "." << info->name(); } else { out << " GType:" << g_type_name(gtype); } @@ -59,68 +62,16 @@ bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, return false; } -// These policies work around having separate g_foo_info_get_n_methods() and -// g_foo_info_get_method() functions for different GIInfoTypes. It's not -// possible to use GIFooInfo* as the template parameter, because the GIFooInfo -// structs are all typedefs of GIBaseInfo. It's also not possible to use the -// GIInfoType enum value as the template parameter, because GI_INFO_TYPE_BOXED -// could be either a GIStructInfo or GIUnionInfo. -template -static inline GIStructInfo* no_type_struct(InfoT*) { - return nullptr; -} - -template > -struct InfoMethodsPolicy { - static constexpr decltype(NMethods) n_methods = NMethods; - static constexpr decltype(Method) method = Method; - static constexpr decltype(TypeStruct) type_struct = TypeStruct; -}; - -template <> -struct InfoMethodsPolicy - : InfoMethodsPolicy {}; -template <> -struct InfoMethodsPolicy - : InfoMethodsPolicy< - InfoType::Interface, GIInterfaceInfo, &g_interface_info_get_n_methods, - &g_interface_info_get_method, &g_interface_info_get_iface_struct> {}; -template <> -struct InfoMethodsPolicy - : InfoMethodsPolicy {}; -template <> -struct InfoMethodsPolicy - : InfoMethodsPolicy {}; -template <> -struct InfoMethodsPolicy - : InfoMethodsPolicy { -}; - -template +template bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, - GType gtype, GIBaseInfo* info) { - int n_methods = InfoMethodsPolicy::n_methods(info); - - for (int ix = 0; ix < n_methods; ix++) { - GjsAutoFunctionInfo meth_info = - InfoMethodsPolicy::method(info, ix); - GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); - + GType gtype, const GI::UnownedInfo info) { + for (GI::AutoFunctionInfo meth_info : info.methods()) { // Anything that isn't a method we put on the constructor. This // includes introspection methods, as well as static // methods. We may want to change this to use // GI_FUNCTION_IS_CONSTRUCTOR and GI_FUNCTION_IS_STATIC or the like // in the future. - if (!(flags & GI_FUNCTION_IS_METHOD)) { + if (!meth_info.is_method()) { if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } @@ -128,19 +79,15 @@ bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, // Also define class/interface methods if there is a gtype struct - GjsAutoStructInfo type_struct = InfoMethodsPolicy::type_struct(info); - // Not an error for it to be null even in the case of Object and Interface; - // documentation says g_object_info_get_class_struct() and - // g_interface_info_get_iface_struct() can validly return a null pointer. + Maybe type_struct; + if constexpr (TAG == GI::InfoTag::OBJECT) + type_struct = info.class_struct(); + else if constexpr (TAG == GI::InfoTag::INTERFACE) + type_struct = info.iface_struct(); if (!type_struct) return true; - n_methods = g_struct_info_get_n_methods(type_struct); - - for (int ix = 0; ix < n_methods; ix++) { - GjsAutoFunctionInfo meth_info = - g_struct_info_get_method(type_struct, ix); - + for (GI::AutoFunctionInfo meth_info : type_struct->methods()) { if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } @@ -149,13 +96,13 @@ bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, } // All possible instantiations are needed -template bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); -template bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); -template bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); -template bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); -template bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); +template bool gjs_define_static_methods( + JSContext*, JS::HandleObject constructor, GType, const GI::EnumInfo); +template bool gjs_define_static_methods( + JSContext*, JS::HandleObject constructor, GType, const GI::InterfaceInfo); +template bool gjs_define_static_methods( + JSContext*, JS::HandleObject constructor, GType, const GI::ObjectInfo); +template bool gjs_define_static_methods( + JSContext*, JS::HandleObject constructor, GType, const GI::StructInfo); +template bool gjs_define_static_methods( + JSContext*, JS::HandleObject constructor, GType, const GI::UnionInfo); diff --git a/gi/wrapperutils.h b/gi/wrapperutils.h index d2fa7eade..bfda28c35 100644 --- a/gi/wrapperutils.h +++ b/gi/wrapperutils.h @@ -10,10 +10,10 @@ #include -#include #include +#include -#include +#include #include #include @@ -29,10 +29,13 @@ #include #include #include // for JS_GetPrototype +#include #include "gi/arg-inl.h" #include "gi/cwrapper.h" +#include "gi/info.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" @@ -46,35 +49,63 @@ class JSTracer; GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj, - const char* objtype, GIBaseInfo* info, + const char* objtype, + mozilla::Maybe info, GType gtype, const void* native_address, JS::MutableHandleValue ret); +GJS_JSAPI_RETURN_CONVENTION +static inline bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj, + const char* objtype, + const GI::BaseInfo info, + GType gtype, + const void* native_address, + JS::MutableHandleValue ret) { + return gjs_wrapper_to_string_func( + cx, this_obj, objtype, mozilla::Some(info), gtype, native_address, ret); +} + bool gjs_wrapper_throw_nonexistent_field(JSContext* cx, GType gtype, const char* field_name); bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, const char* field_name); -namespace InfoType { -enum Tag { Enum, Interface, Object, Struct, Union }; -} - namespace MemoryUse { constexpr JS::MemoryUse GObjectInstanceStruct = JS::MemoryUse::Embedding1; } struct GjsTypecheckNoThrow {}; +// Some types of introspected wrapper permit creating a new type from JS (e.g., +// objects, interfaces.) These JS-created types do not have introspection info +// and so their GIWrapperPrototype::info() methods return Maybe. +// Others do not permit creating a new type from JS (e.g., enums, boxeds.) These +// have GIWrapperPrototype::info() methods that return const FooInfo directly. +// Sometimes we need to have different code for the two cases. +template +struct is_maybe : std::false_type {}; +template +struct is_maybe> : std::true_type {}; + /* * gjs_define_static_methods: * * Defines all static methods from @info on @constructor. Also includes class - * methods for GIObjectInfo, and interface methods for GIInterfaceInfo. + * methods for GI::ObjectInfo, and interface methods for GI::InterfaceInfo. */ -template +template GJS_JSAPI_RETURN_CONVENTION bool gjs_define_static_methods( - JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); + JSContext*, JS::HandleObject constructor, GType, + const GI::UnownedInfo); + +template +GJS_JSAPI_RETURN_CONVENTION inline bool gjs_define_static_methods( + JSContext* cx, JS::HandleObject constructor, GType gtype, + const GI::OwnedInfo& info) { + return gjs_define_static_methods(cx, constructor, gtype, + GI::UnownedInfo{info}); +} /* * GIWrapperBase: @@ -183,17 +214,27 @@ class GIWrapperBase : public CWrapperPointerOps { // should be able to access the GIFooInfo and the GType, but for space // reasons we store them only on Prototype. - [[nodiscard]] GIBaseInfo* info() const { return get_prototype()->info(); } + [[nodiscard]] auto info() const { return get_prototype()->info(); } [[nodiscard]] GType gtype() const { return get_prototype()->gtype(); } // The next three methods are operations derived from the GIFooInfo. [[nodiscard]] const char* type_name() const { return g_type_name(gtype()); } [[nodiscard]] const char* ns() const { - return info() ? g_base_info_get_namespace(info()) : ""; + if constexpr (Prototype::may_not_have_info) { + const auto i = info(); + return i ? i->ns() : ""; + } else { + return info().ns(); + } } [[nodiscard]] const char* name() const { - return info() ? g_base_info_get_name(info()) : type_name(); + if constexpr (Prototype::may_not_have_info) { + const auto i = info(); + return i ? i->name() : type_name(); + } else { + return info().name(); + } } [[nodiscard]] std::string format_name() const { @@ -214,26 +255,26 @@ class GIWrapperBase : public CWrapperPointerOps { protected: void debug_lifecycle(const char* message GJS_USED_VERBOSE_LIFECYCLE) const { - gjs_debug_lifecycle( - Base::DEBUG_TOPIC, "[%p: %s pointer %p - %s.%s (%s)] %s", this, - Base::DEBUG_TAG, ptr_addr(), ns(), name(), type_name(), message); + gjs_debug_lifecycle(Base::DEBUG_TOPIC, + "[%p: %s pointer %p - %s (%s)] %s", this, + Base::DEBUG_TAG, ptr_addr(), format_name().c_str(), + type_name(), message); } void debug_lifecycle(const void* obj GJS_USED_VERBOSE_LIFECYCLE, const char* message GJS_USED_VERBOSE_LIFECYCLE) const { - gjs_debug_lifecycle( - Base::DEBUG_TOPIC, - "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s", this, - Base::DEBUG_TAG, ptr_addr(), obj, ns(), name(), type_name(), - message); + gjs_debug_lifecycle(Base::DEBUG_TOPIC, + "[%p: %s pointer %p - JS wrapper %p - %s (%s)] %s", + this, Base::DEBUG_TAG, ptr_addr(), obj, + format_name().c_str(), type_name(), message); } void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS, const char* id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) const { gjs_debug_jsprop( Base::DEBUG_TOPIC, - "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s '%s'", this, - Base::DEBUG_TAG, ptr_addr(), obj, ns(), name(), type_name(), - message, id); + "[%p: %s pointer %p - JS wrapper %p - %s (%s)] %s '%s'", this, + Base::DEBUG_TAG, ptr_addr(), obj, format_name().c_str(), + type_name(), message, id); } void debug_jsprop(const char* message, jsid id, const void* obj) const { if constexpr (GJS_VERBOSE_ENABLE_PROPS) @@ -481,8 +522,9 @@ class GIWrapperBase : public CWrapperPointerOps { Instance* priv = Instance::new_for_js_object(prototype, obj); { - std::string fullName = priv->format_name(); - AutoProfilerLabel label(cx, "constructor", fullName.c_str()); + std::string full_name{ + GJS_PROFILER_DYNAMIC_STRING(cx, priv->format_name())}; + AutoProfilerLabel label{cx, "constructor", full_name}; if (!priv->constructor_impl(cx, obj, args)) return false; @@ -528,8 +570,8 @@ class GIWrapperBase : public CWrapperPointerOps { bool check_is_instance(JSContext* cx, const char* for_what) const { if (!is_prototype()) return true; - gjs_throw(cx, "Can't %s on %s.%s.prototype; only on instances", - for_what, ns(), name()); + gjs_throw(cx, "Can't %s on %s.prototype; only on instances", for_what, + format_name().c_str()); return false; } @@ -580,13 +622,13 @@ class GIWrapperBase : public CWrapperPointerOps { GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, - GType expected_gtype, - GIBaseInfo* expected_info = nullptr) { + GType expected_gtype) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); - if (!Base::typecheck(cx, obj, expected_info, expected_gtype)) { - gjs_arg_unset(arg); + if (expected_gtype != G_TYPE_NONE && + !Base::typecheck(cx, obj, expected_gtype)) { + gjs_arg_unset(arg); return false; } @@ -624,41 +666,47 @@ class GIWrapperBase : public CWrapperPointerOps { */ GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject object, - GIBaseInfo* expected_info, GType expected_gtype) { + const GI::BaseInfo expected_info) { Base* priv; if (!Base::for_js_typecheck(cx, object, &priv) || !priv->check_is_instance(cx, "convert to pointer")) return false; - if (priv->to_instance()->typecheck_impl(cx, expected_info, - expected_gtype)) + if (priv->to_instance()->typecheck_impl(expected_info)) return true; - if (expected_info) { - gjs_throw_custom( - 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, JSEXN_TYPEERR, nullptr, - "Object is of type %s.%s - cannot convert to %s", - priv->ns(), priv->name(), - g_type_name(expected_gtype)); - } + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, + "Object is of type %s - cannot convert to %s.%s", + priv->format_name().c_str(), + expected_info.ns(), expected_info.name()); + return false; + } + GJS_JSAPI_RETURN_CONVENTION + static bool typecheck(JSContext* cx, JS::HandleObject object, + GType expected_gtype) { + Base* priv; + if (!Base::for_js_typecheck(cx, object, &priv) || + !priv->check_is_instance(cx, "convert to pointer")) + return false; + + if (priv->to_instance()->typecheck_impl(expected_gtype)) + return true; + gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, + "Object is of type %s - cannot convert to %s", + priv->format_name().c_str(), + g_type_name(expected_gtype)); return false; } - [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject object, - GIBaseInfo* expected_info, - GType expected_gtype, - GjsTypecheckNoThrow) { + template + [[nodiscard]] + static bool typecheck(JSContext* cx, JS::HandleObject object, + T expected, GjsTypecheckNoThrow) { Base* priv = Base::for_js(cx, object); if (!priv || priv->is_prototype()) return false; - return priv->to_instance()->typecheck_impl(cx, expected_info, - expected_gtype); + return priv->to_instance()->typecheck_impl(expected); } // Deleting these constructors and assignment operators will also delete @@ -680,22 +728,22 @@ class GIWrapperBase : public CWrapperPointerOps { * GIWrapperPrototype", because of the unusual polymorphism scheme, in order for * Base to call methods such as trace_impl(). */ -template +template class GIWrapperPrototype : public Base { using GjsAutoPrototype = - GjsAutoPointer; + Gjs::AutoPointer; protected: // m_info may be null in the case of JS-defined types, or internal types // not exposed through introspection, such as GLocalFile. Not all subclasses // of GIWrapperPrototype support this. Object and Interface support it in // any case. - GjsAutoBaseInfo m_info; + OwnedInfo m_info; GType m_gtype; - explicit GIWrapperPrototype(Info* info, GType gtype) - : Base(), m_info(info, GjsAutoTakeOwnership()), m_gtype(gtype) { + explicit GIWrapperPrototype(const UnownedInfo info, GType gtype) + : Base(), m_info(info), m_gtype(gtype) { Base::debug_lifecycle("Prototype constructor"); } @@ -804,7 +852,11 @@ class GIWrapperPrototype : public Base { // "unknown" if this is a custom or internal JS class with no GI // namespace, as in that case the name is already globally unique (it's // a GType name). - const char* gi_namespace = Base::info() ? Base::ns() : "unknown"; + const char* gi_namespace; + if constexpr (may_not_have_info) + gi_namespace = Base::info() ? Base::ns() : "unknown"; + else + gi_namespace = Base::ns(); unsigned nargs = static_cast(this)->constructor_nargs(); @@ -831,20 +883,21 @@ class GIWrapperPrototype : public Base { * Defines all introspectable static methods on @constructor, including * class methods for objects, and interface methods for interfaces. See * gjs_define_static_methods() for details. - * - * It requires Prototype to have an info_type_tag member to indicate - * the correct template specialization of gjs_define_static_methods(). */ GJS_JSAPI_RETURN_CONVENTION bool define_static_methods(JSContext* cx, JS::HandleObject constructor) { - if (!info()) - return true; // no introspection means no methods to define - return gjs_define_static_methods( - cx, constructor, m_gtype, m_info); + if constexpr (may_not_have_info) { + if (!info()) + return true; // no introspection means no methods to define + return gjs_define_static_methods(cx, constructor, m_gtype, + info().value()); + } else { + return gjs_define_static_methods(cx, constructor, m_gtype, m_info); + } } GJS_JSAPI_RETURN_CONVENTION - static Prototype* create_prototype(Info* info, GType gtype) { + static Prototype* create_prototype(const UnownedInfo info, GType gtype) { g_assert(gtype != G_TYPE_INVALID); // We have to keep the Prototype in an arcbox because some of its @@ -885,7 +938,7 @@ class GIWrapperPrototype : public Base { */ GJS_JSAPI_RETURN_CONVENTION static Prototype* create_class(JSContext* cx, JS::HandleObject in_object, - Info* info, GType gtype, + const UnownedInfo info, GType gtype, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { g_assert(in_object); @@ -927,7 +980,7 @@ class GIWrapperPrototype : public Base { GJS_JSAPI_RETURN_CONVENTION static Prototype* wrap_class(JSContext* cx, JS::HandleObject in_object, - Info* info, GType gtype, + const UnownedInfo info, GType gtype, JS::HandleObject constructor, JS::MutableHandleObject prototype) { g_assert(in_object); @@ -956,7 +1009,7 @@ class GIWrapperPrototype : public Base { if (!proto->define_static_methods(cx, constructor)) return nullptr; - GjsAutoChar class_name = g_strdup_printf("%s", proto->name()); + Gjs::AutoChar class_name{g_strdup_printf("%s", proto->name())}; if (!JS_DefineProperty(cx, in_object, class_name, constructor, GJS_MODULE_PROP_FLAGS)) return nullptr; @@ -994,7 +1047,8 @@ class GIWrapperPrototype : public Base { // Accessors - [[nodiscard]] Info* info() const { return m_info; } + static constexpr bool may_not_have_info = is_maybe::value; + [[nodiscard]] const UnownedInfo info() const { return m_info; } [[nodiscard]] GType gtype() const { return m_gtype; } // Helper methods @@ -1022,11 +1076,13 @@ class GIWrapperPrototype : public Base { }; using GIWrappedUnowned = void; +namespace Gjs { template <> -struct GjsSmartPointer - : GjsAutoPointer { - using GjsAutoPointer::GjsAutoPointer; +struct SmartPointer + : AutoPointer { + using AutoPointer::AutoPointer; }; +} // namespace Gjs /* * GIWrapperInstance: @@ -1043,7 +1099,7 @@ template class GIWrapperInstance : public Base { protected: - GjsSmartPointer m_ptr; + Gjs::SmartPointer m_ptr; explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj) : Base(prototype), m_ptr(nullptr) { @@ -1129,14 +1185,22 @@ class GIWrapperInstance : public Base { * It's possible to override typecheck_impl() if you need an extra step in * the check. */ - [[nodiscard]] bool typecheck_impl(JSContext*, GIBaseInfo* expected_info, - GType expected_gtype) const { - if (expected_gtype != G_TYPE_NONE) - return g_type_is_a(Base::gtype(), expected_gtype); - else if (expected_info) - return g_base_info_equal(Base::info(), expected_info); + [[nodiscard]] + bool typecheck_impl(const GI::BaseInfo expected_info) const { + if constexpr (Prototype::may_not_have_info) { + if (Base::info()) + return Base::info().ref() == expected_info; + } else { + return Base::info() == expected_info; + } return true; } + [[nodiscard]] + bool typecheck_impl(GType expected_gtype) const { + g_assert(expected_gtype != G_TYPE_NONE && + "should not call typecheck_impl() without a real GType"); + return g_type_is_a(Base::gtype(), expected_gtype); + } }; #endif // GI_WRAPPERUTILS_H_ diff --git a/installed-tests/.eslintrc.yml b/installed-tests/.eslintrc.yml deleted file mode 100644 index 6c9c0253c..000000000 --- a/installed-tests/.eslintrc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - jsdoc/require-jsdoc: 'off' diff --git a/installed-tests/debugger-test.sh b/installed-tests/debugger-test.sh old mode 100644 new mode 100755 index bb1b0d227..6b18fd7f4 --- a/installed-tests/debugger-test.sh +++ b/installed-tests/debugger-test.sh @@ -1,6 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento +stringContain() { case $2 in *$1* ) return 0;; *) return 1;; esac ;} if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" @@ -13,10 +14,21 @@ echo 1..1 DEBUGGER_SCRIPT="$1" JS_SCRIPT="$1.js" EXPECTED_OUTPUT="$1.output" -THE_DIFF=$("$gjs" -d "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ - -e "s#$1#$(basename $1)#g" \ - -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ - | diff -u "$EXPECTED_OUTPUT" -) +if stringContain "module" "$JS_SCRIPT" ; then + # module specifiers are canonicalized into absolute paths + THE_DIFF=$("$gjs" -d -m "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ + -e "s#file://$(realpath $1)#$(basename $1)#g" \ + -e "s/file:\/\/.*sourcemap-number-module.js/sourcemap-number-module.js/g" \ + -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ + -e "s/[0-9][0-9.]* ms/XXXX ms/g" \ + | diff -u "$EXPECTED_OUTPUT" -) +else + THE_DIFF=$("$gjs" -d "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ + -e "s#$1#$(basename $1)#g" \ + -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ + -e "s/[0-9][0-9.]* ms/XXXX ms/g" \ + | diff -u "$EXPECTED_OUTPUT" -) +fi EXITCODE=$? if test -n "$THE_DIFF"; then diff --git a/installed-tests/debugger/.eslintrc.yml b/installed-tests/debugger/.eslintrc.yml deleted file mode 100644 index 5bd67acbb..000000000 --- a/installed-tests/debugger/.eslintrc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -rules: - no-debugger: 'off' diff --git a/installed-tests/debugger/keys.debugger b/installed-tests/debugger/keys.debugger index 595ae715b..6a15d3481 100644 --- a/installed-tests/debugger/keys.debugger +++ b/installed-tests/debugger/keys.debugger @@ -8,4 +8,5 @@ keys {} keys bar keys ['a', 'b', 'c'] keys +keys b c diff --git a/installed-tests/debugger/keys.debugger.js b/installed-tests/debugger/keys.debugger.js index 8176360ff..fd68343a7 100644 --- a/installed-tests/debugger/keys.debugger.js +++ b/installed-tests/debugger/keys.debugger.js @@ -6,5 +6,45 @@ const a = { tres: undefined, [Symbol('s')]: 'string', }; +class Parent { + #privateField; + constructor() { + this.#privateField = 1; + } +} + +class Child extends Parent { + #subPrivateField; + meaningOfLife = 42; + constructor() { + super(); + this.#subPrivateField = 2; + } +} + +class PrivateTest extends Child { + #child; + childVisible; + #customToStringChild; + #circular1; + #circular2; + #selfRef; + #date; + #privateFunc; + constructor() { + super(); + this.#child = new Child(); + this.childVisible = new Child(); + this.#customToStringChild = new Child(); + this.#customToStringChild.toString = () => 'Custom child!'; + this.#circular2 = {}; + this.#circular1 = {n: this.#circular2}; + this.#circular2.n = this.#circular1; + this.#selfRef = this; + this.#date = new Date('2025-01-07T00:53:42.417Z'); + this.#privateFunc = () => 1; + } +} +const b = new PrivateTest(); debugger; -void a; +void (a, b); diff --git a/installed-tests/debugger/keys.debugger.output b/installed-tests/debugger/keys.debugger.output index 4784c7e67..728d303c8 100644 --- a/installed-tests/debugger/keys.debugger.output +++ b/installed-tests/debugger/keys.debugger.output @@ -2,7 +2,7 @@ GJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c -Debugger statement, toplevel at keys.debugger.js:9:1 +Debugger statement, toplevel at keys.debugger.js:49:1 db> keys a "foo", "bar", "tres", Symbol("s") db> k a @@ -17,5 +17,7 @@ db> keys ['a', 'b', 'c'] "0", "1", "2", "length" db> keys Missing argument. See 'help keys' +db> keys b +"meaningOfLife", "childVisible", #privateField, #subPrivateField, #child, #customToStringChild, #circular1, #circular2, #selfRef, #date, #privateFunc db> c Program exited with code 0 diff --git a/installed-tests/debugger/list.debugger.output b/installed-tests/debugger/list.debugger.output index eaa483e2d..0f6be0a60 100644 --- a/installed-tests/debugger/list.debugger.output +++ b/installed-tests/debugger/list.debugger.output @@ -9,6 +9,7 @@ db> list 9 return a / b; 10 } *11 divide(); + 12 db> list 4 1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 2 // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma @@ -26,12 +27,14 @@ db> list 11 9 return a / b; 10 } *11 divide(); + 12 db> list 12 7 return undefined; 8 else 9 return a / b; 10 } 11 divide(); + *12 db> list 0 1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 2 // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma diff --git a/installed-tests/debugger/noModule.js.map b/installed-tests/debugger/noModule.js.map new file mode 100644 index 000000000..db6c67efe --- /dev/null +++ b/installed-tests/debugger/noModule.js.map @@ -0,0 +1 @@ +{"version":3,"file":"noModule.js","sourceRoot":"","sources":["noModule.ts"],"names":[],"mappings":"AAIA,IAAI,CAAC,GAAgB,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC;AAC/B,IAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC","sourcesContent":["interface FancyNumber {\n n: number;\n}\n\nlet a: FancyNumber = {n: null};\nconst b = a.n.toString(42);\n\n"]} \ No newline at end of file diff --git a/installed-tests/debugger/noModule.js.map.license b/installed-tests/debugger/noModule.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/noModule.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/number.js.map b/installed-tests/debugger/number.js.map new file mode 100644 index 000000000..8c80f3b52 --- /dev/null +++ b/installed-tests/debugger/number.js.map @@ -0,0 +1 @@ +{"version":3,"file":"number.js","sourceRoot":"","sources":["number.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,IAAM,YAAY,GAAG,UAAC,GAAuB;IAChD,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAA","sourcesContent":["interface SuperFancyNumber {\n n: number;\n}\n\nexport const get2ndNumber = (num: SuperFancyNumber[]) => {\n return num[1].n.toFixed(1);\n}"]} \ No newline at end of file diff --git a/installed-tests/debugger/number.js.map.license b/installed-tests/debugger/number.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/number.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/numberWork.js.map b/installed-tests/debugger/numberWork.js.map new file mode 100644 index 000000000..2aa100f4c --- /dev/null +++ b/installed-tests/debugger/numberWork.js.map @@ -0,0 +1 @@ +{"version":3,"file":"numberWork.js","sourceRoot":"","sources":["numberWork.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,EAAE,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC","sourcesContent":["import { get2ndNumber } from \"./number.js\";\n\nget2ndNumber([{n:1}, {n: null}]);"]} \ No newline at end of file diff --git a/installed-tests/debugger/numberWork.js.map.license b/installed-tests/debugger/numberWork.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/numberWork.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/print.debugger b/installed-tests/debugger/print.debugger index 90b1e6678..a6ed8cc52 100644 --- a/installed-tests/debugger/print.debugger +++ b/installed-tests/debugger/print.debugger @@ -22,4 +22,6 @@ p l p m p n p o +p p +p q c diff --git a/installed-tests/debugger/print.debugger.js b/installed-tests/debugger/print.debugger.js index 532ffe5a6..43ce3beb3 100644 --- a/installed-tests/debugger/print.debugger.js +++ b/installed-tests/debugger/print.debugger.js @@ -9,12 +9,62 @@ const e = false; const f = true; const g = Symbol('foobar'); const h = [1, 'money', 2, 'show', {three: 'to', 'get ready': 'go cat go'}]; -const i = {some: 'plain object', that: 'has keys'}; +const i = {some: 'plain object', that: 'has keys', with: null, and: undefined}; const j = new Set([5, 6, 7]); const k = class J {}; const l = new GObject.Object(); const m = new Error('message'); const n = {a: 1}; const o = {some: 'plain object', [Symbol('that')]: 'has symbols'}; + +class Parent { + #privateField; + constructor() { + this.#privateField = 1; + } +} + +class Child extends Parent { + #subPrivateField; + meaningOfLife = 42; + constructor() { + super(); + this.#subPrivateField = 2; + } +} + +class PrivateTest extends Child { + #child; + childVisible; + #customToStringChild; + #circular1; + #circular2; + #selfRef; + #date; + #privateFunc; + + constructor() { + super(); + this.#child = new Child(); + this.childVisible = new Child(); + this.#customToStringChild = new Child(); + this.#customToStringChild.toString = () => 'Custom child!'; + this.#circular2 = {}; + this.#circular1 = {n: this.#circular2}; + this.#circular2.n = this.#circular1; + this.#selfRef = this; + this.#date = new Date('2025-01-07T00:53:42.417Z'); + this.#privateFunc = () => 1; + } +} +const p = new PrivateTest(); +class PrivateNullishToString { + #test; + toString = null; + constructor() { + this.#test = 1; + } +} +const q = new PrivateNullishToString(); debugger; -void (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); +void (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q); diff --git a/installed-tests/debugger/print.debugger.output b/installed-tests/debugger/print.debugger.output index 90c80f877..21fa32407 100644 --- a/installed-tests/debugger/print.debugger.output +++ b/installed-tests/debugger/print.debugger.output @@ -2,7 +2,7 @@ GJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c -Debugger statement, toplevel at print.debugger.js:19:1 +Debugger statement, toplevel at print.debugger.js:69:1 db> # Simple types db> print a $1 = undefined @@ -30,7 +30,7 @@ $10 = [object Array] [1, "money", 2, "show", { three: "to", get ready: "go cat go" }] db> p i $11 = [object Object] -{ some: "plain object", that: "has keys" } +{ some: "plain object", that: "has keys", with: null, and: undefined } db> p/b i $12 = [object Object] [object Object] @@ -55,5 +55,11 @@ $18 = [object Object] db> p o $19 = [object Object] { some: "plain object", [Symbol("that")]: "has symbols" } +db> p p +$20 = [object Object] +{ meaningOfLife: 42, childVisible: { meaningOfLife: 42, #privateField: 1, #subPrivateField: 2 }, #privateField: 1, #subPrivateField: 2, #child: { meaningOfLife: 42, #privateField: 1, #subPrivateField: 2 }, #customToStringChild: Custom child!, #circular1: { n: { n: [Circular] } }, #circular2: [Circular], #selfRef: [Circular], #date: 2025-01-07T00:53:42.417Z, #privateFunc: [ Function: ] } +db> p q +$21 = [object Object] +{ toString: null, #test: 1 } db> c Program exited with code 0 diff --git a/installed-tests/debugger/sourcemap-dynamic-module.debugger b/installed-tests/debugger/sourcemap-dynamic-module.debugger new file mode 100644 index 000000000..4adc1b5c0 --- /dev/null +++ b/installed-tests/debugger/sourcemap-dynamic-module.debugger @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2025 Philip Chimento +c +s +s +set colors false +list diff --git a/installed-tests/debugger/sourcemap-dynamic-module.debugger.js b/installed-tests/debugger/sourcemap-dynamic-module.debugger.js new file mode 100644 index 000000000..a5943cef1 --- /dev/null +++ b/installed-tests/debugger/sourcemap-dynamic-module.debugger.js @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Philip Chimento +const {get2ndNumber} = await import('./sourcemap-number-module.js'); +debugger; +get2ndNumber([{ n: 1 }, { n: null }]); diff --git a/installed-tests/debugger/sourcemap-dynamic-module.debugger.output b/installed-tests/debugger/sourcemap-dynamic-module.debugger.output new file mode 100644 index 000000000..33c868508 --- /dev/null +++ b/installed-tests/debugger/sourcemap-dynamic-module.debugger.output @@ -0,0 +1,26 @@ +GJS debugger. Type "help" for help +db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +db> # SPDX-FileCopyrightText: 2025 Philip Chimento +db> c +Promise 2 started from @sourcemap-dynamic-module.debugger.js:1:1 +Promise 3 started from @sourcemap-dynamic-module.debugger.js:3:24 +Promise 4 fulfilled after XXXX ms +Promise 3 fulfilled after XXXX ms with [object Object] +[Object: null prototype] { get2ndNumber: [ Function: get2ndNumber ], [Symbol.toStringTag]: "Module" } +Debugger statement, module code at sourcemap-dynamic-module.debugger.js:4:1 +db> s +module code at sourcemap-dynamic-module.debugger.js:4:1 +db> s +module code at sourcemap-dynamic-module.debugger.js:5:1 +entered frame: ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 +db> set colors false +db> list + 1 interface SuperFancyNumber { + 2 n: number; + 3 } + 4 + 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { + *6 return num[1].n.toFixed(1); + 7 } +db> [quit due to end of input] +Program exited with code 0 diff --git a/installed-tests/debugger/sourcemap-inlined-module.debugger b/installed-tests/debugger/sourcemap-inlined-module.debugger new file mode 100644 index 000000000..aa6db36c1 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined-module.debugger @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2024 Gary Li +set colors false +list +bt +frame +c +list +up +list +bt diff --git a/installed-tests/debugger/sourcemap-inlined-module.debugger.js b/installed-tests/debugger/sourcemap-inlined-module.debugger.js new file mode 100644 index 000000000..93757e29e --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined-module.debugger.js @@ -0,0 +1,3 @@ +import { get2ndNumber } from "./sourcemap-number-module.js"; +get2ndNumber([{ n: 1 }, { n: null }]); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVtYmVyV29yay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm51bWJlcldvcmsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUUzQyxZQUFZLENBQUMsQ0FBQyxFQUFDLENBQUMsRUFBQyxDQUFDLEVBQUMsRUFBRSxFQUFDLENBQUMsRUFBRSxJQUFJLEVBQUMsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXQybmROdW1iZXIgfSBmcm9tIFwiLi9udW1iZXIuanNcIjtcblxuZ2V0Mm5kTnVtYmVyKFt7bjoxfSwge246IG51bGx9XSk7Il19 \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-inlined-module.debugger.js.license b/installed-tests/debugger/sourcemap-inlined-module.debugger.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined-module.debugger.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-inlined-module.debugger.output b/installed-tests/debugger/sourcemap-inlined-module.debugger.output new file mode 100644 index 000000000..a60be0420 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined-module.debugger.output @@ -0,0 +1,44 @@ +GJS debugger. Type "help" for help +db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +db> # SPDX-FileCopyrightText: 2024 Gary Li +db> set colors false +db> list + 1 interface SuperFancyNumber { + 2 n: number; + 3 } + 4 + *5 export const get2ndNumber = (num: SuperFancyNumber[]) => { + 6 return num[1].n.toFixed(1); + 7 } +db> bt +#0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 +db> frame +#0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 + 1 export var get2ndNumber = function (num) { +db> c +Unwinding due to exception. (Type 'c' to continue unwinding.) +#0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 + 2 return num[1].n.toFixed(1); +Exception value is: +$1 = [object TypeError] +TypeError: can't access property "toFixed", num[1].n is null +db> list + 1 interface SuperFancyNumber { + 2 n: number; + 3 } + 4 + 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { + *6 return num[1].n.toFixed(1); + 7 } +db> up +#1 module code at sourcemap-inlined-module.debugger.js:2:13 -> numberWork.ts:3:14 + 2 get2ndNumber([{ n: 1 }, { n: null }]); +db> list + 1 import { get2ndNumber } from "./number.js"; + 2 + *3 get2ndNumber([{n:1}, {n: null}]); +db> bt +#0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 +#1 module code at sourcemap-inlined-module.debugger.js:2:13 -> numberWork.ts:3:14 +db> [quit due to end of input] +Program exited with code 0 diff --git a/installed-tests/debugger/sourcemap-inlined.debugger b/installed-tests/debugger/sourcemap-inlined.debugger new file mode 100644 index 000000000..aa6db36c1 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined.debugger @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2024 Gary Li +set colors false +list +bt +frame +c +list +up +list +bt diff --git a/installed-tests/debugger/sourcemap-inlined.debugger.js b/installed-tests/debugger/sourcemap-inlined.debugger.js new file mode 100644 index 000000000..9f39c27ae --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined.debugger.js @@ -0,0 +1,3 @@ +var a = { n: null }; +var b = a.n.toString(42); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9Nb2R1bGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub01vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxJQUFJLENBQUMsR0FBZ0IsRUFBQyxDQUFDLEVBQUUsSUFBSSxFQUFDLENBQUM7QUFDL0IsSUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbnRlcmZhY2UgRmFuY3lOdW1iZXIge1xuICAgIG46IG51bWJlcjtcbn1cblxubGV0IGE6IEZhbmN5TnVtYmVyID0ge246IG51bGx9O1xuY29uc3QgYiA9IGEubi50b1N0cmluZyg0Mik7XG5cbiJdfQ== \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-inlined.debugger.js.license b/installed-tests/debugger/sourcemap-inlined.debugger.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined.debugger.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-inlined.debugger.output b/installed-tests/debugger/sourcemap-inlined.debugger.output new file mode 100644 index 000000000..5e49d98e5 --- /dev/null +++ b/installed-tests/debugger/sourcemap-inlined.debugger.output @@ -0,0 +1,49 @@ +GJS debugger. Type "help" for help +db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +db> # SPDX-FileCopyrightText: 2024 Gary Li +db> set colors false +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + *5 let a: FancyNumber = {n: null}; + 6 const b = a.n.toString(42); + 7 + 8 +db> bt +#0 toplevel at sourcemap-inlined.debugger.js:1:1 -> noModule.ts:5:1 +db> frame +#0 toplevel at sourcemap-inlined.debugger.js:1:1 -> noModule.ts:5:1 + 1 var a = { n: null }; +db> c +Unwinding due to exception. (Type 'c' to continue unwinding.) +#0 toplevel at sourcemap-inlined.debugger.js:2:9 -> noModule.ts:6:12 + 2 var b = a.n.toString(42); +Exception value is: +$1 = [object TypeError] +TypeError: can't access property "toString", a.n is null +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + 5 let a: FancyNumber = {n: null}; + *6 const b = a.n.toString(42); + 7 + 8 +db> up +Initial frame selected; you cannot go up. +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + 5 let a: FancyNumber = {n: null}; + *6 const b = a.n.toString(42); + 7 + 8 +db> bt +#0 toplevel at sourcemap-inlined.debugger.js:2:9 -> noModule.ts:6:12 +db> [quit due to end of input] +Program exited with code 0 diff --git a/installed-tests/debugger/sourcemap-number-module.js b/installed-tests/debugger/sourcemap-number-module.js new file mode 100644 index 000000000..d9c8f3444 --- /dev/null +++ b/installed-tests/debugger/sourcemap-number-module.js @@ -0,0 +1,4 @@ +export var get2ndNumber = function (num) { + return num[1].n.toFixed(1); +}; +//# sourceMappingURL=number.js.map \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-number-module.js.license b/installed-tests/debugger/sourcemap-number-module.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/sourcemap-number-module.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-separate-module.debugger b/installed-tests/debugger/sourcemap-separate-module.debugger new file mode 100644 index 000000000..aa6db36c1 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate-module.debugger @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2024 Gary Li +set colors false +list +bt +frame +c +list +up +list +bt diff --git a/installed-tests/debugger/sourcemap-separate-module.debugger.js b/installed-tests/debugger/sourcemap-separate-module.debugger.js new file mode 100644 index 000000000..70113ccfa --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate-module.debugger.js @@ -0,0 +1,3 @@ +import { get2ndNumber } from "./sourcemap-number-module.js"; +get2ndNumber([{ n: 1 }, { n: null }]); +//# sourceMappingURL=numberWork.js.map \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-separate-module.debugger.js.license b/installed-tests/debugger/sourcemap-separate-module.debugger.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate-module.debugger.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-separate-module.debugger.output b/installed-tests/debugger/sourcemap-separate-module.debugger.output new file mode 100644 index 000000000..5f429ecd7 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate-module.debugger.output @@ -0,0 +1,44 @@ +GJS debugger. Type "help" for help +db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +db> # SPDX-FileCopyrightText: 2024 Gary Li +db> set colors false +db> list + 1 interface SuperFancyNumber { + 2 n: number; + 3 } + 4 + *5 export const get2ndNumber = (num: SuperFancyNumber[]) => { + 6 return num[1].n.toFixed(1); + 7 } +db> bt +#0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 +db> frame +#0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 + 1 export var get2ndNumber = function (num) { +db> c +Unwinding due to exception. (Type 'c' to continue unwinding.) +#0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 + 2 return num[1].n.toFixed(1); +Exception value is: +$1 = [object TypeError] +TypeError: can't access property "toFixed", num[1].n is null +db> list + 1 interface SuperFancyNumber { + 2 n: number; + 3 } + 4 + 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { + *6 return num[1].n.toFixed(1); + 7 } +db> up +#1 module code at sourcemap-separate-module.debugger.js:2:13 -> numberWork.ts:3:14 + 2 get2ndNumber([{ n: 1 }, { n: null }]); +db> list + 1 import { get2ndNumber } from "./number.js"; + 2 + *3 get2ndNumber([{n:1}, {n: null}]); +db> bt +#0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 +#1 module code at sourcemap-separate-module.debugger.js:2:13 -> numberWork.ts:3:14 +db> [quit due to end of input] +Program exited with code 0 diff --git a/installed-tests/debugger/sourcemap-separate.debugger b/installed-tests/debugger/sourcemap-separate.debugger new file mode 100644 index 000000000..aa6db36c1 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate.debugger @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2024 Gary Li +set colors false +list +bt +frame +c +list +up +list +bt diff --git a/installed-tests/debugger/sourcemap-separate.debugger.js b/installed-tests/debugger/sourcemap-separate.debugger.js new file mode 100644 index 000000000..a094e2142 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate.debugger.js @@ -0,0 +1,3 @@ +var a = { n: null }; +var b = a.n.toString(42); +//# sourceMappingURL=noModule.js.map \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-separate.debugger.js.license b/installed-tests/debugger/sourcemap-separate.debugger.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate.debugger.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/installed-tests/debugger/sourcemap-separate.debugger.output b/installed-tests/debugger/sourcemap-separate.debugger.output new file mode 100644 index 000000000..52b177b53 --- /dev/null +++ b/installed-tests/debugger/sourcemap-separate.debugger.output @@ -0,0 +1,49 @@ +GJS debugger. Type "help" for help +db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +db> # SPDX-FileCopyrightText: 2024 Gary Li +db> set colors false +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + *5 let a: FancyNumber = {n: null}; + 6 const b = a.n.toString(42); + 7 + 8 +db> bt +#0 toplevel at sourcemap-separate.debugger.js:1:1 -> noModule.ts:5:1 +db> frame +#0 toplevel at sourcemap-separate.debugger.js:1:1 -> noModule.ts:5:1 + 1 var a = { n: null }; +db> c +Unwinding due to exception. (Type 'c' to continue unwinding.) +#0 toplevel at sourcemap-separate.debugger.js:2:9 -> noModule.ts:6:12 + 2 var b = a.n.toString(42); +Exception value is: +$1 = [object TypeError] +TypeError: can't access property "toString", a.n is null +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + 5 let a: FancyNumber = {n: null}; + *6 const b = a.n.toString(42); + 7 + 8 +db> up +Initial frame selected; you cannot go up. +db> list + 1 interface FancyNumber { + 2 n: number; + 3 } + 4 + 5 let a: FancyNumber = {n: null}; + *6 const b = a.n.toString(42); + 7 + 8 +db> bt +#0 toplevel at sourcemap-separate.debugger.js:2:9 -> noModule.ts:6:12 +db> [quit due to end of input] +Program exited with code 0 diff --git a/installed-tests/extra/lsan.supp b/installed-tests/extra/lsan.supp index b792a49fa..ba75f949d 100644 --- a/installed-tests/extra/lsan.supp +++ b/installed-tests/extra/lsan.supp @@ -17,3 +17,7 @@ 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 + +# Data created as part of opening X display +leak:glx_screen_init +leak:eglCreateContext diff --git a/installed-tests/js/.eslintrc.yml b/installed-tests/js/.eslintrc.yml deleted file mode 100644 index 77044c33e..000000000 --- a/installed-tests/js/.eslintrc.yml +++ /dev/null @@ -1,45 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -env: - jasmine: true -rules: - no-restricted-globals: - - error - - name: fdescribe - message: Do not commit fdescribe(). Use describe() instead. - - name: fit - message: Do not commit fit(). Use it() instead. - no-restricted-syntax: - - error - - selector: CallExpression[callee.name="it"] > ArrowFunctionExpression - message: Arrow functions can mess up some Jasmine APIs. Use function () instead - - selector: CallExpression[callee.name="beforeEach"] > ArrowFunctionExpression - message: Arrow functions can mess up some Jasmine APIs. Use function () instead - - selector: CallExpression[callee.name="afterEach"] > ArrowFunctionExpression - message: Arrow functions can mess up some Jasmine APIs. Use function () instead - - selector: CallExpression[callee.name="beforeAll"] > ArrowFunctionExpression - message: Arrow functions can mess up some Jasmine APIs. Use function () instead - - selector: CallExpression[callee.name="afterAll"] > ArrowFunctionExpression - message: Arrow functions can mess up some Jasmine APIs. Use function () instead -overrides: - - files: - - matchers.js - - minijasmine.js - - minijasmine-executor.js - - testAsync.js - - testAsyncMainloop.js - - testCairoModule.js - - testConsole.js - - testESModules.js - - testEncoding.js - - testGLibLogWriter.js - - testTimers.js - - testWeakRef.js - - modules/importmeta.js - - modules/exports.js - - modules/greet.js - - modules/say.js - - modules/sideEffect4.js - parserOptions: - sourceType: module diff --git a/installed-tests/js/jsunit.gresources.xml b/installed-tests/js/jsunit.gresources.xml index aedbd588e..b0902383a 100644 --- a/installed-tests/js/jsunit.gresources.xml +++ b/installed-tests/js/jsunit.gresources.xml @@ -28,8 +28,10 @@ modules/modunicode.js modules/mutualImport/a.js modules/mutualImport/b.js + modules/networkURI.js modules/overrides/GIMarshallingTests.js modules/say.js + modules/scaryURI.js modules/sideEffect.js modules/sideEffect2.js modules/sideEffect3.js diff --git a/installed-tests/js/libgjstesttools/gjs-test-tools.cpp b/installed-tests/js/libgjstesttools/gjs-test-tools.cpp index 2e62f00e4..ad235a720 100644 --- a/installed-tests/js/libgjstesttools/gjs-test-tools.cpp +++ b/installed-tests/js/libgjstesttools/gjs-test-tools.cpp @@ -7,8 +7,6 @@ #include #include -#include "cjs/jsapi-util.h" - #ifdef G_OS_UNIX # include # include /* for FD_CLOEXEC */ @@ -18,6 +16,8 @@ # include /* for g_unix_open_pipe */ #endif +#include "cjs/auto.h" + static std::atomic s_tmp_object = nullptr; static GWeakRef s_tmp_weak; static std::unordered_set s_finalized_objects; @@ -110,6 +110,21 @@ void gjs_test_tools_ref_other_thread(GObject* object, GError** error) { // cppcheck-suppress memleak } +static gpointer emit_test_signal_other_thread_func(gpointer data) { + g_signal_emit_by_name(data, "test"); + return nullptr; +} + +void gjs_test_tools_emit_test_signal_other_thread(GObject* object, + GError** error) { + auto* thread = + g_thread_try_new("emit_signal_object", + emit_test_signal_other_thread_func, object, error); + if (thread) + g_thread_join(thread); + // cppcheck-suppress memleak +} + typedef enum { REF = 1 << 0, UNREF = 1 << 1, @@ -135,8 +150,8 @@ static RefThreadData* ref_thread_data_new(GObject* object, int interval, } static void* ref_thread_func(void* data) { - GjsAutoPointer ref_data = - static_cast(data); + Gjs::AutoPointer ref_data{ + static_cast(data)}; if (FinalizedObjectsLocked()->count(ref_data->object)) return nullptr; diff --git a/installed-tests/js/libgjstesttools/gjs-test-tools.h b/installed-tests/js/libgjstesttools/gjs-test-tools.h index 722eee4fc..61fc727de 100644 --- a/installed-tests/js/libgjstesttools/gjs-test-tools.h +++ b/installed-tests/js/libgjstesttools/gjs-test-tools.h @@ -50,6 +50,10 @@ GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object, int interval, GError** error); +GJS_TEST_TOOL_EXTERN +void gjs_test_tools_emit_test_signal_other_thread(GObject* object, + GError** error); + GJS_TEST_TOOL_EXTERN GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object, int interval, diff --git a/installed-tests/js/libgjstesttools/meson.build b/installed-tests/js/libgjstesttools/meson.build index 48cceee17..ec5b49217 100644 --- a/installed-tests/js/libgjstesttools/meson.build +++ b/installed-tests/js/libgjstesttools/meson.build @@ -13,12 +13,12 @@ gjstest_tools_sources = [ ] libgjstesttools = library('gjstesttools', sources: gjstest_tools_sources, - include_directories: top_include, dependencies: libcjs_dep, - cpp_args: libcjs_cpp_args + libgjstesttools_extra_cpp_args, + include_directories: top_include, dependencies: libgjs_dep, + cpp_args: libgjs_cpp_args + libgjstesttools_extra_cpp_args, install: get_option('installed_tests'), install_dir: installed_tests_execdir) gjstest_tools_gir = gnome.generate_gir(libgjstesttools, includes: ['GObject-2.0', 'Gio-2.0'], sources: gjstest_tools_sources, - namespace: 'CjsTestTools', nsversion: '1.0', + namespace: 'GjsTestTools', nsversion: '1.0', symbol_prefix: 'gjs_test_tools_', fatal_warnings: get_option('werror'), install: get_option('installed_tests'), install_gir: false, install_dir_typelib: installed_tests_execdir) diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build index aac965754..5d8d60bb8 100644 --- a/installed-tests/js/meson.build +++ b/installed-tests/js/meson.build @@ -8,7 +8,7 @@ jsunit_resources_files = gnome.compile_resources('jsunit-resources', 'jsunit.gresources.xml', c_name: 'jsunit_resources') minijasmine = executable('minijasmine', '../minijasmine.cpp', - jsunit_resources_files, dependencies: libcjs_dep, + jsunit_resources_files, dependencies: libgjs_dep, cpp_args: [ '-DINSTTESTDIR="@0@"'.format(prefix / installed_tests_execdir), ], @@ -54,10 +54,16 @@ jasmine_tests = [ if not get_option('skip_gtk_tests') jasmine_tests += [ - 'Gtk3', 'GObjectDestructionAccess', 'LegacyGtk', ] + + if have_gtk3 + jasmine_tests += 'Gtk3' + endif + if have_gtk4 + jasmine_tests += 'Gtk4' + endif endif installed_js_tests_dir = installed_tests_execdir / 'js' @@ -66,12 +72,15 @@ gschemas_compiled = gnome.compile_schemas( depend_files: 'org.cinnamon.CjsTest.gschema.xml') tests_dependencies = [ + gir_deps, gschemas_compiled, - cjs_private_typelib, + gjs_private_typelib, gjstest_tools_typelib, gi_tests.get_variable('gimarshallingtests_typelib'), gi_tests.get_variable('regress_typelib'), + gi_tests.get_variable('regress_unix_typelib'), gi_tests.get_variable('warnlib_typelib'), + gi_tests.get_variable('utility_typelib'), ] foreach test : jasmine_tests @@ -99,45 +108,32 @@ if get_option('installed_tests') install_subdir('modules', install_dir: installed_js_tests_dir) endif -# testGDBus.js and testGtk4.js are separate, because they can be skipped, and +# testGDBus.js is separate, because it can be skipped, and # during build should be run using dbus-run-session +bus_config = files('../../test/test-bus.conf') +dbus_test_file = files('testGDBus.js') -dbus_tests = ['GDBus'] -if not get_option('skip_gtk_tests') - have_gtk4 = dependency('gtk4', required: false).found() - - if have_gtk4 - # FIXME: find out why GTK4 tries to acquire a message bus - dbus_tests += 'Gtk4' - endif +if not get_option('skip_dbus_tests') + test('GDBus', dbus_run_session, + args: ['--config-file', bus_config, '--', minijasmine, dbus_test_file], + env: tests_environment, protocol: 'tap', suite: 'dbus', + depends: tests_dependencies) endif -bus_config = files('../../test/test-bus.conf') -foreach test : dbus_tests - test_file = files('test@0@.js'.format(test)) +dbus_test_description_subst = { + 'name': 'testGDBus.js', + 'installed_tests_execdir': prefix / installed_tests_execdir, +} +configure_file( + configuration: dbus_test_description_subst, + input: '../minijasmine.test.in', + output: 'testGDBus.test', + install: get_option('installed_tests'), + install_dir: installed_tests_metadir) - 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', - depends: tests_dependencies) - endif - - dbus_test_description_subst = { - 'name': 'test@0@.js'.format(test), - 'installed_tests_execdir': prefix / installed_tests_execdir, - } - 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) - - if get_option('installed_tests') - install_data(test_file, install_dir: installed_js_tests_dir) - endif -endforeach +if get_option('installed_tests') + install_data(dbus_test_file, install_dir: installed_js_tests_dir) +endif # tests using ES modules are also separate because they need an extra # minijasmine flag @@ -152,6 +148,7 @@ modules_tests = [ 'GLibLogWriter', 'Global', 'Timers', + 'Utility', 'WeakRef', ] diff --git a/installed-tests/js/modules/badOverrides/.eslintrc.yml b/installed-tests/js/modules/badOverrides/.eslintrc.yml deleted file mode 100644 index b1c10f5d2..000000000 --- a/installed-tests/js/modules/badOverrides/.eslintrc.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -rules: - no-throw-literal: 'off' # these are intended to be bad code - no-unused-vars: - - error - - varsIgnorePattern: ^_init$ diff --git a/installed-tests/js/modules/badOverrides2/.eslintrc.yml b/installed-tests/js/modules/badOverrides2/.eslintrc.yml deleted file mode 100644 index b1c10f5d2..000000000 --- a/installed-tests/js/modules/badOverrides2/.eslintrc.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -rules: - no-throw-literal: 'off' # these are intended to be bad code - no-unused-vars: - - error - - varsIgnorePattern: ^_init$ diff --git a/installed-tests/js/modules/networkURI.js b/installed-tests/js/modules/networkURI.js new file mode 100644 index 000000000..c2b352c1f --- /dev/null +++ b/installed-tests/js/modules/networkURI.js @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Philip Chimento + +export {foo} from 'https://gitlab.gnome.org/GNOME/gjs/-/raw/ce4411f5d9b6fc00ab8d949890037bd351634d5f/installed-tests/js/modules/say.js'; diff --git a/installed-tests/js/modules/overrides/.eslintrc.yml b/installed-tests/js/modules/overrides/.eslintrc.yml deleted file mode 100644 index 8a5f8fd93..000000000 --- a/installed-tests/js/modules/overrides/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -rules: - no-unused-vars: - - error - - varsIgnorePattern: ^_init$ diff --git a/installed-tests/js/modules/scaryURI.js b/installed-tests/js/modules/scaryURI.js new file mode 100644 index 000000000..f47183c19 --- /dev/null +++ b/installed-tests/js/modules/scaryURI.js @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Philip Chimento + +export {foo} from 'scary:///module.js'; diff --git a/installed-tests/js/testCairo.js b/installed-tests/js/testCairo.js index 786bf2a22..3816b5b7e 100644 --- a/installed-tests/js/testCairo.js +++ b/installed-tests/js/testCairo.js @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC +// SPDX-FileCopyrightText: 2024 Philip Chimento imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gtk = '3.0'; @@ -301,31 +302,33 @@ describe('Cairo', function () { describe('GI test suite', function () { describe('for context', function () { - it('can be marshalled as a return value', function () { - const outCr = Regress.test_cairo_context_full_return(); - const outSurface = outCr.getTarget(); - expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); - expect(outSurface.getWidth()).toEqual(10); - expect(outSurface.getHeight()).toEqual(10); - }); + ['none', 'full'].forEach(transfer => { + it(`can be marshalled as a transfer ${transfer} return value`, function () { + const outCr = Regress[`test_cairo_context_${transfer}_return`](); + const outSurface = outCr.getTarget(); + expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); + expect(outSurface.getWidth()).toEqual(10); + expect(outSurface.getHeight()).toEqual(10); + }); - it('can be marshalled as an in parameter', function () { - expect(() => Regress.test_cairo_context_none_in(cr)).not.toThrow(); + it(`can be marshalled as a transfer ${transfer} in parameter`, function () { + expect(() => Regress[`test_cairo_context_${transfer}_in`](cr)).not.toThrow(); + }); }); }); describe('for surface', function () { ['none', 'full'].forEach(transfer => { - it(`can be marshalled as a transfer-${transfer} return value`, function () { + it(`can be marshalled as a transfer ${transfer} return value`, function () { const outSurface = Regress[`test_cairo_surface_${transfer}_return`](); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); - }); - it('can be marshalled as an in parameter', function () { - expect(() => Regress.test_cairo_surface_none_in(surface)).not.toThrow(); + it(`can be marshalled as a transfer ${transfer} in parameter`, function () { + expect(() => Regress[`test_cairo_surface_${transfer}_in`](surface)).not.toThrow(); + }); }); it('can be marshalled as an out parameter', function () { @@ -336,6 +339,105 @@ describe('Cairo', function () { }); }); + describe('for path', function () { + it('can be marshalled as a return value', function () { + const path = Regress.test_cairo_path_full_return(); + expect(path).toBeInstanceOf(Cairo.Path); + }); + + it('can be marshalled as an in parameter with transfer none', function () { + const path = cr.copyPath(); + Regress.test_cairo_path_none_in(path); + }); + + it('can be marshalled as an in parameter with transfer full', function () { + const path = cr.copyPath(); + const outPath = Regress.test_cairo_path_full_in_full_return(path); + expect(outPath).toBeInstanceOf(Cairo.Path); + }); + }); + + describe('for pattern', function () { + ['none', 'full'].forEach(transfer => { + it(`can be marshalled as a return value with transfer ${transfer}`, function () { + const pattern = Regress[`test_cairo_pattern_${transfer}_return`](); + expect(pattern).toBeInstanceOf(Cairo.Pattern); + }); + + it(`can be marshalled as an in parameter with transfer ${transfer}`, function () { + const pattern = Cairo.SolidPattern.createRGB(1, 2, 3); + Regress[`test_cairo_pattern_${transfer}_in`](pattern); + }); + }); + }); + + describe('for region', function () { + it('can be marshalled as an in argument', function () { + const region = new Cairo.Region(); + Regress.test_cairo_region_full_in(region); + }); + }); + + xdescribe('for FontOptions', function () { + ['none', 'full'].forEach(transfer => { + it(`can be marshalled as a return value with transfer ${transfer}`, function () { + const fontOptions = Regress[`test_cairo_font_options_${transfer}_return`](); + expect(fontOptions).toBeInstanceOf(Cairo.FontOptions); + }); + + it(`can be marshalled as an in parameter with transfer ${transfer}`, function () { + const fontOptions = Cairo.SolidPattern.createRGB(1, 2, 3); + Regress[`test_cairo_font_options_${transfer}_in`](fontOptions); + }); + }); + }).pend('Cairo.FontOptions not implemented. Open an issue if you need this'); + + xdescribe('for FontFace', function () { + it('can be marshalled as a return value with transfer full', function () { + const fontFace = Regress.test_cairo_font_face_full_return(cr); + expect(fontFace).toBeInstanceOf(Cairo.FontFace); + }); + }).pend('Cairo.FontFace not implemented. Open an issue if you need this'); + + xdescribe('for ScaledFont', function () { + it('can be marshalled as a return value with transfer full', function () { + const scaledFont = Regress.test_cairo_scaled_font_full_return(cr); + expect(scaledFont).toBeInstanceOf(Cairo.ScaledFont); + }); + }).pend('Cairo.ScaledFont not implemented. Open an issue if you need this'); + + xdescribe('for matrix', function () { + it('can be marshalled as a return value', function () { + const matrix = Regress.test_cairo_matrix_none_return(); + expect(matrix).toEqual(jasmine.objectContaining({ + x0: 0, + y0: 0, + xx: 1, + xy: 0, + yy: 1, + yx: 0, + })); + }); + + it('can be marshalled as an in parameter', function () { + const matrix = new Cairo.Matrix(); + matrix.initIdentity(); + Regress.test_cairo_matrix_none_in(matrix); + }); + + it('can be marshalled as a caller-allocates out parameter', function () { + const matrix = Regress.test_cairo_matrix_out_caller_allocates(); + expect(matrix).toEqual(jasmine.objectContaining({ + x0: 0, + y0: 0, + xx: 1, + xy: 0, + yy: 1, + yx: 0, + })); + }); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/662'); + it('can be marshalled through a signal handler', function () { let o = new Regress.TestObj(); let foreignSpy = jasmine.createSpy('sig-with-foreign-struct'); diff --git a/installed-tests/js/testConsole.js b/installed-tests/js/testConsole.js index f7110cfa2..ee0e7e219 100644 --- a/installed-tests/js/testConsole.js +++ b/installed-tests/js/testConsole.js @@ -107,6 +107,10 @@ describe('console', function () { writer_func.calls.reset(); }); + afterAll(function () { + GLib.log_set_writer_default(); + }); + it('has correct object tag', function () { expect(console.toString()).toBe('[object console]'); }); diff --git a/installed-tests/js/testESModules.js b/installed-tests/js/testESModules.js index 02bdf8681..994b1ae08 100644 --- a/installed-tests/js/testESModules.js +++ b/installed-tests/js/testESModules.js @@ -94,6 +94,16 @@ describe('ES module imports', function () { expect(greeting1).toEqual('Hello, Test Code'); expect(greeting2).toEqual('Bonjour, Code de Test'); }); + + it('rejects imports from a nonsense URI scheme', async function () { + await expectAsync(import('resource:///org/cjs/jsunit/modules/scaryURI.js')) + .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); + }); + + it('rejects imports from a real but unsupported URI scheme', async function () { + await expectAsync(import('resource:///org/cjs/jsunit/modules/networkURI.js')) + .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); + }); }); describe('Builtin ES modules', function () { @@ -208,4 +218,14 @@ describe('Dynamic imports', function () { expect(e.stack).not.toMatch('internal/'); } }); + + it('rejects imports from a nonsense URI scheme', async function () { + await expectAsync(import('scary:///module.js')) + .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); + }); + + it('rejects imports from a real but unsupported URI scheme', async function () { + await expectAsync(import('https://gitlab.gnome.org/GNOME/gjs/-/raw/ce4411f5d9b6fc00ab8d949890037bd351634d5f/installed-tests/js/modules/say.js')) + .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); + }); }); diff --git a/installed-tests/js/testExceptions.js b/installed-tests/js/testExceptions.js index 391544b8f..3a0f7cb09 100644 --- a/installed-tests/js/testExceptions.js +++ b/installed-tests/js/testExceptions.js @@ -39,12 +39,12 @@ describe('Exceptions', function () { // FIXME: In the next cases the errors aren't thrown but logged it('are logged from constructor', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); new Foo({prop: 'bar'}); - GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterFromConstructor'); }); @@ -55,13 +55,13 @@ describe('Exceptions', function () { bar.bind_property('prop', foo, 'prop', GObject.BindingFlags.DEFAULT); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); // wake up the binding so that g_object_set() is called on foo bar.notify('prop'); - GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterWithBinding'); }); @@ -72,25 +72,25 @@ describe('Exceptions', function () { foo.bind_property('prop', bar, 'prop', GObject.BindingFlags.DEFAULT); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: get*'); // wake up the binding so that g_object_get() is called on foo foo.notify('prop'); - GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertyGetterWithBinding'); }); }); describe('logError', function () { afterEach(function () { - GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', + GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testGErrorMessages'); }); it('logs a warning for a GError', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); @@ -101,7 +101,7 @@ describe('logError', function () { }); it('logs a warning with a message if given', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); @@ -111,25 +111,25 @@ describe('logError', function () { }); it('also logs an error for a created GError that is not thrown', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new Gio.IOErrorEnum({message: 'a message', code: 0})); }); it('logs an error created with the GLib.Error constructor', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message')); }); it('logs the quark for a JS-created GError type', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error my-error: a message\nmarker@*'); logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message')); }); it('logs with stack for a GError created from a C struct', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error gi-marshalling-tests-gerror-domain: gi-marshalling-tests-gerror-message\nmarker@*'); logError(GIMarshallingTests.gerror_return()); }); @@ -137,7 +137,7 @@ describe('logError', function () { // Now with prefix it('logs an error with a prefix if given', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); @@ -148,7 +148,7 @@ describe('logError', function () { }); it('logs an error with prefix and message', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); @@ -163,7 +163,7 @@ describe('logError', function () { } it('logs a SyntaxError', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*'); try { throwSyntaxError(); @@ -173,7 +173,7 @@ describe('logError', function () { }); it('logs a stack trace with the SyntaxError', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*throwSyntaxError@*'); try { throwSyntaxError(); @@ -184,7 +184,7 @@ describe('logError', function () { }); it('logs an error with cause', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\nmarker@*Caused by: Gio.IOErrorEnum: another error\nmarker2@*'); function marker2() { return new Gio.IOErrorEnum({message: 'another error', code: 0}); @@ -193,7 +193,7 @@ describe('logError', function () { }); it('logs a GError with cause', function marker() { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: an error\nmarker@*Caused by: Error: another error\nmarker2@*'); function marker2() { return new Error('another error'); @@ -204,13 +204,13 @@ describe('logError', function () { }); it('logs an error with non-object cause', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\n*Caused by: 3'); logError(new Error('an error', {cause: 3})); }); it('logs an error with a cause tree', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*Caused by: Error: three\n*'); const three = new Error('three'); const two = new Error('two', {cause: three}); @@ -221,7 +221,7 @@ describe('logError', function () { // We cannot assert here with GLib.test_expect_message that the * at the // end of the string doesn't match more causes, but at least the idea is // that it shouldn't go into an infinite loop - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*'); const one = new Error('one'); one.cause = new Error('two', {cause: one}); @@ -280,3 +280,37 @@ describe('GError.new_literal', function () { .toThrowError(/0 is not a valid domain/); }); }); + +describe('Interoperation with Error.isError', function () { + it('thrown GError', function () { + let err; + try { + const file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); + file.read(null); + } catch (e) { + err = e; + } + expect(Error.isError(err)).toBeTruthy(); + }); + + xit('returned GError', function () { + const err = GIMarshallingTests.gerror_return(); + expect(Error.isError(err)).toBeTruthy(); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/700'); + + it('created GError', function () { + const err = new Gio.IOErrorEnum({message: 'a message', code: 0}); + expect(Error.isError(err)).toBeTruthy(); + }); + + it('GError created with the GLib.Error constructor', function () { + const err = new GLib.Error(Gio.IOErrorEnum, 0, 'a message'); + expect(Error.isError(err)).toBeTruthy(); + }); + + it('GError created with GLib.Error.new_literal', function () { + const err = GLib.Error.new_literal( + Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'message'); + expect(Error.isError(err)).toBeTruthy(); + }); +}); diff --git a/installed-tests/js/testGDBus.js b/installed-tests/js/testGDBus.js index ed646bddf..06093b2cf 100644 --- a/installed-tests/js/testGDBus.js +++ b/installed-tests/js/testGDBus.js @@ -1,24 +1,14 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC -const {Gio, CjsTestTools, GLib} = imports.gi; - -// Adapter for compatibility with pre-GLib-2.80 -let GioUnix; -if (imports.gi.versions.GioUnix === '2.0') { - GioUnix = imports.gi.GioUnix; -} else { - GioUnix = { - InputStream: Gio.UnixInputStream, - }; -} +const {Gio, GjsTestTools, GLib, GioUnix} = imports.gi; /* The methods list with their signatures. * * *** NOTE: If you add stuff here, you need to update the Test class below. */ var TestIface = ` - + @@ -260,14 +250,14 @@ class Test { } fdOut(bytes) { - const fd = CjsTestTools.open_bytes(bytes); + const fd = GjsTestTools.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); return [0, fdList]; } fdOut2Async([bytes], invocation) { GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { - const fd = CjsTestTools.open_bytes(bytes); + const fd = GjsTestTools.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); invocation.return_value_with_unix_fd_list(new GLib.Variant('(h)', [0]), fdList); @@ -306,7 +296,7 @@ describe('Exported DBus object', function () { loop = new GLib.MainLoop(null, false); test = new Test(); - ownNameID = Gio.DBus.session.own_name('org.cinnamon.cjs.Test', + ownNameID = Gio.DBus.session.own_name('org.gnome.gjs.Test', Gio.BusNameOwnerFlags.NONE, name => { log(`Acquired name ${name}`); @@ -316,7 +306,7 @@ describe('Exported DBus object', function () { log(`Lost name ${name}`); }); loop.run(); - new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', + new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', (obj, error) => { expect(error).toBeNull(); @@ -354,7 +344,7 @@ describe('Exported DBus object', function () { it('can initiate a proxy with promise and call a method with async/await', async function () { const asyncProxy = await ProxyClass.newAsync(Gio.DBus.session, - 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test'); + 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test'); expect(asyncProxy).toBeInstanceOf(Gio.DBusProxy); const [{hello}] = await asyncProxy.frobateStuffAsync({}); expect(hello.deepUnpack()).toEqual('world'); @@ -367,7 +357,7 @@ describe('Exported DBus object', function () { Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.DO_NOT_AUTO_START, iface, - 'org.cinnamon.cjs.Test', + 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', iface.name, null, @@ -388,7 +378,7 @@ describe('Exported DBus object', function () { /* excp must be exactly the exception thrown by the remote method (more or less) */ it('can handle an exception thrown by a remote method', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); proxy.alwaysThrowExceptionRemote({}, function (result, excp) { @@ -399,14 +389,14 @@ describe('Exported DBus object', function () { }); it('can handle an exception thrown by a method with async/await', async function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); await expectAsync(proxy.alwaysThrowExceptionAsync({})).toBeRejected(); }); it('can still destructure the return value when an exception is thrown', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); // This test will not fail, but instead if the functionality is not @@ -693,7 +683,7 @@ describe('Exported DBus object', function () { }); it('can call a remote method with a Unix FD', function (done) { - const fd = CjsTestTools.open_bytes(expectedBytes); + const fd = GjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdInRemote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); @@ -704,7 +694,7 @@ describe('Exported DBus object', function () { }); it('can call an async/await method with a Unix FD', async function () { - const fd = CjsTestTools.open_bytes(expectedBytes); + const fd = GjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); const [bytes, outFdList] = await proxy.fdInAsync(0, fdList); expect(outFdList).not.toBeDefined(); @@ -712,7 +702,7 @@ describe('Exported DBus object', function () { }); it('can call an asynchronously implemented remote method with a Unix FD', function (done) { - const fd = CjsTestTools.open_bytes(expectedBytes); + const fd = GjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdIn2Remote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); @@ -723,7 +713,7 @@ describe('Exported DBus object', function () { }); it('can call an asynchronously implemented async/await method with a Unix FD', async function () { - const fd = CjsTestTools.open_bytes(expectedBytes); + const fd = GjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); const [bytes, outFdList] = await proxy.fdIn2Async(0, fdList); expect(outFdList).not.toBeDefined(); @@ -887,10 +877,14 @@ describe('DBus Proxy wrapper', function () { writerFunc.calls.reset(); }); + afterAll(function () { + GLib.log_set_writer_default(); + }); + it('init failures are reported in sync mode', function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); - expect(() => new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', + expect(() => new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', Gio.DBusProxyFlags.NONE, cancellable)).toThrow(); @@ -902,7 +896,7 @@ describe('DBus Proxy wrapper', function () { const initDoneSpy = jasmine.createSpy( 'init finish func', () => loop.quit()); initDoneSpy.and.callThrough(); - new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', + new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', initDoneSpy, cancellable, Gio.DBusProxyFlags.NONE); loop.run(); @@ -915,7 +909,7 @@ describe('DBus Proxy wrapper', function () { }); it('can init a proxy asynchronously when promisified', function () { - new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', + new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', () => loop.quit(), Gio.DBusProxyFlags.NONE); @@ -925,7 +919,7 @@ describe('DBus Proxy wrapper', function () { }); it('can create a proxy from a promise', async function () { - const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.cinnamon.cjs.Test', + const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test'); await expectAsync(proxyPromise).toBeResolved(); }); @@ -933,7 +927,7 @@ describe('DBus Proxy wrapper', function () { it('can create fail a proxy from a promise', async function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); - const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.cinnamon.cjs.Test', + const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/cinnamon/cjs/Test', cancellable); await expectAsync(proxyPromise).toBeRejected(); }); diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js index a4270a51c..9fe107b3c 100644 --- a/installed-tests/js/testGIMarshalling.js +++ b/installed-tests/js/testGIMarshalling.js @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2010 Giovanni Campagna // SPDX-FileCopyrightText: 2011 Red Hat, Inc. // SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. -// SPDX-FileCopyrightText: 2019 Philip Chimento +// SPDX-FileCopyrightText: 2019, 2024 Philip Chimento // Load overrides for GIMarshallingTests imports.overrides.searchPath.unshift('resource:///org/cjs/jsunit/modules/overrides'); @@ -88,9 +88,6 @@ function testTransferMarshalling(root, value, inoutValue, defaultValue, options }); describe('with transfer full', function () { const fullOptions = { - in: { - omit: true, // this case is not in the test suite - }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192', }, @@ -105,7 +102,7 @@ function testContainerMarshalling(root, value, inoutValue, defaultValue, options describe('with transfer container', function () { const containerOptions = { in: { - omit: true, // this case is not in the test suite + skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', @@ -178,12 +175,12 @@ if (GLib.SIZEOF_SSIZE_T === 8) { // each other. That's fine for now. Then we just have to suppress the warnings. function warn64(is64bit, func, ...args) { if (is64bit) { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); } const retval = func(...args); if (is64bit) { - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'Ignore message'); } return retval; @@ -330,6 +327,30 @@ describe('time_t', function () { testSimpleMarshalling('time_t', 1234567890, 0, 0); }); +describe('off_t', function () { + testSimpleMarshalling('off_t', 1234567890, 0, 0); +}); + +function testUnixIntegerTypedefMarshalling(type, inValue, skipAny = {}) { + describe(type, function () { + const skip = GIMarshallingTests[`${type}_in`] ? false : 'Only supported on Unix'; + testSimpleMarshalling(type, inValue, 0, 0, { + returnv: {skip: skip || skipAny.skipReturn}, + in: {skip: skip || skipAny.skipIn}, + out: {skip: skip || skipAny.skipOut}, + uninitOut: {skip: skip || skipAny.skipUninitOut}, + inout: {skip: skip || skipAny.skipInOut}, + }); + }); +} + +// https://gitlab.gnome.org/GNOME/gjs/-/issues/673 +testUnixIntegerTypedefMarshalling('dev_t', 1234567890, {skipInOut: true}); +testUnixIntegerTypedefMarshalling('gid_t', 65534); +testUnixIntegerTypedefMarshalling('pid_t', 12345); +testUnixIntegerTypedefMarshalling('socklen_t', 123); +testUnixIntegerTypedefMarshalling('uid_t', 65534); + describe('GType', function () { describe('void', function () { testSimpleMarshalling('gtype', GObject.TYPE_NONE, GObject.TYPE_INT, null); @@ -391,6 +412,7 @@ describe('Fixed-size C array', function () { testReturnValue('array_fixed_int', [-1, 0, 1, 2]); testInParameter('array_fixed_int', [-1, 0, 1, 2]); testOutParameter('array_fixed', [-1, 0, 1, 2]); + testUninitializedOutParameter('array_fixed', null); testOutParameter('array_fixed_caller_allocated', [-1, 0, 1, 2]); testInoutParameter('array_fixed', [-1, 0, 1, 2], [2, 1, 0, -1]); }); @@ -407,6 +429,10 @@ describe('Fixed-size C array', function () { ]); }); + it('picks a reasonable default for struct array out param when uninitialized', function () { + expect(GIMarshallingTests.array_fixed_out_struct_uninitialized()).toEqual([false, null]); + }); + it('marshals a fixed-size struct array as caller allocated out param', function () { expect(GIMarshallingTests.array_fixed_caller_allocated_struct_out()).toEqual([ jasmine.objectContaining({long_: -2, int8: -1}), @@ -415,6 +441,15 @@ describe('Fixed-size C array', function () { jasmine.objectContaining({long_: 5, int8: 6}), ]); }); + + for (const marshal of ['return', 'out']) { + it(`handles a ${marshal} array with odd alignment`, function () { + const arr = GIMarshallingTests[`array_fixed_${marshal}_unaligned`](); + expect(arr.length).toEqual(32); + expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); + GIMarshallingTests.cleanup_unaligned_buffer(); + }); + } }); describe('C array with length', function () { @@ -604,6 +639,15 @@ describe('C array with length', function () { it('does not interpret an unannotated integer as a length parameter', function () { expect(() => GIMarshallingTests.array_in_nonzero_nonlen(42, 'abcd')).not.toThrow(); }); + + for (const marshal of ['return', 'out']) { + it(`handles a ${marshal} array with odd alignment`, function () { + const arr = GIMarshallingTests[`array_${marshal}_unaligned`](); + expect(arr.length).toEqual(32); + expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); + GIMarshallingTests.cleanup_unaligned_buffer(); + }); + } }); describe('Zero-terminated C array', function () { @@ -621,6 +665,11 @@ describe('Zero-terminated C array', function () { expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]); }); + it('marshals an array of sequential structs as a return value', function () { + let structArray = GIMarshallingTests.array_zero_terminated_return_sequential_struct(); + expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]); + }); + it('marshals an array of unichars as a return value', function () { expect(GIMarshallingTests.array_zero_terminated_return_unichar()) .toEqual('const ♥ utf8'); @@ -644,6 +693,50 @@ describe('Zero-terminated C array', function () { }); }); }); + + for (const marshal of ['return', 'out']) { + it(`handles a ${marshal} array with odd alignment`, function () { + const arr = GIMarshallingTests[`array_zero_terminated_${marshal}_unaligned`](); + expect(Array.from(arr)).toEqual([1, 2, 3, 4, 5, 6, 7]); + GIMarshallingTests.cleanup_unaligned_buffer(); + }); + } +}); + +describe('Exhaustive test of UTF-8 sequences', function () { + ['length', 'fixed', 'zero_terminated'].forEach(arrayKind => + ['none', 'container', 'full'].forEach(transfer => { + const testFunction = returnMode => { + const commonName = 'array_utf8'; + const funcName = [arrayKind, commonName, transfer, returnMode].join('_'); + const func = GIMarshallingTests[funcName]; + if (!func) // FIXME + pending('https://gitlab.gnome.org/GNOME/gobject-introspection-tests/-/merge_requests/11'); + return func; + }; + + ['out', 'return'].forEach(returnMode => + it(`${arrayKind} ${returnMode} transfer ${transfer}`, function () { + const func = testFunction(returnMode); + expect(func()).toEqual(['a', 'b', '¢', '🔠']); + })); + + it(`${arrayKind} in transfer ${transfer}`, function () { + const func = testFunction('in'); + if (transfer === 'container') + pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); + + expect(() => func(['🅰', 'β', 'c', 'd'])).not.toThrow(); + }); + + it(`${arrayKind} inout transfer ${transfer}`, function () { + const func = testFunction('inout'); + if (transfer === 'container') + pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); + + expect(func(['🅰', 'β', 'c', 'd'])).toEqual(['a', 'b', '¢', '🔠']); + }); + })); }); describe('GArray', function () { @@ -652,6 +745,10 @@ describe('GArray', function () { testInParameter('garray_int_none', [-1, 0, 1, 2]); }); + it('marshals BigInt int64s as a transfer-none in value', function () { + GIMarshallingTests.garray_uint64_none_in([0, BigIntLimits.int64.umax]); + }); + it('marshals int64s as a transfer-none return value', function () { expect(warn64(true, GIMarshallingTests.garray_uint64_none_return)) .toEqual([0, Limits.int64.umax]); @@ -671,8 +768,7 @@ describe('GArray', function () { it('marshals as a transfer-full caller-allocated out parameter throws errors', function () { // should throw when called, not when the function object is created expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated).not.toThrow(); - expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated()) - .toThrowError(/type array.*\(out caller-allocates\)/); + expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated()).toThrow(); }); }); @@ -712,6 +808,8 @@ describe('GByteArray', function () { const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]); testReturnValue('bytearray_full', refByteArray); + testOutParameter('bytearray_full', refByteArray); + testInoutParameter('bytearray_full', refByteArray, Uint8Array.from([104, 101, 108, 0, 0xFF])); it('can be passed in with transfer none', function () { expect(() => GIMarshallingTests.bytearray_none_in(refByteArray)) @@ -982,7 +1080,8 @@ describe('GValue', function () { }); it('marshals as an int64 out parameter', function () { - expect(GIMarshallingTests.gvalue_int64_out()).toEqual(Limits.int64.max); + expect(warn64(true, GIMarshallingTests.gvalue_int64_out)).toEqual( + Limits.int64.max); }); it('marshals as a caller-allocated out parameter', function () { @@ -1109,6 +1208,15 @@ describe('GValue', function () { .not.toThrow(); }); + it('separates float from double correctly', function () { + // Passing a Number infers the type 'double'. To pass a float GValue, we + // need to construct it manually. + const floatValue = new GObject.Value(); + floatValue.init(GObject.TYPE_FLOAT); + floatValue.set_float(3.14); + expect(() => GIMarshallingTests.gvalue_float(floatValue, 3.14)).not.toThrow(); + }); + // See testCairo.js for a test of GIMarshallingTests.gvalue_in_with_type() // on Cairo foreign structs, since it will be skipped if compiling without // Cairo support. @@ -1273,6 +1381,8 @@ describe('Boxed struct', function () { })); }); + testUninitializedOutParameter('boxed_struct', null); + it('marshals as an inout parameter', function () { const struct = new GIMarshallingTests.BoxedStruct({ long_: 42, @@ -1299,6 +1409,73 @@ describe('Union', function () { }); }); +describe('Structured union', function () { + xit('can be created with a default constructor', function () { + const u = new GIMarshallingTests.StructuredUnion(GIMarshallingTests.StructuredUnionType.NONE); + expect(u).toBeInstanceOf(GIMarshallingTests.StructuredUnion); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/657'); + + it('can be created with NONE', function () { + const t = GIMarshallingTests.StructuredUnionType.NONE; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + }); + + it('can be created with SIMPLE_STRUCT', function () { + const t = GIMarshallingTests.StructuredUnionType.SIMPLE_STRUCT; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.simple_struct.parent.long_).toBe(6); + // expect(u.simple_struct.parent.int8).toBe(7); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); + + it('can be created with NESTED_STRUCT', function () { + const t = GIMarshallingTests.StructuredUnionType.NESTED_STRUCT; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.nested_struct.parent.simple_struct.parent.long_).toBe(6); + // expect(u.nested_struct.parent.simple_struct.parent.int8).toBe(7); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); + + it('can be created with BOXED_STRUCT', function () { + const t = GIMarshallingTests.StructuredUnionType.BOXED_STRUCT; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.boxed_struct.parent.long_).toBe(42); + // expect(u.boxed_struct.parent.string_).toBe('hello'); + // expect(u.boxed_struct.parent.g_strv).toBe(['0', '1', '2']); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); + + it('can be created with BOXED_STRUCT_PTR', function () { + const t = GIMarshallingTests.StructuredUnionType.BOXED_STRUCT_PTR; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.boxed_struct_ptr.parent.long_).toBe(42); + // expect(u.boxed_struct_ptr.parent.string_).toBe('hello'); + // expect(u.boxed_struct_ptr.parent.g_strv).toBe(['0', '1', '2']); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); + + it('can be created with POINTER_STRUCT', function () { + const t = GIMarshallingTests.StructuredUnionType.POINTER_STRUCT; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.pointer_struct.parent.long_).toBe(42); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); + + it('can be created with SINGLE_UNION', function () { + const t = GIMarshallingTests.StructuredUnionType.SINGLE_UNION; + const u = GIMarshallingTests.StructuredUnion.new(t); + expect(u.type()).toBe(t); + // expect(u.single_union.parent.union_.long_).toBe(42); + // https://gitlab.gnome.org/GNOME/gjs/-/issues/273 + }); +}); + describe('GObject', function () { it('has a static method that can be called', function () { expect(() => GIMarshallingTests.Object.static_method()).not.toThrow(); @@ -1359,6 +1536,10 @@ describe('GObject', function () { }); }); + it(`picks a reasonable default when uninitialized as out parameter with transfer ${transfer}`, function () { + expect(GIMarshallingTests.Object[`${transfer}_out_uninitialized`]()).toEqual([false, null]); + }); + it(`marshals as an inout parameter with transfer ${transfer}`, function () { const o = new GIMarshallingTests.Object({int: 42}); expect(GIMarshallingTests.Object[`${transfer}_inout`](o).int).toEqual(0); @@ -1552,10 +1733,19 @@ describe('Virtual function', function () { expect(tester.int).toEqual(39); }); + it('marshals an in argument through a method that indirectly calls the vfunc', function () { + tester.int8_in(39); + expect(tester.int).toEqual(39); + }); + it('marshals an out argument', function () { expect(tester.method_int8_out()).toEqual(40); }); + it('marshals an out argument through a method that indirectly calls the vfunc', function () { + expect(tester.int8_out()).toEqual(40); + }); + it('marshals a POD out argument', function () { expect(tester.method_int8_arg_and_out_caller(39)).toEqual(42); }); @@ -1789,86 +1979,151 @@ describe('Wrong virtual functions', function () { }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/311'); it('marshals multiple out parameters', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_vfunc_multiple_out_parameters*Array*'); expect(tester.vfunc_multiple_out_parameters()).toEqual([0, 0]); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and one out parameter', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_return_value_and_one_out_parameter*Array*'); expect(tester.vfunc_return_value_and_one_out_parameter()).toEqual([0, 0]); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and multiple out parameters', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_return_value_and_multiple_out_parameters*Array*'); expect(tester.vfunc_return_value_and_multiple_out_parameters()).toEqual([0, 0, 0]); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an array out parameter', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type gfloat for Argument*undefined*'); expect(tester.vfunc_array_out_parameter()).toEqual(null); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum return value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - 'JS ERROR: Error: Expected type enum for Return*undefined*'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + 'JS ERROR: Error: Expected type *Enum* for Return*undefined*'); expect(tester.vfunc_return_enum()).toEqual(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum out parameter', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - 'JS ERROR: Error: Expected type enum for Argument*undefined*'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + 'JS ERROR: Error: Expected type *Enum* for Argument*undefined*'); expect(tester.vfunc_out_enum()).toEqual(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags return value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - 'JS ERROR: Error: Expected type flags for Return*undefined*'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + 'JS ERROR: Error: Expected type *Flags* for Return*undefined*'); expect(tester.vfunc_return_flags()).toEqual(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags out parameter', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, - 'JS ERROR: Error: Expected type flags for Argument*undefined*'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + 'JS ERROR: Error: Expected type *Flags* for Argument*undefined*'); expect(tester.vfunc_out_flags()).toEqual(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); }); +let StaticVFuncTester; +try { + StaticVFuncTester = GObject.registerClass( + class StaticVFuncTesterClass extends VFuncTester { + static vfunc_vfunc_static_name() { + return 'Overridden name'; + } + + static vfunc_vfunc_static_create_new(int) { + return new StaticVFuncTester({int}); + } + + static vfunc_vfunc_static_create_new_out(int) { + return new StaticVFuncTester({int}); + } + }); +} catch (e) { + if (!`${e}`.includes('Could not find definition of virtual function')) + throw e; +} + +describe('Static virtual functions', function () { + beforeEach(function () { + if (!StaticVFuncTester) { + if (GIMarshallingTests.Object.vfunc_static_name) + pending('https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4457'); + } + }); + + it('has static_name', function () { + expect(GIMarshallingTests.Object.vfunc_static_name()).toBe( + 'GIMarshallingTestsObject'); + expect(StaticVFuncTester.vfunc_static_name()).toBe( + 'GIMarshallingTestsObject'); + }); + + it('has static_typed_name', function () { + expect(GIMarshallingTests.Object.vfunc_static_typed_name( + GIMarshallingTests.Object.$gtype)).toBe('GIMarshallingTestsObject'); + expect(StaticVFuncTester.vfunc_static_typed_name(StaticVFuncTester.$gtype)) + .toBe('Overridden name'); + }); + + ['', '_out'].forEach(suffix => it(`has static_create_new${suffix}`, function () { + const baseObj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( + GIMarshallingTests.Object.$gtype, 55); + expect(baseObj).toBeInstanceOf(GIMarshallingTests.Object); + expect(baseObj).not.toBeInstanceOf(StaticVFuncTester); + expect(baseObj.int_).toBe(55); + + const middleObj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( + VFuncTester.$gtype, 35); + expect(middleObj).toBeInstanceOf(GIMarshallingTests.Object); + expect(middleObj).not.toBeInstanceOf(VFuncTester); + expect(middleObj.int_).toBe(35); + + const obj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( + StaticVFuncTester.$gtype, 85); + expect(obj).toBeInstanceOf(GIMarshallingTests.Object); + expect(obj).toBeInstanceOf(VFuncTester); + expect(obj).toBeInstanceOf(StaticVFuncTester); + expect(obj.int_).toBe(85); + })); +}); + describe('Inherited GObject', function () { ['SubObject', 'SubSubObject'].forEach(klass => { describe(klass, function () { @@ -1892,6 +2147,29 @@ describe('Inherited GObject', function () { o.method_with_default_implementation(43); expect(o.int).toEqual(43); }); + + it('has a vfunc default implementation that can be called', function () { + const o = new GIMarshallingTests[klass]({int: 0}); + o.vfunc_method_deep_hierarchy(44); + expect(o.int).toBe(44); + }); + + it('has a vfunc that can be overridden', function () { + class Derived extends GIMarshallingTests[klass] { + static [GObject.GTypeName] = `Derived${klass}`; + static { + GObject.registerClass(Derived); + } + + vfunc_method_deep_hierarchy(param) { + expect(param).toBe(45); + this.int = 46; + } + } + const o = new Derived({int: 0}); + o.vfunc_method_deep_hierarchy(45); + expect(o.int).toBe(46); + }); }); }); }); @@ -2033,6 +2311,8 @@ describe('GError', function () { ]); }); + testUninitializedOutParameter('gerror', null); + it('marshals a GError** elsewhere in the signature as an out parameter with transfer none', function () { expect(GIMarshallingTests.gerror_out_transfer_none()).toEqual([ jasmine.any(GLib.Error), @@ -2040,6 +2320,10 @@ describe('GError', function () { ]); }); + it('picks a reasonable default value when out parameter is uninitialized with transfer none', function () { + expect(GIMarshallingTests.gerror_out_transfer_none_uninitialized()).toEqual([false, null, null]); + }); + it('marshals GError as a return value', function () { expect(GIMarshallingTests.gerror_return()).toEqual(jasmine.any(GLib.Error)); }); @@ -2074,6 +2358,11 @@ describe('Overrides', function () { const obj = GIMarshallingTests.OverridesObject.returnv(); expect(obj).toBeInstanceOf(GIMarshallingTests.OverridesObject); }); + + it('returns the overridden object from a C constructor', function () { + const obj = GIMarshallingTests.OverridesObject.new(); + expect(obj).toBeInstanceOf(GIMarshallingTests.OverridesObject); + }); }); describe('Filename', function () { @@ -2097,6 +2386,7 @@ describe('GObject.ParamSpec', function () { }; testReturnValue('param_spec', jasmine.objectContaining(expectedProps)); testOutParameter('param_spec', jasmine.objectContaining(expectedProps)); + testUninitializedOutParameter('param_spec', null); }); describe('GObject properties', function () { @@ -2134,17 +2424,21 @@ describe('GObject properties', function () { function testPropertyGetSetBigInt(type, value1, value2) { const snakeCase = `some_${type}`; const paramCase = snakeCase.replaceAll('_', '-'); + const isBigInt = v => + v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER); it(`gets and sets a ${type} property with a bigint`, function () { const handler = jasmine.createSpy(`handle-${paramCase}`); const id = obj.connect(`notify::${paramCase}`, handler); obj[snakeCase] = value1; expect(handler).toHaveBeenCalledTimes(1); - expect(obj[snakeCase]).toEqual(Number(value1)); + expect(warn64(isBigInt(value1), () => obj[snakeCase])).toEqual( + Number(value1)); obj[snakeCase] = value2; expect(handler).toHaveBeenCalledTimes(2); - expect(obj[snakeCase]).toEqual(Number(value2)); + expect(warn64(isBigInt(value2), () => obj[snakeCase])).toEqual( + Number(value2)); obj.disconnect(id); }); @@ -2162,7 +2456,8 @@ describe('GObject properties', function () { testPropertyGetSetBigInt('int64', BigIntLimits.int64.min, BigIntLimits.int64.max); testPropertyGetSet('uint64', 42, 64); testPropertyGetSetBigInt('uint64', BigIntLimits.int64.max, BigIntLimits.int64.umax); - testPropertyGetSet('string', 'Cjs', 'is cool!'); + testPropertyGetSetBigInt('uint64', 0n, BigInt(Number.MAX_SAFE_INTEGER)); + testPropertyGetSet('string', 'Gjs', 'is cool!'); testPropertyGetSet('string', 'and supports', null); it('get and sets out-of-range values throws', function () { @@ -2254,23 +2549,19 @@ describe('GObject properties', function () { expect(() => (obj.some_readonly = 35)).toThrow(); }); - it('allows to set/get deprecated properties', function () { - if (!GObject.Object.find_property.call( - GIMarshallingTests.PropertiesObject, 'some-deprecated-int')) - pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/410'); - - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + xit('allows to set/get deprecated properties', function () { + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); obj.some_deprecated_int = 35; - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); expect(obj.some_deprecated_int).toBe(35); - GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); - }); + }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/410'); const JSOverridingProperty = GObject.registerClass( class Overriding extends GIMarshallingTests.PropertiesObject { @@ -2361,6 +2652,146 @@ describe('GObject properties', function () { ids.forEach(id => overriding.disconnect(id)); }); + + it('can be created from C constructor as well', function () { + obj = GIMarshallingTests.PropertiesObject.new(); + expect(obj).toBeInstanceOf(GIMarshallingTests.PropertiesObject); + }); +}); + +describe('GObject properties accessors', function () { + let obj; + beforeEach(function () { + obj = new GIMarshallingTests.PropertiesAccessorsObject(); + }); + + function testPropertyGetSet(type, value1, value2, skip = false) { + const snakeCase = `some_${type}`; + const paramCase = snakeCase.replaceAll('_', '-'); + const camelCase = snakeCase.replace(/(_\w)/g, + match => match.toUpperCase().replace('_', '')); + + [snakeCase, paramCase, camelCase].forEach(propertyName => { + it(`gets and sets a ${type} property as ${propertyName}`, function () { + if (skip) + pending(skip); + obj[propertyName] = value1; + expect(obj[propertyName]).toEqual(value1); + obj[propertyName] = value2; + expect(obj[propertyName]).toEqual(value2); + }); + }); + } + + function testPropertyGetSetBigInt(type, value1, value2) { + const isBigInt = v => + v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER); + it(`gets and sets a ${type} property with a bigint`, function () { + obj[`some_${type}`] = value1; + expect(warn64(isBigInt(value1), () => obj[`some_${type}`])).toEqual( + Number(value1)); + obj[`some_${type}`] = value2; + expect(warn64(isBigInt(value2), () => obj[`some_${type}`])).toEqual( + Number(value2)); + }); + } + + testPropertyGetSet('boolean', true, false); + testPropertyGetSet('char', 42, 64); + testPropertyGetSet('uchar', 42, 64); + testPropertyGetSet('int', 42, 64); + testPropertyGetSet('uint', 42, 64); + testPropertyGetSet('long', 42, 64); + testPropertyGetSet('ulong', 42, 64); + testPropertyGetSet('int64', 42, 64); + testPropertyGetSet('int64', Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + testPropertyGetSetBigInt('int64', BigIntLimits.int64.min, BigIntLimits.int64.max, + 'https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/524'); + testPropertyGetSet('uint64', 42, 64); + testPropertyGetSetBigInt('uint64', BigIntLimits.int64.max, BigIntLimits.int64.umax, + 'https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/524'); + testPropertyGetSet('string', 'Gjs', 'is cool!'); + + it('get and sets out-of-range values throws', function () { + expect(() => { + obj.some_int64 = Limits.int64.max; + }).toThrowError(/out of range/); + expect(() => { + obj.some_int64 = BigIntLimits.int64.max + 1n; + }).toThrowError(/out of range/); + expect(() => { + obj.some_int64 = BigIntLimits.int64.min - 1n; + }).toThrowError(/out of range/); + expect(() => { + obj.some_int64 = BigIntLimits.int64.umax; + }).toThrowError(/out of range/); + expect(() => { + obj.some_int64 = -BigIntLimits.int64.umax; + }).toThrowError(/out of range/); + expect(() => { + obj.some_uint64 = Limits.int64.min; + }).toThrowError(/out of range/); + expect(() => { + obj.some_uint64 = BigIntLimits.int64.umax + 100n; + }).toThrowError(/out of range/); + }); + + it('gets and sets a float property', function () { + obj.some_float = Math.E; + expect(obj.some_float).toBeCloseTo(Math.E); + obj.some_float = Math.PI; + expect(obj.some_float).toBeCloseTo(Math.PI); + }); + + it('gets and sets a double property', function () { + obj.some_double = Math.E; + expect(obj.some_double).toBeCloseTo(Math.E); + obj.some_double = Math.PI; + expect(obj.some_double).toBeCloseTo(Math.PI); + }); + + testPropertyGetSet('strv', ['0', '1', '2'], []); + testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(), + new GIMarshallingTests.BoxedStruct({long_: 42})); + // testPropertyGetSet('boxed_glist', [1, 2, 3], []); + testPropertyGetSet('gvalue', 42, 'foo'); + testPropertyGetSetBigInt('gvalue', BigIntLimits.int64.umax, BigIntLimits.int64.min); + testPropertyGetSet('variant', new GLib.Variant('b', true), + new GLib.Variant('s', 'hello')); + testPropertyGetSet('variant', new GLib.Variant('x', BigIntLimits.int64.min), + new GLib.Variant('x', BigIntLimits.int64.max)); + testPropertyGetSet('variant', new GLib.Variant('t', BigIntLimits.int64.max), + new GLib.Variant('t', BigIntLimits.int64.umax)); + testPropertyGetSet('object', new GObject.Object(), + new GIMarshallingTests.Object({int: 42})); + testPropertyGetSet('flags', GIMarshallingTests.Flags.VALUE2, + GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2); + testPropertyGetSet('enum', GIMarshallingTests.GEnum.VALUE2, + GIMarshallingTests.GEnum.VALUE3); + testPropertyGetSet('byte_array', Uint8Array.of(1, 2, 3), + new TextEncoder().encode('👾')); + + it('gets a read-only property', function () { + expect(obj.some_readonly).toEqual(42); + }); + + it('throws when setting a read-only property', function () { + expect(() => (obj.some_readonly = 35)).toThrow(); + }); + + it('allows to set/get deprecated properties', function () { + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, + '*GObject property*.some-deprecated-int is deprecated*'); + obj.some_deprecated_int = 35; + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, + 'testAllowToSetGetDeprecatedProperties'); + + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, + '*GObject property*.some-deprecated-int is deprecated*'); + expect(obj.some_deprecated_int).toBe(35); + GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, + 'testAllowToSetGetDeprecatedProperties'); + }); }); describe('GObject signals', function () { @@ -2388,7 +2819,7 @@ describe('GObject signals', function () { }); } - ['none', 'container', 'none'].forEach(transfer => { + ['none', 'container', 'full'].forEach(transfer => { testSignalEmission('boxed-gptrarray-utf8', transfer, ['0', '1', '2']); testSignalEmission('boxed-gptrarray-boxed-struct', transfer, [ new GIMarshallingTests.BoxedStruct({long_: 42}), @@ -2427,4 +2858,125 @@ describe('GObject signals', function () { expect(callbackFunc).toHaveBeenCalledOnceWith(obj, new GIMarshallingTests.BoxedStruct({long_: 44})); }); + + xit('not-ref-counted boxed types with transfer full originating from C are properly handled', function () { + const callbackFunc = jasmine.createSpy('callbackFunc'); + const signalId = obj.connect('some-boxed-struct-full', callbackFunc); + obj.emit_boxed_struct_full(); + obj.disconnect(signalId); + expect(callbackFunc).toHaveBeenCalledOnceWith(obj, + new GIMarshallingTests.BoxedStruct({long_: 99, string_: 'a string', g_strv: ['foo', 'bar', 'baz']})); + }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/470'); + + it('can be created from C constructor as well', function () { + obj = GIMarshallingTests.SignalsObject.new(); + expect(obj).toBeInstanceOf(GIMarshallingTests.SignalsObject); + }); +}); + +// Adapted from pygobject +describe('GError extra tests', function () { + it('marshals GError instances through GValue', function () { + const error = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); + const error1 = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); + GIMarshallingTests.compare_two_gerrors_in_gvalue(error, error1); + }); + + it('can be nullable', function () { + const error = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); + expect(GIMarshallingTests.nullable_gerror(error)).toBeTruthy(); + expect(GIMarshallingTests.nullable_gerror(null)).toBeFalsy(); + }); +}); + +// Adapted from pygobject +describe('GHashTable extra tests', function () { + it('marshals a hash table of enums as an in argument', function () { + GIMarshallingTests.ghashtable_enum_none_in({ + 1: GIMarshallingTests.ExtraEnum.VALUE1, + 2: GIMarshallingTests.ExtraEnum.VALUE2, + 3: GIMarshallingTests.ExtraEnum.VALUE3, + }); + }); + + it('marshals a hash table of enums as a return value', function () { + expect(GIMarshallingTests.ghashtable_enum_none_return()).toEqual({ + 1: GIMarshallingTests.ExtraEnum.VALUE1, + 2: GIMarshallingTests.ExtraEnum.VALUE2, + 3: GIMarshallingTests.ExtraEnum.VALUE3, + }); + }); +}); + +// Adapted from pygobject +describe('Filename tests', function () { + let workdir; + beforeAll(function (done) { + Gio.File.new_tmp_dir_async(null, GLib.PRIORITY_DEFAULT, null, (self, result) => { + workdir = Gio.File.new_tmp_dir_finish(result); + done(); + }); + }); + + afterAll(function () { + GLib.rmdir(workdir.get_path()); + }); + + it('wrong types', function () { + expect(() => GIMarshallingTests.filename_copy(23)).toThrowError(); + expect(() => GIMarshallingTests.filename_copy([])).toThrowError(); + }); + + it('nullability', function () { + expect(GIMarshallingTests.filename_copy(null)).toBeNull(); + expect(() => GIMarshallingTests.filename_exists(null)).toThrowError(); + }); + + it('round-tripping', function () { + expect(GIMarshallingTests.filename_copy('foo')).toBe('foo'); + }); + + // We run the tests with Latin1 filename encoding, to catch mistakes + it('various types of paths in GLib encoding', function () { + const strPath = GIMarshallingTests.filename_copy('ä'); + expect(strPath).toBe('ä'); + expect(GIMarshallingTests.filename_to_glib_repr(strPath)) + .toEqual(Uint8Array.of(0xe4)); + }); + + it('various types of path existing', function () { + const paths = ['foo-2', 'öäü-3']; + for (const path of paths) { + const file = workdir.get_child(path); + const stream = file.create(Gio.FileCreateFlags.NONE, null); + expect(GIMarshallingTests.filename_exists(file.get_path())).toBeTrue(); + stream.close(null); + file.delete(null); + } + }); +}); + +// Adapted from pygobject +describe('Array of enum extra tests', function () { + it('marshals a C array of enum values as a return value', function () { + expect(GIMarshallingTests.enum_array_return_type()).toEqual([0, 1, 42]); + }); +}); + +// Adapted from pygobject +describe('Flags extra tests', function () { + it('marshals a 32-high bit flags value as an in argument', function () { + GIMarshallingTests.extra_flags_large_in(GIMarshallingTests.ExtraFlags.VALUE2); + }); +}); + +// Adapted from pygobject +describe('UTF-8 strings invalid bytes tests', function () { + it('handles invalid UTF-8 return values gracefully', function () { + expect(() => GIMarshallingTests.extra_utf8_full_return_invalid()).toThrowError(TypeError); + }); + + it('handles invalid UTF-8 out arguments gracefully', function () { + expect(() => GIMarshallingTests.extra_utf8_full_out_invalid()).toThrowError(TypeError); + }); }); diff --git a/installed-tests/js/testGLib.js b/installed-tests/js/testGLib.js index f9a9aa551..5b9406896 100644 --- a/installed-tests/js/testGLib.js +++ b/installed-tests/js/testGLib.js @@ -149,14 +149,14 @@ describe('GLib string function overrides', function () { function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { - GLib.test_assert_expected_messages_internal('Cjs', 'testGLib.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGLib.js', 0, `test GLib.${testName}`); } numExpectedWarnings = 0; @@ -301,7 +301,7 @@ describe('GLib.MatchInfo', function () { it('is returned from GLib.Regex.match', function () { const [, match] = regex.match('foo', 0); expect(match).toBeInstanceOf(GLib.MatchInfo); - expect(match.toString()).toContain('CjsPrivate.MatchInfo'); + expect(match.toString()).toContain('GjsPrivate.MatchInfo'); }); it('stores the string that was matched', function () { @@ -317,19 +317,19 @@ describe('GLib.MatchInfo', function () { it('is returned from GLib.Regex.match_all', function () { const [, match] = regex.match_all('foo', 0); expect(match).toBeInstanceOf(GLib.MatchInfo); - expect(match.toString()).toContain('CjsPrivate.MatchInfo'); + expect(match.toString()).toContain('GjsPrivate.MatchInfo'); }); it('is returned from GLib.Regex.match_all_full', function () { const [, match] = regex.match_all_full('foo', 0, 0); expect(match).toBeInstanceOf(GLib.MatchInfo); - expect(match.toString()).toContain('CjsPrivate.MatchInfo'); + expect(match.toString()).toContain('GjsPrivate.MatchInfo'); }); it('is returned from GLib.Regex.match_full', function () { const [, match] = regex.match_full('foo', 0, 0); expect(match).toBeInstanceOf(GLib.MatchInfo); - expect(match.toString()).toContain('CjsPrivate.MatchInfo'); + expect(match.toString()).toContain('GjsPrivate.MatchInfo'); }); describe('method', function () { @@ -394,3 +394,59 @@ describe('GLib.MatchInfo', function () { }); }); }); + +describe('GLibUnix functionality', function () { + let GLibUnix; + try { + GLibUnix = imports.gi.GLibUnix; + } catch {} + + beforeEach(function () { + if (!GLibUnix) + pending('Not supported platform'); + }); + + it('provides structs', function () { + new GLibUnix.Pipe(); + }); + + it('provides functions', function () { + GLibUnix.fd_source_new(0, GLib.IOCondition.IN); + }); + + it('provides enums', function () { + expect(GLibUnix.PipeEnd.READ).toBe(0); + expect(GLibUnix.PipeEnd.WRITE).toBe(1); + }); +}); + +describe('GLibUnix compatibility fallback', function () { + // Only test this if GLibUnix is available + const skip = imports.gi.versions.GLibUnix !== '2.0'; + + beforeEach(function () { + if (skip) + pending('Not supported platform'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, + '*GLib.* has been moved to a separate platform-specific library. ' + + 'Please update your code to use GLibUnix.* instead*'); + }); + + it('provides structs', function () { + new GLib.UnixPipe(); + }); + + it('provides functions', function () { + GLib.unix_fd_source_new(0, GLib.IOCondition.IN); + }); + + it('provides enums', function () { + expect(GLib.UnixPipeEnd.READ).toBe(0); + expect(GLib.UnixPipeEnd.WRITE).toBe(1); + }); + + afterEach(function () { + GLib.test_assert_expected_messages_internal('Gjs', 'testGLib.js', 0, + 'GLib.Unix expect warning'); + }); +}); diff --git a/installed-tests/js/testGLibLogWriter.js b/installed-tests/js/testGLibLogWriter.js index d69935d8e..811280b04 100644 --- a/installed-tests/js/testGLibLogWriter.js +++ b/installed-tests/js/testGLibLogWriter.js @@ -31,6 +31,10 @@ describe('GLib Structured logging handler', function () { GLib.log_set_writer_func(writer_func); }); + afterAll(function () { + GLib.log_set_writer_default(); + }); + beforeEach(function () { writer_func.calls.reset(); }); diff --git a/installed-tests/js/testGObject.js b/installed-tests/js/testGObject.js index fd96f9a1f..1d86ae07b 100644 --- a/installed-tests/js/testGObject.js +++ b/installed-tests/js/testGObject.js @@ -6,7 +6,7 @@ // except for the class machinery, interface machinery, and GObject.ParamSpec, // which are big enough to get their own files. -const {GLib, GObject} = imports.gi; +const {GLib, GObject, GjsTestTools} = imports.gi; const {system: System} = imports; const TestObj = GObject.registerClass({ @@ -51,6 +51,16 @@ describe('GObject overrides', function () { GObject.signal_emit_by_name(o, 'test'); expect(handler).not.toHaveBeenCalled(); }); + + it('guards against signal emission on non-js thread', function () { + handler.calls.reset(); + GObject.signal_connect(o, 'test', handler); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Attempting to call back into JSAPI on a different thread.*The offending signal was test on Gjs_TestObj*'); + GjsTestTools.emit_test_signal_other_thread(o); + expect(handler).not.toHaveBeenCalled(); + GLib.test_assert_expected_messages_internal('Gjs', 'testGObject.js', 0, + 'testSignalEmissionOtherThread'); + }); }); it('toString() shows the native object address', function () { diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js index 55aff9638..1a92eb08f 100644 --- a/installed-tests/js/testGObjectClass.js +++ b/installed-tests/js/testGObjectClass.js @@ -11,6 +11,18 @@ const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; +// Sometimes tests pass if we are comparing two inaccurate values in JS with +// each other. That's fine for now. Then we just have to suppress the warnings. +function warn64(func, ...args) { + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, + '*cannot be safely stored*'); + const ret = func(...args); + const error = new Error(); + GLib.test_assert_expected_messages_internal('Gjs', + error.fileName, error.lineNumber, 'warn64'); + return ret; +} + const MyObject = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', @@ -312,12 +324,12 @@ describe('GObject class with decorator', function () { }); it('warns if more than one argument passed to the default constructor', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_MESSAGE, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_MESSAGE, '*Too many arguments*'); new ObjectWithDefaultConstructor({}, 'this is ignored', 123); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'testGObjectClassTooManyArguments'); }); @@ -533,11 +545,11 @@ describe('GObject class with decorator', function () { }, class PropInt64 extends GObject.Object {}); let int64 = GLib.MAXINT64_BIGINT - 5n; - let obj = new PropInt64({int64}); + let obj = warn64(() => new PropInt64({int64})); expect(obj.int64).toEqual(Number(int64)); int64 = GLib.MININT64_BIGINT + 555n; - obj = new PropInt64({int64}); + obj = warn64(() => new PropInt64({int64})); expect(obj.int64).toEqual(Number(int64)); }); @@ -552,7 +564,7 @@ describe('GObject class with decorator', function () { }, }, class PropDefaultInt64Init extends GObject.Object {}); - const obj = new PropInt64Init(); + const obj = warn64(() => new PropInt64Init()); expect(obj.int64).toEqual(Number(defaultValue)); }); @@ -566,7 +578,7 @@ describe('GObject class with decorator', function () { }, class PropUint64 extends GObject.Object {}); const uint64 = GLib.MAXUINT64_BIGINT - 5n; - const obj = new PropUint64({uint64}); + const obj = warn64(() => new PropUint64({uint64})); expect(obj.uint64).toEqual(Number(uint64)); }); @@ -580,7 +592,7 @@ describe('GObject class with decorator', function () { }, }, class PropDefaultUint64Init extends GObject.Object {}); - const obj = new PropUint64Init(); + const obj = warn64(() => new PropUint64Init()); expect(obj.uint64).toEqual(Number(defaultValue)); }); @@ -625,7 +637,7 @@ describe('GObject class with decorator', function () { expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); @@ -665,7 +677,10 @@ describe('GObject class with decorator', function () { } }); - expect(() => new MyCustomCharset() && new MySecondCustomCharset()).not.toThrow(); + expect(() => { + new MyCustomCharset(); + new MySecondCustomCharset(); + }).not.toThrow(); }); it('resolves properties from interfaces', function () { @@ -952,6 +967,12 @@ describe('GObject virtual function', function () { }, class Foo extends GObject.Object { vfunc_init_async() {} })).toThrow(); + + expect(() => GObject.registerClass({ + Implements: [Gio.AsyncInitable], + }, class FooStatic extends GObject.Object { + static vfunc_not_existing() {} + })).toThrow(); }); it('are defined also for static virtual functions', function () { @@ -963,6 +984,34 @@ describe('GObject virtual function', function () { expect(Gio.Icon.new_for_string).toBeInstanceOf(Function); expect(CustomEmptyGIcon.new_for_string).toBe(Gio.Icon.new_for_string); }); + + it('supports static methods', function () { + if (!Gio.Icon.vfunc_from_tokens) + pending('https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4457'); + expect(() => GObject.registerClass({ + Implements: [Gio.Icon], + }, class extends GObject.Object { + static vfunc_from_tokens() {} + })).not.toThrow(); + }); + + it('must be non-static for methods', function () { + expect(() => GObject.registerClass({ + Implements: [Gio.Icon], + }, class extends GObject.Object { + static vfunc_serialize() {} + })).toThrowError(/.* static definition of non-static.*/); + }); + + it('must be static for methods', function () { + if (!Gio.Icon.vfunc_from_tokens) + pending('https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4457'); + expect(() => GObject.registerClass({ + Implements: [Gio.Icon], + }, class extends GObject.Object { + vfunc_from_tokens() {} + })).toThrowError(/.*non-static definition of static.*/); + }); }); describe('GObject creation using base classes without registered GType', function () { @@ -1038,7 +1087,7 @@ describe('Register GType name', function () { let gtypeName = 'GType$Test/WithLòt\'s of*bad§chars!'; let expectedSanitized = 'GType_Test_WithL_t_s_of_bad_chars_'; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, `*RangeError: Provided GType name '${gtypeName}' is not valid; ` + `automatically sanitized to '${expectedSanitized}'*`); @@ -1046,7 +1095,7 @@ describe('Register GType name', function () { GTypeName: gtypeName, }, class extends GObject.Object {}); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'testGObjectRegisterClassSanitize'); expect(GtypeClass.$gtype.name).toEqual(expectedSanitized); @@ -1729,38 +1778,38 @@ describe('GObject class with JSObject signals', function () { // be caught from signal handlers, so we check for logged messages instead it('throws an error when returning a boolean value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => true); myInstance.emit('get-object', {}); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning an int value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 1); myInstance.emit('get-object', {}); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning a numeric value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => Math.PI); myInstance.emit('get-object', {}); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning a string value', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 'string'); myInstance.emit('get-object', {}); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); }); diff --git a/installed-tests/js/testGObjectDestructionAccess.js b/installed-tests/js/testGObjectDestructionAccess.js index 586df4d96..766c60e1b 100644 --- a/installed-tests/js/testGObjectDestructionAccess.js +++ b/installed-tests/js/testGObjectDestructionAccess.js @@ -4,7 +4,7 @@ imports.gi.versions.Gtk = '3.0'; -const {GLib, Gio, CjsTestTools, GObject, Gtk} = imports.gi; +const {GLib, Gio, GjsTestTools, GObject, Gtk} = imports.gi; const {system: System} = imports; describe('Access to destroyed GObject', function () { @@ -21,35 +21,35 @@ describe('Access to destroyed GObject', function () { }); it('Get property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.title).toBe('To be destroyed'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.title = 'I am dead'; expect(destroyedWindow.title).toBe('I am dead'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.expandoProperty = 'Hello!'; - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectExpandoPropertySet'); }); @@ -66,85 +66,85 @@ describe('Access to destroyed GObject', function () { }); it('Access to getter method', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.get_title()).toBe('To be destroyed'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.set_title('I am dead'); expect(destroyedWindow.get_title()).toBe('I am dead'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect('foo-signal', () => {})).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect_after('foo-signal', () => {})).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_disconnect_by_func(destroyedWindow, () => {})).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_block_by_func(destroyedWindow, () => {})).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_unblock_by_func(destroyedWindow, () => {})).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsUnblock'); }); @@ -178,52 +178,52 @@ describe('Access to finalized GObject', function () { destroyedWindow.set_title('To be destroyed'); destroyedWindow.destroy(); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); - CjsTestTools.unref(destroyedWindow); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GjsTestTools.unref(destroyedWindow); + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); afterEach(function () { destroyedWindow = null; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection'); }); it('Get property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.title).toBeUndefined(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.title = 'I am dead'; expect(destroyedWindow.title).toBeUndefined(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.expandoProperty = 'Hello!'; - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectExpandoPropertySet'); }); @@ -240,86 +240,86 @@ describe('Access to finalized GObject', function () { }); it('Access to getter method', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); expect(destroyedWindow.get_title()).toBeNull(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); destroyedWindow.set_title('I am dead'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect('foo-signal', () => { })).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect_after('foo-signal', () => { })).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_disconnect_by_func(destroyedWindow, () => { })).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_block_by_func(destroyedWindow, () => { })).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_unblock_by_func(destroyedWindow, () => { })).toBe(0); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsUnblock'); }); @@ -331,11 +331,11 @@ describe('Access to finalized GObject', function () { describe('Disposed or finalized GObject', function () { beforeAll(function () { - CjsTestTools.init(); + GjsTestTools.init(); }); afterEach(function () { - CjsTestTools.reset(); + GjsTestTools.reset(); }); [true, false].forEach(gc => { @@ -367,7 +367,7 @@ describe('Disposed or finalized GObject', function () { const callSpy = jasmine.createSpy('vfunc_dispose'); const DisposeFile = GObject.registerClass(class DisposeFile extends Gio.ThemedIcon { vfunc_dispose(...args) { - expect(this.names).toEqual(['dummy']); + expect(this.names).toEqual(['dummy', 'dummy-symbolic']); callSpy(...args); } }); @@ -380,36 +380,36 @@ describe('Disposed or finalized GObject', function () { expect(callSpy).toHaveBeenCalledTimes(2); file = null; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was dispose()*'); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'calls dispose vfunc on explicit disposal only'); expect(callSpy).toHaveBeenCalledTimes(2); }); it('generates a warn on object garbage collection', function () { - CjsTestTools.unref(Gio.File.new_for_path('/')); + GjsTestTools.unref(Gio.File.new_for_path('/')); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection'); }); it('generates a warn on object garbage collection if has expando property', function () { let file = Gio.File.new_for_path('/'); file.toggleReferenced = true; - CjsTestTools.unref(file); + GjsTestTools.unref(file); expect(file.toString()).toMatch( /\[object \(FINALIZED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); file = null; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection if has expando property'); }); @@ -417,7 +417,7 @@ describe('Disposed or finalized GObject', function () { const loop = new GLib.MainLoop(null, false); let file = Gio.File.new_for_path('/'); - CjsTestTools.delayed_unref(file, 1); // Will happen after dispose + GjsTestTools.delayed_unref(file, 1); // Will happen after dispose file.run_dispose(); let done = false; @@ -426,17 +426,17 @@ describe('Disposed or finalized GObject', function () { loop.get_context().iteration(true); file = null; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn if already disposed at garbage collection'); }); [true, false].forEach(gc => { it(`created from other function is marked as disposed and ${gc ? '' : 'not '}garbage collected`, function () { let file = Gio.File.new_for_path('/'); - CjsTestTools.save_object(file); + GjsTestTools.save_object(file); file.run_dispose(); file = null; System.gc(); @@ -444,7 +444,7 @@ describe('Disposed or finalized GObject', function () { Array(10).fill().forEach(() => { // We need to repeat the test to ensure that we disassociate // wrappers from disposed objects on destruction. - expect(CjsTestTools.peek_saved()).toMatch( + expect(GjsTestTools.peek_saved()).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); if (gc) System.gc(); @@ -453,17 +453,17 @@ describe('Disposed or finalized GObject', function () { }); it('returned from function is marked as disposed', function () { - expect(CjsTestTools.get_disposed(Gio.File.new_for_path('/'))).toMatch( + expect(GjsTestTools.get_disposed(Gio.File.new_for_path('/'))).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('returned from function is marked as disposed and then as finalized', function () { let file = Gio.File.new_for_path('/'); - CjsTestTools.save_object(file); - CjsTestTools.delayed_unref(file, 30); + GjsTestTools.save_object(file); + GjsTestTools.delayed_unref(file, 30); file.run_dispose(); - let disposedFile = CjsTestTools.get_saved(); + let disposedFile = GjsTestTools.get_saved(); expect(disposedFile).toEqual(file); expect(disposedFile).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); @@ -478,42 +478,42 @@ describe('Disposed or finalized GObject', function () { expect(disposedFile).toMatch( /\[object \(FINALIZED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); disposedFile = null; System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'returned from function is marked as disposed and then as finalized'); }); it('ignores toggling queued unref toggles', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.ref(file); - CjsTestTools.unref_other_thread(file); + GjsTestTools.ref(file); + GjsTestTools.unref_other_thread(file); file.run_dispose(); }); it('ignores toggling queued toggles', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.ref_other_thread(file); - CjsTestTools.unref_other_thread(file); + GjsTestTools.ref_other_thread(file); + GjsTestTools.unref_other_thread(file); file.run_dispose(); }); it('can be disposed from other thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.ref(file); - CjsTestTools.unref_other_thread(file); - CjsTestTools.run_dispose_other_thread(file); + GjsTestTools.ref(file); + GjsTestTools.unref_other_thread(file); + GjsTestTools.run_dispose_other_thread(file); }); it('can be garbage collected once disposed from other thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.run_dispose_other_thread(file); + GjsTestTools.run_dispose_other_thread(file); file = null; System.gc(); }); @@ -521,19 +521,19 @@ describe('Disposed or finalized GObject', function () { describe('GObject with toggle references', function () { beforeAll(function () { - CjsTestTools.init(); + GjsTestTools.init(); }); afterEach(function () { - CjsTestTools.reset(); + GjsTestTools.reset(); }); it('can be re-reffed from other thread delayed', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; const objectAddress = System.addressOfGObject(file); - CjsTestTools.save_object_unreffed(file); - CjsTestTools.delayed_ref_other_thread(file, 10); + GjsTestTools.save_object_unreffed(file); + GjsTestTools.delayed_ref_other_thread(file, 10); file = null; System.gc(); @@ -545,11 +545,11 @@ describe('GObject with toggle references', function () { // However, depending on whether the thread ref happens the object // may be already finalized, and in such case we need to throw try { - file = CjsTestTools.steal_saved(); + file = GjsTestTools.steal_saved(); if (file) { expect(System.addressOfGObject(file)).toBe(objectAddress); expect(file instanceof Gio.File).toBeTruthy(); - CjsTestTools.unref(file); + GjsTestTools.unref(file); } } catch (e) { expect(() => { @@ -562,9 +562,9 @@ describe('GObject with toggle references', function () { let file = Gio.File.new_for_path('/'); const objectAddress = System.addressOfGObject(file); file.expandMeWithToggleRef = true; - CjsTestTools.save_object(file); - CjsTestTools.ref(file); - CjsTestTools.delayed_unref_other_thread(file, 10); + GjsTestTools.save_object(file); + GjsTestTools.ref(file); + GjsTestTools.delayed_unref_other_thread(file, 10); file = null; System.gc(); @@ -572,7 +572,7 @@ describe('GObject with toggle references', function () { GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => loop.quit()); loop.run(); - file = CjsTestTools.get_saved(); + file = GjsTestTools.get_saved(); expect(System.addressOfGObject(file)).toBe(objectAddress); expect(file instanceof Gio.File).toBeTruthy(); }); @@ -580,7 +580,7 @@ describe('GObject with toggle references', function () { it('can be re-reffed and unreffed again from other thread with delay', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.delayed_ref_unref_other_thread(file, 10); + GjsTestTools.delayed_ref_unref_other_thread(file, 10); file = null; System.gc(); @@ -592,50 +592,50 @@ describe('GObject with toggle references', function () { it('can be toggled up by getting a GWeakRef', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.save_weak(file); - CjsTestTools.get_weak(); + GjsTestTools.save_weak(file); + GjsTestTools.get_weak(); }); it('can be toggled up by getting a GWeakRef from another thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.save_weak(file); - CjsTestTools.get_weak_other_thread(); + GjsTestTools.save_weak(file); + GjsTestTools.get_weak_other_thread(); }); it('can be toggled up by getting a GWeakRef from another thread and re-reffed in main thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.save_weak(file); - CjsTestTools.get_weak_other_thread(); + GjsTestTools.save_weak(file); + GjsTestTools.get_weak_other_thread(); // Ok, let's play more dirty now... - CjsTestTools.ref(file); // toggle up - CjsTestTools.unref(file); // toggle down + GjsTestTools.ref(file); // toggle up + GjsTestTools.unref(file); // toggle down - CjsTestTools.ref(file); - CjsTestTools.ref(file); - CjsTestTools.unref(file); - CjsTestTools.unref(file); + GjsTestTools.ref(file); + GjsTestTools.ref(file); + GjsTestTools.unref(file); + GjsTestTools.unref(file); }); it('can be toggled up by getting a GWeakRef from another and re-reffed from various threads', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.save_weak(file); - CjsTestTools.get_weak_other_thread(); + GjsTestTools.save_weak(file); + GjsTestTools.get_weak_other_thread(); - CjsTestTools.ref_other_thread(file); - CjsTestTools.unref_other_thread(file); + GjsTestTools.ref_other_thread(file); + GjsTestTools.unref_other_thread(file); - CjsTestTools.ref(file); - CjsTestTools.unref(file); + GjsTestTools.ref(file); + GjsTestTools.unref(file); - CjsTestTools.ref_other_thread(file); - CjsTestTools.unref(file); + GjsTestTools.ref_other_thread(file); + GjsTestTools.unref(file); - CjsTestTools.ref(file); - CjsTestTools.unref_other_thread(file); + GjsTestTools.ref(file); + GjsTestTools.unref_other_thread(file); }); it('can be toggled up-down from various threads when the wrapper is gone', function () { @@ -644,20 +644,20 @@ describe('GObject with toggle references', function () { // We also check that late thread events won't affect the destroyed wrapper const threads = []; - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 0)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 100000)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 200000)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 300000)); - CjsTestTools.save_object(file); - CjsTestTools.save_weak(file); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 0)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 100000)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 200000)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 300000)); + GjsTestTools.save_object(file); + GjsTestTools.save_weak(file); file = null; System.gc(); threads.forEach(th => th.join()); - CjsTestTools.clear_saved(); + GjsTestTools.clear_saved(); System.gc(); - expect(CjsTestTools.get_weak()).toBeNull(); + expect(GjsTestTools.get_weak()).toBeNull(); }); it('can be toggled up-down from various threads when disposed and the wrapper is gone', function () { @@ -666,47 +666,47 @@ describe('GObject with toggle references', function () { // We also check that late thread events won't affect the destroyed wrapper const threads = []; - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 0)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 100000)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 200000)); - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 300000)); - CjsTestTools.save_object(file); - CjsTestTools.save_weak(file); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 0)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 100000)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 200000)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 300000)); + GjsTestTools.save_object(file); + GjsTestTools.save_weak(file); file.run_dispose(); file = null; System.gc(); threads.forEach(th => th.join()); - CjsTestTools.clear_saved(); - expect(CjsTestTools.get_weak()).toBeNull(); + GjsTestTools.clear_saved(); + expect(GjsTestTools.get_weak()).toBeNull(); }); it('can be finalized while queued in toggle queue', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; - CjsTestTools.ref(file); - CjsTestTools.unref_other_thread(file); - CjsTestTools.unref_other_thread(file); + GjsTestTools.ref(file); + GjsTestTools.unref_other_thread(file); + GjsTestTools.unref_other_thread(file); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); file = null; System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'can be finalized while queued in toggle queue'); }); 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; - CjsTestTools.save_weak(file); + GjsTestTools.save_weak(file); const ids = []; let threads = []; ids.push(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { threads = threads.slice(-50); try { - threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 1)); + threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 1)); } catch (e) { // If creating the thread failed we're almost going out of memory // so let's first wait for the ones allocated to complete. @@ -718,7 +718,7 @@ describe('GObject with toggle references', function () { const loop = new GLib.MainLoop(null, false); ids.push(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - expect(CjsTestTools.get_weak()).toEqual(file); + expect(GjsTestTools.get_weak()).toEqual(file); return GLib.SOURCE_CONTINUE; })); @@ -728,14 +728,14 @@ describe('GObject with toggle references', function () { ids.forEach(id => GLib.source_remove(id)); // We also check that late thread events won't affect the destroyed wrapper - CjsTestTools.save_object(file); + GjsTestTools.save_object(file); file = null; System.gc(); threads.forEach(th => th.join()); - expect(CjsTestTools.get_saved_ref_count()).toBeGreaterThan(0); + expect(GjsTestTools.get_saved_ref_count()).toBeGreaterThan(0); - CjsTestTools.clear_saved(); + GjsTestTools.clear_saved(); System.gc(); - expect(CjsTestTools.get_weak()).toBeNull(); + expect(GjsTestTools.get_weak()).toBeNull(); }).pend('Flaky, see https://gitlab.gnome.org/GNOME/gjs/-/issues/NNN'); }); diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js index 0736b78aa..40f5eca82 100644 --- a/installed-tests/js/testGObjectInterface.js +++ b/installed-tests/js/testGObjectInterface.js @@ -272,7 +272,7 @@ describe('GObject interface', function () { requiredG() {} }); // g_test_assert_expected_messages() is a macro, not introspectable - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectInterface.js', + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js', 253, 'testGObjectMustOverrideInterfaceProperties'); }); diff --git a/installed-tests/js/testGObjectValue.js b/installed-tests/js/testGObjectValue.js index 8b33d8759..0a8a22ca2 100644 --- a/installed-tests/js/testGObjectValue.js +++ b/installed-tests/js/testGObjectValue.js @@ -7,12 +7,14 @@ const SIGNED_TYPES = ['schar', 'int', 'int64', 'long']; const UNSIGNED_TYPES = ['char', 'uchar', 'uint', 'uint64', 'ulong']; const FLOATING_TYPES = ['double', 'float']; const NUMERIC_TYPES = [...SIGNED_TYPES, ...UNSIGNED_TYPES, ...FLOATING_TYPES]; -const SPECIFIC_TYPES = ['gtype', 'boolean', 'string', 'param', 'variant', 'boxed', 'gvalue']; +const SPECIFIC_TYPES = ['gtype', 'boolean', 'string', 'param', 'variant', 'boxed', 'gvalue', 'enum']; const INSTANCED_TYPES = ['object', 'instance']; const ALL_TYPES = [...NUMERIC_TYPES, ...SPECIFIC_TYPES, ...INSTANCED_TYPES]; +// Test that constructors can be used in place of GType arguments and corresponds to specified type +const CONSTRUCTORS = [[String, 'string'], [Number, 'double'], [Boolean, 'boolean'], [Object, 'boxed'], [GIMarshallingTests.PropertiesObject, 'object'], [Regress.TestEnum, 'enum']]; describe('GObject value (GValue)', function () { - let v; + let v, overrideV; beforeEach(function () { v = new GObject.Value(); }); @@ -31,6 +33,10 @@ describe('GObject value (GValue)', function () { if (type === 'gtype') return getGType(ALL_TYPES[Math.random() * ALL_TYPES.length | 0]); + if (type === 'enum') + return Regress.TestEnum[`VALUE${(Math.random() * 5 | 0) + 1}`]; + + if (type === 'boxed' || type === 'boxed-struct') { return new GIMarshallingTests.BoxedStruct({ long_: getDefaultContentByType('long'), @@ -46,7 +52,8 @@ describe('GObject value (GValue)', function () { e !== 'gtype' && e !== 'instance' && e !== 'param' && - e !== 'schar').concat([ + e !== 'schar' && + e !== 'enum').concat([ 'boxed-struct', ]).reduce((ac, a) => ({ ...ac, [`some-${a}`]: getDefaultContentByType(a), @@ -117,8 +124,14 @@ describe('GObject value (GValue)', function () { pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/272'); } - ALL_TYPES.forEach(type => { - const gtype = getGType(type); + [...ALL_TYPES, ...CONSTRUCTORS].forEach(type => { + let gtype; + // for testing constructor/type tuples + if (Array.isArray(type)) + [gtype, type] = [type[0], type[1]]; + else + gtype = getGType(type); + it(`initializes ${type}`, function () { v.init(gtype); }); @@ -136,12 +149,14 @@ describe('GObject value (GValue)', function () { beforeEach(function () { v.init(gtype); randomContent = getDefaultContentByType(type); + overrideV = new GObject.Value(gtype, randomContent); }); it(`sets and gets ${type}`, function () { skipUnsupported(type); setContent(v, type, randomContent); expect(getContent(v, type)).toEqual(randomContent); + expect(getContent(overrideV, type)).toEqual(randomContent); }); it(`can be passed to a function and returns a ${type}`, function () { @@ -149,6 +164,8 @@ describe('GObject value (GValue)', function () { setContent(v, type, randomContent); expect(GIMarshallingTests.gvalue_round_trip(v)).toEqual(randomContent); expect(GIMarshallingTests.gvalue_copy(v)).toEqual(randomContent); + expect(GIMarshallingTests.gvalue_round_trip(overrideV)).toEqual(randomContent); + expect(GIMarshallingTests.gvalue_copy(overrideV)).toEqual(randomContent); }); it(`copies ${type}`, function () { @@ -159,6 +176,9 @@ describe('GObject value (GValue)', function () { other.init(gtype); v.copy(other); expect(getContent(other, type)).toEqual(randomContent); + + overrideV.copy(other); + expect(getContent(other, type)).toEqual(randomContent); }); }); @@ -183,10 +203,13 @@ describe('GObject value (GValue)', function () { const instance = getDefaultContentByType(type); v.init_from_instance(instance); expect(getContent(v, type)).toEqual(instance); + overrideV.init_from_instance(instance); + expect(getContent(overrideV, type)).toEqual(instance); }); }); afterEach(function () { v.unset(); + overrideV?.unset(); }); }); diff --git a/installed-tests/js/testGio.js b/installed-tests/js/testGio.js index b2775d1f6..1b40d0914 100644 --- a/installed-tests/js/testGio.js +++ b/installed-tests/js/testGio.js @@ -396,9 +396,6 @@ describe('Gio.FileEnumerator overrides', function () { }); describe('Gio.DesktopAppInfo fallback', function () { - const requiredVersion = - GLib.MAJOR_VERSION > 2 || - (GLib.MAJOR_VERSION === 2 && GLib.MINOR_VERSION >= 86); let keyFile; const desktopFileContent = `[Desktop Entry] Version=1.0 @@ -407,7 +404,6 @@ Name=Some Application Exec=${GLib.find_program_in_path('sh')} `; beforeAll(function () { - // Set up log writer for tests to override keyFile = new GLib.KeyFile(); keyFile.load_from_data(desktopFileContent, desktopFileContent.length, GLib.KeyFileFlags.NONE); @@ -416,20 +412,14 @@ Exec=${GLib.find_program_in_path('sh')} beforeEach(function () { if (!GioUnix) pending('Not supported platform'); - - if (!requiredVersion) - pending('Installed Gio is not new enough for this test'); }); function expectDeprecationWarning(testFunction) { - if (!requiredVersion) - pending('Installed Gio is not new enough for this test'); - - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.DesktopAppInfo has been moved to a separate platform-specific library. ' + 'Please update your code to use GioUnix.DesktopAppInfo instead*'); testFunction(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGio.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGio.js', 0, 'Gio.DesktopAppInfo expectWarnsOnNewerGio'); } @@ -446,9 +436,6 @@ Exec=${GLib.find_program_in_path('sh')} describe('provides platform-independent functions', function () { [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () { - if (!requiredVersion) - pending('Installed Gio is not new enough for this test'); - const maybeExpectDeprecationWarning = ns === Gio ? expectDeprecationWarning : tf => tf(); @@ -461,9 +448,6 @@ Exec=${GLib.find_program_in_path('sh')} describe('provides unix-only functions', function () { [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () { - if (!requiredVersion) - pending('Installed Gio is not new enough for this test'); - const maybeExpectDeprecationWarning = ns === Gio ? expectDeprecationWarning : tf => tf(); @@ -483,14 +467,14 @@ describe('Non-introspectable file attribute overrides', function () { function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { - GLib.test_assert_expected_messages_internal('Cjs', 'testGio.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGio.js', 0, `test Gio.${testName}`); } numExpectedWarnings = 0; @@ -541,7 +525,7 @@ describe('Non-introspectable file attribute overrides', function () { it('works for object', function () { expectWarnings(2); - const icon = Gio.ThemedIcon.new_from_names(['xsi-list-add-symbolic']); + const icon = Gio.ThemedIcon.new_from_names(['list-add-symbolic']); expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileAttributeType.OBJECT, icon, ...flags)) .toThrowError(/not introspectable/); diff --git a/installed-tests/js/testGtk3.js b/installed-tests/js/testGtk3.js index 73462fa57..39f9d1cd4 100644 --- a/installed-tests/js/testGtk3.js +++ b/installed-tests/js/testGtk3.js @@ -175,7 +175,7 @@ describe('Gtk overrides', function () { }); it('avoid crashing when GTK vfuncs are called in garbage collection', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was destroy()*'); const BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label { @@ -185,7 +185,7 @@ describe('Gtk overrides', function () { new BadLabel(); System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0, 'Gtk overrides avoid crashing and print a stack trace'); }); @@ -202,11 +202,11 @@ describe('Gtk overrides', function () { label.destroy(); expect(spy).toHaveBeenCalledTimes(1); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was destroy()*'); label = null; System.gc(); - GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0, 'GTK vfuncs are not called if the object is disposed'); }); @@ -220,10 +220,10 @@ describe('Gtk overrides', function () { expect(handleDispose).toHaveBeenCalledWith(label); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Label (0x* disposed *'); expect(label.label).toBe('Hello'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0, 'GTK destroy signal is emitted while disposing objects'); }); diff --git a/installed-tests/js/testGtk4.js b/installed-tests/js/testGtk4.js index b21d63612..a51515110 100644 --- a/installed-tests/js/testGtk4.js +++ b/installed-tests/js/testGtk4.js @@ -4,14 +4,9 @@ imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; -const {Gdk, Gio, GObject, Gtk, GLib, CjsTestTools} = imports.gi; +const {Gdk, Gio, GObject, Gtk, GLib, GjsTestTools} = imports.gi; const System = imports.system; -// Set up log writer for tests to override -const writerFunc = jasmine.createSpy('log writer', () => GLib.LogWriterOutput.UNHANDLED); -writerFunc.and.callThrough(); -GLib.log_set_writer_func(writerFunc); - // This is ugly here, but usually it would be in a resource function createTemplate(className) { return ` @@ -188,182 +183,201 @@ function validateTemplate(description, ClassName, pending = false) { }); } -describe('Gtk overrides', function () { +class LeakTestWidget extends Gtk.Button { + buttonClicked() {} +} + +GObject.registerClass({ + Template: new TextEncoder().encode(` + + +`), +}, LeakTestWidget); + +describe('Gtk 4', function () { + let writerFunc; beforeAll(function () { Gtk.init(); + + // Set up log writer for tests to override + writerFunc = jasmine.createSpy('log writer', () => GLib.LogWriterOutput.UNHANDLED); + writerFunc.and.callThrough(); + GLib.log_set_writer_func(writerFunc); }); afterAll(function () { + GLib.log_set_writer_default(); templateFile.delete(null); }); - validateTemplate('UI template', MyComplexGtkSubclass); - validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); - validateTemplate('UI template from string', MyComplexGtkSubclassFromString); - validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); - validateTemplate('Class inheriting from template class', SubclassSubclass, true); - - it('ensures signal handlers are callable', function () { - const ClassWithUncallableHandler = GObject.registerClass({ - Template: createTemplate('Gjs_ClassWithUncallableHandler'), - Children: ['label-child', 'label-child2'], - InternalChildren: ['internal-label-child'], - }, class ClassWithUncallableHandler extends Gtk.Grid { - templateCallback() {} - get boundCallback() { - return 'who ya gonna call?'; - } - }); - - // The exception is thrown inside a vfunc with a GError out parameter, - // and Gtk logs a critical. - writerFunc.calls.reset(); - writerFunc.and.callFake((level, fields) => { - const decoder = new TextDecoder('utf-8'); - const domain = decoder.decode(fields?.GLIB_DOMAIN); - const message = decoder.decode(fields?.MESSAGE); - expect(level).toBe(GLib.LogLevelFlags.LEVEL_CRITICAL); - expect(domain).toBe('Gtk'); - expect(message).toMatch('is not a function'); - return GLib.LogWriterOutput.HANDLED; - }); - - void new ClassWithUncallableHandler(); - - expect(writerFunc).toHaveBeenCalled(); - writerFunc.and.callThrough(); - }); + describe('overrides', function () { + validateTemplate('UI template', MyComplexGtkSubclass); + validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); + validateTemplate('UI template from string', MyComplexGtkSubclassFromString); + validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); + validateTemplate('Class inheriting from template class', SubclassSubclass, true); + + it('ensures signal handlers are callable', function () { + const ClassWithUncallableHandler = GObject.registerClass({ + Template: createTemplate('Gjs_ClassWithUncallableHandler'), + Children: ['label-child', 'label-child2'], + InternalChildren: ['internal-label-child'], + }, class ClassWithUncallableHandler extends Gtk.Grid { + templateCallback() {} + get boundCallback() { + return 'who ya gonna call?'; + } + }); - it('rejects unsupported template URIs', function () { - expect(() => { - return GObject.registerClass({ - Template: 'https://gnome.org', - }, class GtkTemplateInvalid extends Gtk.Widget { + // The exception is thrown inside a vfunc with a GError out parameter, + // and Gtk logs a critical. + writerFunc.calls.reset(); + writerFunc.and.callFake((level, fields) => { + const decoder = new TextDecoder('utf-8'); + const domain = decoder.decode(fields?.GLIB_DOMAIN); + const message = decoder.decode(fields?.MESSAGE); + expect(level).toBe(GLib.LogLevelFlags.LEVEL_CRITICAL); + expect(domain).toBe('Gtk'); + expect(message).toMatch('is not a function'); + return GLib.LogWriterOutput.HANDLED; }); - }).toThrowError(TypeError, /Invalid template URI/); - }); - it('sets CSS names on classes', function () { - expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); - }); + void new ClassWithUncallableHandler(); - it('static inheritance works', function () { - expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass'); - }); + expect(writerFunc).toHaveBeenCalled(); + writerFunc.and.callThrough(); + }); - it('can create a Gtk.TreeIter with accessible stamp field', function () { - const iter = new Gtk.TreeIter(); - iter.stamp = 42; - expect(iter.stamp).toEqual(42); - }); + it('rejects unsupported template URIs', function () { + expect(() => { + return GObject.registerClass({ + Template: 'https://gnome.org', + }, class GtkTemplateInvalid extends Gtk.Widget { + }); + }).toThrowError(TypeError, /Invalid template URI/); + }); - it('can create a Gtk.CustomSorter with callback', function () { - const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); - const model = Gtk.StringList.new(['hello', 'world']); - const sorter = Gtk.CustomSorter.new(sortFunc); - void Gtk.SortListModel.new(model, sorter); - expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); - }); + it('sets CSS names on classes', function () { + expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); + }); - it('can change the callback of a Gtk.CustomSorter', function () { - const model = Gtk.StringList.new(['hello', 'world']); - const sorter = Gtk.CustomSorter.new(null); - void Gtk.SortListModel.new(model, sorter); + it('static inheritance works', function () { + expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass'); + }); - const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); - sorter.set_sort_func(sortFunc); - expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); + it('can create a Gtk.TreeIter with accessible stamp field', function () { + const iter = new Gtk.TreeIter(); + iter.stamp = 42; + expect(iter.stamp).toEqual(42); + }); - sortFunc.calls.reset(); - sorter.set_sort_func(null); - expect(sortFunc).not.toHaveBeenCalled(); - }); -}); + it('can create a Gtk.CustomSorter with callback', function () { + const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); + const model = Gtk.StringList.new(['hello', 'world']); + const sorter = Gtk.CustomSorter.new(sortFunc); + void Gtk.SortListModel.new(model, sorter); + expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); + }); -describe('Gtk 4 regressions', function () { - it('Gdk.Event fundamental type should not crash', function () { - expect(() => new Gdk.Event()).toThrowError(/Couldn't find a constructor/); - }); + it('can change the callback of a Gtk.CustomSorter', function () { + const model = Gtk.StringList.new(['hello', 'world']); + const sorter = Gtk.CustomSorter.new(null); + void Gtk.SortListModel.new(model, sorter); - xit('Actions added via Gtk.WidgetClass.add_action() should not crash', function () { - const custom = new CustomActionWidget(); - custom.activate_action('custom.action', null); - expect(custom.action).toEqual(42); - }).pend('https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3796'); - - it('Gdk.NoSelection section returns valid start/end values', function () { - if (!Gtk.NoSelection.prototype.get_section) - pending('Gtk 4.12 is required'); - - let result; - try { - result = new Gtk.NoSelection().get_section(0); - } catch (err) { - if (err.message.includes('not introspectable')) - pending('This version of GTK has the annotation bug'); - throw err; - } - expect(result).toEqual([0, GLib.MAXUINT32]); + const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); + sorter.set_sort_func(sortFunc); + expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); + + sortFunc.calls.reset(); + sorter.set_sort_func(null); + expect(sortFunc).not.toHaveBeenCalled(); + }); }); - function createSurface(shouldStash) { - // Create a Gdk.Surface that is unreachable after this function ends - const display = Gdk.Display.get_default(); - const surface = Gdk.Surface.new_toplevel(display); - if (shouldStash) - CjsTestTools.save_object(surface); - } + describe('regressions', function () { + it('Gdk.Event fundamental type should not crash', function () { + expect(() => new Gdk.Event()).toThrowError(/Couldn't find a constructor/); + }); - it('Gdk.Surface is destroyed properly', function () { - createSurface(false); - System.gc(); - }); + xit('Actions added via Gtk.WidgetClass.add_action() should not crash', function () { + const custom = new CustomActionWidget(); + custom.activate_action('custom.action', null); + expect(custom.action).toEqual(42); + }).pend('https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3796'); + + it('Gdk.NoSelection section returns valid start/end values', function () { + if (!Gtk.NoSelection.prototype.get_section) + pending('Gtk 4.12 is required'); + + let result; + try { + result = new Gtk.NoSelection().get_section(0); + } catch (err) { + if (err.message.includes('not introspectable')) + pending('This version of GTK has the annotation bug'); + throw err; + } + expect(result).toEqual([0, GLib.MAXUINT32]); + }); - it('Gdk.Surface is not destroyed if a ref is held from C', function () { - createSurface(true); - System.gc(); - const surface = CjsTestTools.steal_saved(); - expect(surface.is_destroyed()).toBeFalsy(); - }); -}); + function createSurface(shouldStash) { + // Create a Gdk.Surface that is unreachable after this function ends + const display = Gdk.Display.get_default(); + const surface = Gdk.Surface.new_toplevel(display); + if (shouldStash) + GjsTestTools.save_object(surface); + } -class LeakTestWidget extends Gtk.Button { - buttonClicked() {} -} + it('Gdk.Surface is destroyed properly', function () { + createSurface(false); + System.gc(); + }); -GObject.registerClass({ - Template: new TextEncoder().encode(` - - -`), -}, LeakTestWidget); + it('Gdk.Surface is not destroyed if a ref is held from C', function () { + createSurface(true); + System.gc(); + const surface = GjsTestTools.steal_saved(); + expect(surface.is_destroyed()).toBeFalsy(); + }); + it('private type implementing two interfaces is introspected correctly', function () { + const pages = new Gtk.Notebook().pages; // implements Gio.ListModel and Gtk.SelectionModel + expect(pages.get_n_items()).toBe(0); + expect(pages.get_selection().get_size()).toBe(0); + }); -describe('Gtk template signal', function () { - beforeAll(function () { - Gtk.init(); + it('callback with scope-notify transfer-full in parameter', function () { + // https://gitlab.gnome.org/GNOME/gjs/-/issues/691 + const model = new Gio.ListStore({itemType: Gtk.Label}); + model.append(new Gtk.Label({label: 'test'})); + const mapModel = new Gtk.MapListModel({model}); + mapModel.set_map_func(item => Gtk.StringObject.new(item.label)); + mapModel.get_item(0); + }); }); - function asyncIdle() { - return new Promise(resolve => { - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - resolve(); - return GLib.SOURCE_REMOVE; + describe('template signal', function () { + function asyncIdle() { + return new Promise(resolve => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + resolve(); + return GLib.SOURCE_REMOVE; + }); }); - }); - } + } - it('does not leak', async function () { - const weakRef = new WeakRef(new LeakTestWidget()); + it('does not leak', async function () { + const weakRef = new WeakRef(new LeakTestWidget()); - await asyncIdle(); - // It takes two GC cycles to free the widget, because of the tardy sweep - // problem (https://gitlab.gnome.org/GNOME/gjs/-/issues/217) - System.gc(); - System.gc(); + await asyncIdle(); + // It takes two GC cycles to free the widget, because of the tardy sweep + // problem (https://gitlab.gnome.org/GNOME/gjs/-/issues/217) + System.gc(); + System.gc(); - expect(weakRef.deref()).toBeUndefined(); + expect(weakRef.deref()).toBeUndefined(); + }); }); }); diff --git a/installed-tests/js/testImporter.js b/installed-tests/js/testImporter.js index af5b5c29f..0fbdf1118 100644 --- a/installed-tests/js/testImporter.js +++ b/installed-tests/js/testImporter.js @@ -205,10 +205,10 @@ describe('Importer', function () { it('will log a compatibility warning when accessed', function () { const GLib = imports.gi.GLib; - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'b' on the module " + "'lexicalScope'.*"); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'c' on the module " + "'lexicalScope'.*"); @@ -216,7 +216,7 @@ describe('Importer', function () { void LexicalScope.c; // g_test_assert_expected_messages() is a macro, not introspectable - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testImporter.js', 179, ''); }); diff --git a/installed-tests/js/testIntrospection.js b/installed-tests/js/testIntrospection.js index f9c0f35be..9cfe64010 100644 --- a/installed-tests/js/testIntrospection.js +++ b/installed-tests/js/testIntrospection.js @@ -23,16 +23,16 @@ describe('GLib.DestroyNotify parameter', function () { describe('Unsafe integer marshalling', function () { it('warns when conversion is lossy', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); void GLib.MININT64; void GLib.MAXINT64; void GLib.MAXUINT64; - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testEverythingBasic.js', 0, 'Limits warns when conversion is lossy'); }); @@ -155,7 +155,7 @@ describe('Garbage collection of introspected objects', function () { } } - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*property screenfull*'); const settings = new Gio.Settings({schemaId: 'org.cinnamon.CjsTest'}); @@ -166,7 +166,7 @@ describe('Garbage collection of introspected objects', function () { obj = null; settings.disconnect(handler); GLib.idle_add(GLib.PRIORITY_LOW, () => { - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testIntrospection.js', 0, 'Warn about setting property on disposed JS object'); done(); @@ -221,13 +221,13 @@ describe('Backwards compatibility for GLib/Gio platform specific GIRs', function return; } - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.UnixMountMonitor*'); const monitor = Gio.UnixMountMonitor.get(); expect(monitor.toString()).toContain('GIName:GioUnix.MountMonitor'); - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testIntrospection.js', 0, 'Expected deprecation message for Gio.Unix -> GioUnix'); }); @@ -238,12 +238,12 @@ describe('Backwards compatibility for GLib/Gio platform specific GIRs', function return; } - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.unix_mounts_get*GioUnix.mounts_get*instead*'); expect(imports.gi.Gio.unix_mounts_get.name).toBe('g_unix_mounts_get'); - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testIntrospection.js', 0, 'Expected deprecation message for Gio.Unix -> GioUnix'); }); diff --git a/installed-tests/js/testLegacyByteArray.js b/installed-tests/js/testLegacyByteArray.js index 3ae2fb04f..03ef41374 100644 --- a/installed-tests/js/testLegacyByteArray.js +++ b/installed-tests/js/testLegacyByteArray.js @@ -3,7 +3,7 @@ // SPDX-FileCopyrightText: 2017 Philip Chimento const ByteArray = imports.byteArray; -const {GIMarshallingTests, CjsTestTools, GLib} = imports.gi; +const {GIMarshallingTests, GjsTestTools, GLib} = imports.gi; describe('Uint8Array with legacy ByteArray functions', function () { it('can be created from a string', function () { @@ -74,14 +74,14 @@ describe('Uint8Array with legacy ByteArray functions', function () { }); it('deals gracefully with a non-aligned GBytes', function () { - const unalignedBytes = CjsTestTools.new_unaligned_bytes(48); + const unalignedBytes = GjsTestTools.new_unaligned_bytes(48); const arr = ByteArray.fromGBytes(unalignedBytes); expect(arr.length).toEqual(48); expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); }); it('deals gracefully with a GBytes in static storage', function () { - const staticBytes = CjsTestTools.new_static_bytes(); + const staticBytes = GjsTestTools.new_static_bytes(); const arr = ByteArray.fromGBytes(staticBytes); arr[2] = 42; expect(Array.from(arr)).toEqual([104, 101, 42, 108, 111, 0]); @@ -100,7 +100,7 @@ describe('Uint8Array with legacy ByteArray functions', function () { describe('legacy toString() behavior', function () { beforeEach(function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Some code called array.toString()*'); }); @@ -115,7 +115,7 @@ describe('Uint8Array with legacy ByteArray functions', function () { }); afterEach(function () { - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testByteArray.js', 0, 'testToStringCompatibility'); }); }); diff --git a/installed-tests/js/testLegacyGObject.js b/installed-tests/js/testLegacyGObject.js index 75cc3db19..2f113cadb 100644 --- a/installed-tests/js/testLegacyGObject.js +++ b/installed-tests/js/testLegacyGObject.js @@ -400,7 +400,7 @@ describe('GObject class', function () { expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); @@ -750,7 +750,7 @@ describe('GObject interface', function () { requiredG() {}, }); // g_test_assert_expected_messages() is a macro, not introspectable - GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectInterface.js', + GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js', 416, 'testGObjectMustOverrideInterfaceProperties'); }); diff --git a/installed-tests/js/testPrint.js b/installed-tests/js/testPrint.js index 558de6640..68d762c1f 100644 --- a/installed-tests/js/testPrint.js +++ b/installed-tests/js/testPrint.js @@ -213,6 +213,22 @@ describe('prettyPrint', function () { .toEqual('{ foo: [GjsFileImporter root] }'); }); + it('null prototype object', function () { + const obj = Object.create(null); + obj.test = 1; + expect(prettyPrint(obj)).toEqual('[Object: null prototype] { test: 1 }'); + }); + + it('null prototype object with custom toString', function () { + const obj = Object.create(null); + obj.toString = () => 'Maple Syrup'; + expect(prettyPrint(obj)).toEqual('Maple Syrup'); + }); + + it('object with nullish toString', function () { + expect(prettyPrint({toString: null})).toEqual('{ toString: null }'); + }); + describe('TypedArrays', () => { [ Int8Array, diff --git a/installed-tests/js/testPromise.js b/installed-tests/js/testPromise.js index 02c35cf4b..df302bac2 100644 --- a/installed-tests/js/testPromise.js +++ b/installed-tests/js/testPromise.js @@ -82,10 +82,10 @@ describe('Promise', function () { expect(thenHandler).not.toHaveBeenCalled(); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => loop.quit()); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Unhandled promise rejection.*'); loop.run(); - GLib.test_assert_expected_messages_internal('Cjs', 'testPromise.js', 0, + GLib.test_assert_expected_messages_internal('Gjs', 'testPromise.js', 0, 'warnsIfRejected'); }); diff --git a/installed-tests/js/testRegress.js b/installed-tests/js/testRegress.js index a4589c8cd..b824848c5 100644 --- a/installed-tests/js/testRegress.js +++ b/installed-tests/js/testRegress.js @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2008 Red Hat, Inc. +// SPDX-FileCopyrightText: 2024 Philip Chimento -const Regress = imports.gi.Regress; +const {Regress, Utility} = imports.gi; // We use Gio to have some objects that we know exist imports.gi.versions.Gtk = '3.0'; @@ -11,10 +12,10 @@ const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; function expectWarn64(callable) { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); const ret = callable(); - GLib.test_assert_expected_messages_internal('Cjs', + GLib.test_assert_expected_messages_internal('Gjs', 'testRegress.js', 0, 'Ignore message'); return ret; } @@ -189,6 +190,10 @@ describe('Life, the Universe and Everything', function () { expect(bounced).toEqual(now); }); + it('includes off_t', function () { + expect(Regress.test_offt(0x7fff_ffff)).toBe(0x7fff_ffff); + }); + it('includes GTypes', function () { expect(Regress.test_gtype(GObject.TYPE_NONE)).toBe(GObject.TYPE_NONE); expect(Regress.test_gtype(String)).toBe(GObject.TYPE_STRING); @@ -349,7 +354,11 @@ describe('Life, the Universe and Everything', function () { describe('String arrays', function () { it('marshalling in', function () { expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy(); + expect(Regress.test_strv_in(['1', '2'])).toBeFalsy(); expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy(); + expect(Regress.test_strv_in(['1', '5', '6'])).toBeFalsy(); + expect(Regress.test_strv_in(['1', '2', '6'])).toBeFalsy(); + expect(Regress.test_strv_in(['4', '5', null])).toBeFalsy(); // Ensure that primitives throw without SEGFAULT expect(() => Regress.test_strv_in(1)).toThrow(); expect(() => Regress.test_strv_in('')).toThrow(); @@ -570,6 +579,16 @@ describe('Life, the Universe and Everything', function () { Regress.TestFlags.FLAG3); }); + it('flag returned without private values below smallest flag value', function () { + expect(Regress.TestDiscontinuousFlags.DISCONTINUOUS1).toEqual(512); + expect(Regress.test_discontinuous_1_with_private_values()).toEqual(Regress.TestDiscontinuousFlags.DISCONTINUOUS1); + }); + + it('flag returned without private values above highest flag value', function () { + expect(Regress.TestDiscontinuousFlags.DISCONTINUOUS2).toEqual(536870912); + expect(Regress.test_discontinuous_2_with_private_values()).toEqual(Regress.TestDiscontinuousFlags.DISCONTINUOUS2); + }); + describe('Simple introspected struct', function () { let struct; beforeEach(function () { @@ -967,11 +986,11 @@ describe('Life, the Universe and Everything', function () { }); }); - xit('methods take priority over fields in a name conflict', function () { + it('methods take priority over fields in a name conflict', function () { const boxed = new Regress.TestBoxedC({name_conflict: true}); expect(boxed.name_conflict).not.toBeTrue(); expect(boxed.name_conflict()).toBeTrue(); - }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/454'); + }); }); describe('wrong type for GBoxed', function () { @@ -1155,6 +1174,9 @@ describe('Life, the Universe and Everything', function () { expect(t.string).toBe(string); t.string = 'string2'; expect(t.string).toBe('string2'); + t.set_string('string3'); + expect(t.string).toBe('string3'); + expect(t.get_string()).toBe('string3'); }); xit('GType object', function () { @@ -1471,41 +1493,61 @@ describe('Life, the Universe and Everything', function () { expect(o.skip_return_val.length).toEqual(5); }); - xit('skips over return value annotated with skip', function () { - const [b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5); + it('DOES NOT skip over return value annotated with skip', function () { + // This test will need to change as part of + // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an + // API break, so we have a regression test for the current + // behaviour. + const [bool, b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5); + expect(bool).toBeTrue(); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); const retval = o.skip_return_val_no_out(1); - expect(retval).not.toBeDefined(); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); + expect(retval).toBeTrue(); + expect(() => o.skip_return_val_no_out(0)).toThrow(); + }); - xit('skips over parameters annotated with skip', function () { - expect(o.skip_param.length).toEqual(4); + it('DOES NOT skip over parameters annotated with skip', function () { + // This test will need to change as part of + // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an + // API break, so we have a regression test for the current + // behaviour. + expect(o.skip_param.length).toEqual(5); - const [success, b, d, sum] = o.skip_param(1, 2, 3, 4); + const [success, b, d, sum] = o.skip_param(1, 1.5, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(3); expect(sum).toEqual(43); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); + }); - xit('skips over out parameters annotated with skip', function () { - const [success, d, sum] = o.skip_out_param(1, 2, 3, 4, 5); + it('DOES NOT skip over out parameters annotated with skip', function () { + // This test will need to change as part of + // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an + // API break, so we have a regression test for the current + // behaviour. + const [success, b, d, sum] = o.skip_out_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); + expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); + }); - xit('skips over inout parameters annotated with skip', function () { - expect(o.skip_inout_param.length).toEqual(4); + it('skips over inout parameters annotated with skip', function () { + // This test will need to change as part of + // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an + // API break, so we have a regression test for the current + // behaviour. + expect(o.skip_inout_param.length).toEqual(5); - const [success, b, sum] = o.skip_inout_param(1, 2, 3, 4); + const [success, b, d, sum] = o.skip_inout_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); expect(b).toEqual(2); - expect(sum).toEqual(43); - }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); + expect(d).toEqual(4); + expect(sum).toEqual(54); + }); it('gives number of arguments for static methods', function () { expect(Regress.TestObj.new_from_file.length).toEqual(1); @@ -1658,6 +1700,11 @@ describe('Life, the Universe and Everything', function () { it('constructs a subtype of a hidden (no introspection data) fundamental type', function () { expect(() => Regress.test_create_fundamental_hidden_class_instance()).not.toThrow(); }); + + it('constructs an instance of a fundamental type with no get/set function', function () { + const o = new Regress.TestFundamentalObjectNoGetSetFunc('plop'); + expect(o.get_data()).toBe('plop'); + }); }); it('callbacks', function () { @@ -1776,12 +1823,58 @@ describe('Life, the Universe and Everything', function () { const callback = jasmine.createSpy('callback'); o.instance_method_callback(callback); expect(callback).toHaveBeenCalled(); + callback.calls.reset(); + o.instance_method_callback(null); + expect(callback).not.toHaveBeenCalled(); + }); + + it('async instance methods', function (done) { + const o = new Regress.TestObj(); + const prio = GLib.PRIORITY_DEFAULT; + const cancel = new Gio.Cancellable(); + expect(o.function_sync(prio)).toBeTrue(); + o.function_async(prio, cancel, (obj, res) => { + expect(obj).toBe(o); + expect(o.function_finish(res)).toBeTrue(); + done(); + }); + expect(o.function_thaw_async()).toBe(1); + }); + + it('async instance method with extra callback', function (done) { + const o = new Regress.TestObj(); + const prio = GLib.PRIORITY_DEFAULT; + const cancel = new Gio.Cancellable(); + expect(o.function2_sync(prio)).toBeTrue(); + const testCallback = jasmine.createSpy('testCallback'); + o.function2(prio, cancel, testCallback, (obj, res) => { + expect(obj).toBe(o); + expect(o.function2_finish(res)).toEqual([true, null]); + expect(testCallback).toHaveBeenCalledTimes(1); + done(); + }); + expect(o.function_thaw_async()).toBe(1); }); it('static method taking a callback', function () { const callback = jasmine.createSpy('callback'); Regress.TestObj.static_method_callback(callback); expect(callback).toHaveBeenCalled(); + callback.calls.reset(); + Regress.TestObj.static_method_callback(null); + expect(callback).not.toHaveBeenCalled(); + }); + + it('async static methods', function (done) { + const prio = GLib.PRIORITY_DEFAULT; + const cancel = new Gio.Cancellable(); + expect(Regress.test_function_sync(prio)).toBeTrue(); + Regress.test_function_async(prio, cancel, (obj, res) => { + expect(obj).toBeNull(); + expect(Regress.test_function_finish(res)).toBeTrue(); + done(); + }); + expect(Regress.test_function_thaw_async()).toBe(1); }); it('constructor taking a callback', function () { @@ -1792,6 +1885,16 @@ describe('Life, the Universe and Everything', function () { expect(callback).toHaveBeenCalledTimes(2); }); + it('async constructor', function (done) { + const cancel = new Gio.Cancellable(); + Regress.TestObj.new_async('plop', cancel, (obj, res) => { + expect(obj).toBeNull(); + expect(Regress.TestObj.new_finish(res)).toBeInstanceOf(Regress.TestObj); + done(); + }); + expect(Regress.TestObj.constructor_thaw_async()).toBe(1); + }); + it('hash table passed to callback', function () { const hashtable = { a: 1, @@ -1938,6 +2041,7 @@ describe('Life, the Universe and Everything', function () { it('marshals an aliased type', function () { // GLib.PtrArray is not introspectable, so neither is an alias of it // Regress.introspectable_via_alias(new GLib.PtrArray()); + expect(Regress.not_introspectable_via_alias).not.toBeDefined(); expect(Regress.aliased_caller_alloc()).toEqual(jasmine.any(Regress.TestBoxed)); }); @@ -2002,3 +2106,564 @@ describe('Life, the Universe and Everything', function () { }); }); }); + +// Inline functions are not introspectable because there is no symbol to load +// from the library +describe('Inline', function () { + it('function', function () { + expect(Regress.test_inline_function).not.toBeDefined(); + }); + + it('method', function () { + const o = new Regress.TestObj(); + expect(o.inline_method).not.toBeDefined(); + }); +}); + +describe('Annotations object', function () { + let o; + beforeEach(function () { + o = new Regress.AnnotationObject(); + }); + + it('handles a deprecated string property', function () { + expect(o.stringProperty).toBeNull(); + o.stringProperty = 'foo'; + }); + + xit('handles a callback property', function () { + expect(o.functionProperty).toBeNull(); + o.functionProperty = null; + o.functionProperty = array => array.map(x => x + 1); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); + + it('handles a property that was annotated weirdly', function () { + expect(o.tabProperty).toBeNull(); + o.tabProperty = '\t'; + }); + + xit('handles a signal which takes a pointer argument but annotated as a string', function () { + o.connect('string-signal', function (self, str) { + expect(self).toBe(o); + expect(str).toBe('foo'); + }); + o.emit('string-signal', 'foo'); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/57'); + + xit('handles a signal which takes a list of strings', function () { + o.connect('list-signal', function (self, list) { + expect(self).toBe(o); + expect(list).toBe(['foo', 'bar']); + }); + o.emit('list-signal', ['foo', 'bar']); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/57'); + + it('handles a signal with undocumented argument', function () { + o.connect('doc-empty-arg-parsing', function (self, arg) { + expect(self).toBe(o); + expect(arg).toBeNull(); + }); + o.emit('doc-empty-arg-parsing', null); + }); + + it('handles a signal with arbitrary ignored attributes', function () { + o.connect('attribute-signal', function (self, s1, s2) { + expect(self).toBe(o); + expect(s1).toBe('foo'); + expect(s2).toBe('bar'); + return s1 + s2; + }); + expect(o.emit('attribute-signal', 'foo', 'bar')).toBe('foobar'); + }); + + xit('handles an in-argument passed by pointer', function () { + expect(o.in(2)).toBe(2); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/652'); + + xit('handles an optional in-out argument', function () { + expect(o.inout3()).toEqual([1, null]); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/53'); + + it('handles various in-out argument configurations also tested elsewhere', function () { + expect(o.method()).toBe(1); + expect(o.out()).toEqual([1, 2]); + expect(o.inout(2)).toEqual([3, 3]); + expect(o.inout2(2)).toEqual([3, 3]); // not sure why this exists + expect(o.inout3(1)).toEqual([2, 1]); + + expect(o.calleeowns()).toEqual([1, null]); + expect(o.calleesowns()).toEqual([1, null, null]); + + expect(o.get_strings()).toEqual(['bar', 'regress_annotation']); + expect(o.get_hash()).toEqual({ + one: o, + two: o, + }); + o.with_voidp(null); + expect(o.get_objects()).toEqual([o]); + expect(o.create_object()).toBe(o); + o.use_buffer(new Uint8Array(16)); + + o.compute_sum([1, 2, 3]); + o.compute_sum_n([1, 2, 3]); + o.compute_sum_nz([1, 2, 3]); + + o.parse_args(['--num', '5', '--no-florp']); + expect(o.string_out()).toEqual([false, null]); + o.foreach(() => {}); + + expect(o.set_data(Uint8Array.from([104, 105, 106, 107]))); + expect(o.set_data2([104, 105, 106, 107])); + expect(o.set_data3(Uint8Array.from([104, 105, 106, 107]))); + + expect(o.allow_none('foo')).toBeNull(); + expect(o.notrans()).toBeNull(); + expect(o.do_not_use()).toBeNull(); + o.watch(() => {}); + o.hidden_self(); + + Regress.annotation_init(['--num', '5', '--no-florp']); + expect(Regress.annotation_return_array()).toEqual([]); + expect(Regress.annotation_string_zero_terminated()).toBeNull(); + expect(Regress.annotation_string_zero_terminated_out(['in', 'out'])).toEqual(['in', 'out']); + Regress.annotation_versioned(); + Regress.annotation_string_array_length(['foo', 'bar']); + o.extra_annos(); + Regress.annotation_custom_destroy(() => {}); + Regress.annotation_custom_destroy_cleanup(); + expect(Regress.annotation_get_source_file()).toBeNull(); + Regress.annotation_set_source_file('résumé.txt'); + expect(Regress.annotation_attribute_func(o, 'foo')).toBe(42); + Regress.annotation_invalid_regress_annotation(42); + expect(Regress.annotation_test_parsing_bug630862()).toBeNull(); + Regress.annotation_space_after_comment_bug631690(); + expect(Regress.annotation_return_filename()).toBe('a utf-8 filename'); + expect(Regress.annotation_transfer_floating(o)).toBeNull(); + }); + + xit('handles a GPtrArray of GValue', function () { + Regress.annotation_ptr_array([]); + Regress.annotation_ptr_array([1, 2]); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/272 (maybe)'); + + xit('handles a struct with a fixed length array field', function () { + const s = new Regress.AnnotationStruct({ + objects: Array(10).fill(o), + }); + expect(s.objects).toEqual([o, o, o, o, o, o, o, o, o, o]); + }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/525'); + + it('handles various header-only stuff', function () { + const f = new Regress.AnnotationFields({ + field1: 42, + field4: 43, + }); + // writing the array field is not supported but should fail gracefully + expect(() => (f.arr = Uint8Array.from([104, 105, 106, 107]))).toThrow(); + + expect(Regress.ANNOTATION_CALCULATED_DEFINE).toBe(100); + expect(Regress.ANNOTATION_CALCULATED_LARGE_DIV).toBe(1000000); + }); + + xit('handles an integer constant larger than 32 bits', function () { + expect(Regress.ANNOTATION_CALCULATED_LARGE).toBe(10000000000); + }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/526'); +}); + +describe('Abstract drawable object', function () { + class MyDrawable extends Regress.TestInheritDrawable {} + beforeAll(function () { + GObject.registerClass(MyDrawable); + }); + + let o; + beforeEach(function () { + o = new MyDrawable(); + }); + + it('calls methods', function () { + void o.do_foo(42); + expect(o.get_origin()).toEqual([0, 0]); + expect(o.get_size()).toEqual([42, 42]); + void o.do_foo_maybe_throw(42); + expect(() => o.do_foo_maybe_throw(43)).toThrow(); + }); +}); + +describe('Regress.Foo', function () { + it('various stuff', function () { + expect(Regress.foo_init()).toBe(0x1138); + expect(Regress.foo_init_argv([])).toBe(0x1138); + expect(Regress.foo_init_argv_address(['--num', '5', '--no-florp'])) + .toEqual([0x1138, ['--num', '5', '--no-florp']]); + expect(Regress.foo_not_a_constructor_new()).toBeNull(); + + const o = new Utility.Object(); + const s = new Utility.Struct({ + field: 42, + bitfield1: 7, + bitfield2: 3, + }); + void Regress.foo_method_external_references(o, Utility.EnumType.C, Utility.FlagType.B, s); + void Regress.FooObject.a_global_method(o); + + expect(Regress.foo_private_function).not.toBeDefined(); + }); + + it('Interface', function () { + class Impl extends GObject.Object { + static [GObject.interfaces] = [Regress.FooInterface]; + static { + GObject.registerClass(Impl); + } + + do_regress_foo_called = false; + vfunc_do_regress_foo(foo) { + expect(foo).toBe(777); + this.do_regress_foo_called = true; + } + } + + Impl.static_method(77); + + const o = new Impl(); + o.do_regress_foo(777); + expect(o.do_regress_foo_called).toBeTrue(); + }); + + it('SubInterface', function () { + class SubImpl extends GObject.Object { + static [GObject.interfaces] = [Regress.FooInterface, Regress.FooSubInterface]; + static { + GObject.registerClass(SubImpl); + } + + do_regress_foo_called = false; + vfunc_do_regress_foo(foo) { + expect(foo).toBe(777); + this.do_regress_foo_called = true; + } + + do_bar_called = false; + vfunc_do_bar() { + this.do_bar_called = true; + } + } + + SubImpl.static_method(77); + + const o = new SubImpl(); + o.do_regress_foo(777); + expect(o.do_regress_foo_called).toBeTrue(); + + o.do_bar(); + expect(o.do_bar_called).toBeTrue(); + + const spy = jasmine.createSpy('callback'); + o.connect('destroy-event', spy); + o.emit('destroy-event'); + expect(spy).toHaveBeenCalledOnceWith(o); + }); + + xit('interface with vfunc that takes a callback', function () { + class BazImpl extends GObject.Object { + static [GObject.interfaces] = [Regress.FooInterface, Regress.FooSubInterface]; + static { + GObject.registerClass(BazImpl); + } + + do_baz_called = false; + vfunc_do_baz(callback) { + callback(777); + this.do_baz_called = true; + } + } + + const o = new BazImpl(); + const spy = jasmine.createSpy('callback'); + o.do_baz(spy); + expect(spy).toHaveBeenCalledOnceWith(777); + expect(o.do_baz_called).toBeTrue(); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/72'); + + it('Object static', function () { + expect(Regress.FooObject.new()).toBeInstanceOf(Regress.FooObject); + expect(Regress.FooObject.new_as_super()).toBeInstanceOf(Regress.FooObject); + + expect(Regress.FooObject.static_meth()).toBe(77); + + expect(Regress.FooObject.get_default()).toBeNull(); + }); + + function testRegressFooObjectMethods(o) { + expect(o.external_type()).toBeNull(); + o.various(null, Regress.AnnotationObject); + o.is_it_time_yet(new Date()); + o.seek(0x7fff_ffff_ffff_ffffn); + expect(o.get_name()).toBe('regress_foo'); + expect(o.dup_name()).toBe('regress_foo'); + o.handle_glyph(0x2212); + expect(o.skipped_method).toBeUndefined(); + expect(o.append_new_stack_layer(5)).toBeNull(); + o.do_regress_foo(777); + } + + it('Object instance', function () { + const o = new Regress.FooObject(); + testRegressFooObjectMethods(o); + expect(o.virtual_method(77)).toBeFalse(); + o.read(0xff, 10); + }); + + it('Object properties', function () { + const o = new Regress.FooObject({ + string: 'string', + }); + expect(o.string).toBeNull(); + expect(o.hidden).toBeUndefined(); + }); + + it('Subobject instance', function () { + class MySubobject extends Regress.FooSubobject { + vfunc_virtual_method(first_param) { + expect(first_param).toBe(77); + return true; + } + + read_fn_called = false; + vfunc_read_fn() { + this.read_fn_called = true; + } + } + GObject.registerClass(MySubobject); + const o = new MySubobject(); + testRegressFooObjectMethods(o); + expect(o.virtual_method(77)).toBeTrue(); + o.read(0xff, 10); + expect(o.read_fn_called).toBeFalse(); // C method does not call vfunc + }); + + it('Buffer instance', function () { + const o = new Regress.FooBuffer(); + o.some_method(); + }); + + it('OtherObject instance', function () { + void new Regress.FooOtherObject(); + }); + + it('EnumType', function () { + expect(Regress.foo_enum_method(Regress.FooEnumType.BETA)).toBe(0); + expect(Regress.FooEnumType.method(Regress.FooEnumType.ALPHA)).toBe(1); + expect(Regress.foo_enum_type_method(Regress.FooEnumType.ALPHA)).toBe(1); + expect(Regress.FooEnumType.returnv(1)).toBe(Regress.FooEnumType.DELTA); + expect(Regress.foo_enum_type_returnv(1)).toBe(Regress.FooEnumType.DELTA); + }); + + it('FlagsType', function () { + expect(Regress.FooFlagsType.FIRST).toBe(1); + expect(Regress.FooFlagsType.SECOND).toBe(2); + expect(Regress.FooFlagsType.THIRD).toBe(4); + expect(Regress.FOO_FLAGS_SECOND_AND_THIRD).toBe(6); + }); + + it('EnumNoType', function () { + expect(Regress.FooEnumNoType.UN).toBe(1); + expect(Regress.FooEnumNoType.DEUX).toBe(2); + expect(Regress.FooEnumNoType.TROIS).toBe(3); + expect(Regress.FooEnumNoType.NEUF).toBe(9); + }); + + it('FlagsNoType', function () { + expect(Regress.FooFlagsNoType.ETT).toBe(1); + expect(Regress.FooFlagsNoType.TVA).toBe(2); + expect(Regress.FooFlagsNoType.FYRA).toBe(4); + }); + + it('EnumFullname', function () { + expect(Regress.FooEnumFullname.ONE).toBe(1); + expect(Regress.FooEnumFullname.TWO).toBe(2); + expect(Regress.FooEnumFullname.THREE).toBe(3); + }); + + it('Address', function () { + expect(Regress.FooAddressType.INVALID).toBe(0); + expect(Regress.FooAddressType.IPV4).toBe(1); + expect(Regress.FooAddressType.IPV6).toBe(2); + }); + + it('Various boxed instances', function () { + const o1 = new Regress.FooBoxed(); + o1.method(); + + const o2 = new Regress.FooBRect({x: 1.5, y: -2.5}); + expect(o2.x).toBe(1.5); + expect(o2.y).toBe(-2.5); + + const o3 = Regress.FooBRect.new(-1.4, 2.6); + expect(o3.x).toBe(-1.4); + expect(o3.y).toBe(2.6); + o2.add(o3); + o3.add(o2); + + const o4 = new Regress.FooBUnion(); + o4.type = 77; + expect(o4.type).toBe(77); + o4.v = 7.777; + expect(o4.v).toBe(7.777); + o4.rect = o3; + expect(o4.rect).toBe(o3); + }); + + it('Rectangle instance', function () { + const o1 = new Regress.FooRectangle({x: 0, y: 0, width: 10, height: 10}); + const o2 = new Regress.FooRectangle({x: 1, y: 1, width: 12, height: 12}); + o1.add(o2); + o2.add(o1); + }); + + it('Various test functions', function () { + Regress.foo_test_unsigned(0xffff_ffff); + Regress.foo_test_string_array([]); + Regress.foo_test_string_array_with_g([]); + expect(Regress.foo_test_array()).toBeNull(); + }); + + it('Error type', function () { + expect(() => { + throw new Regress.FooError({message: 'foo', code: Regress.FooError.BAD}); + }).toThrow(jasmine.objectContaining({ + message: 'foo', + code: Regress.FooError.BAD, + })); + }); + + it('Foreign struct', function () { + const s1 = new Regress.FooForeignStruct({regress_foo: 777}); + expect(s1.regress_foo).toBe(777); + }); + + xit('Foreign struct via introspected constructor', function () { + const s2 = Regress.FooForeignStruct.new(); + expect(s2.regress_foo).toBe(0); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/656'); +}); + +describe('RegressUnix', function () { + let RegressUnix; + try { + ({RegressUnix} = imports.gi); + } catch (_) { + return; + } + + ['devt', 'gidt', 'pidt', 'socklent', 'uidt'].forEach(name => { + it(`handles ${name}`, function () { + expect(RegressUnix[`test_${name}`](12345)).toBe(12345); + }); + }); +}); + +// Adapted from pygobject +describe('Boxed type return extra tests', function () { + it('Refcounted boxed type wrapper', function () { + const wrapper = new Regress.TestBoxedCWrapper(); + const copy = wrapper.copy(); + // TestBoxedC uses refcounting, so the underlying native objects should + // be the same + function nativeAddress(boxed) { + const match = /native@0x([0-9a-f]+)/.exec(boxed.toString()); + return match[1]; + } + expect(nativeAddress(copy)).not.toBe(nativeAddress(wrapper)); + expect(nativeAddress(copy.get())).toBe(nativeAddress(wrapper.get())); + }); + + it('Array of boxed type transfer none out parameter', function () { + expect(Regress.test_array_fixed_boxed_none_out()).toEqual([ + jasmine.any(Regress.TestBoxedC), + jasmine.any(Regress.TestBoxedC), + ]); + }); + + it('Boxed GValue out', function () { + const int8 = Math.trunc(Math.random() * (GLib.MAXINT8 - GLib.MININT8) + GLib.MININT8); + expect(Regress.test_gvalue_out_boxed(int8).some_int8).toBe(int8); + }); + + ['none', 'full'].forEach(transfer => { + it(`GList of boxed type transfer ${transfer} return value`, function () { + expect(Regress[`test_glist_boxed_${transfer}_return`](2)).toEqual([ + jasmine.any(Regress.TestBoxedC), + jasmine.any(Regress.TestBoxedC), + ]); + }); + }); +}); + +// Adapted from pygobject +describe('UTF-8 strings invalid bytes tests', function () { + xit('handles invalid UTF-8 return values gracefully', function () { + expect(() => Regress.test_array_of_non_utf8_strings()).toThrowError(TypeError); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/658'); +}); + +// Adapted from pygobject +describe('Fundamental extra tests', function () { + it('array of fundamental objects in', function () { + expect(Regress.test_array_of_fundamental_objects_in([ + new Regress.TestFundamentalSubObject('data1'), + new Regress.TestFundamentalSubObject('data2'), + ])).toBeTrue(); + }); + + it('array of fundamental objects out', function () { + const objs = Regress.test_array_of_fundamental_objects_out(); + expect(objs).toEqual([ + jasmine.any(Regress.TestFundamentalObject), + jasmine.any(Regress.TestFundamentalObject), + ]); + }); + + it('in argument', function () { + const o = new Regress.TestFundamentalSubObject('data'); + expect(Regress.test_fundamental_argument_in(o)).toBeTrue(); + }); + + it('abstract type', function () { + expect(() => new Regress.TestFundamentalObject()).toThrow(); + }); + + it('out argument', function () { + const o = new Regress.TestFundamentalSubObject('data'); + const sameObject = Regress.test_fundamental_argument_out(o); + expect(sameObject).toBe(o); + }); +}); + +// See testCairo.js for extra Cairo tests from regressextra.c + +// Adapted from pygobject +describe('Class with action signals', function () { + let o; + beforeEach(function () { + o = new Regress.TestAction(); + }); + + xit('returns a new object', function () { + const otherObj = o.emit('action'); + expect(otherObj).toBeInstanceOf(Regress.TestAction); + expect(otherObj).not.toBe(o); + }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/661'); + + it('returns null', function () { + expect(o.emit('action2')).toBeNull(); + }); +}); + +describe('Bitmask fundamental type', function () { + xit('can be created', function () { + const bitmask = new Regress.Bitmask(2); + console.log(bitmask); + }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection-tests/-/issues/7'); +}); diff --git a/installed-tests/js/testSignals.js b/installed-tests/js/testSignals.js index 4a04174ea..f80efb0c0 100644 --- a/installed-tests/js/testSignals.js +++ b/installed-tests/js/testSignals.js @@ -151,7 +151,7 @@ function testSignals(klass) { bar2 = jasmine.createSpy('bar'); foo[connectMethod]('bar', bar); foo[connectMethod]('bar', bar2); - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); }); @@ -162,7 +162,7 @@ function testSignals(klass) { }); it('does not disconnect the callback', function () { - GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(2); diff --git a/installed-tests/js/testSystem.js b/installed-tests/js/testSystem.js index 3795e8ab4..3184122d6 100644 --- a/installed-tests/js/testSystem.js +++ b/installed-tests/js/testSystem.js @@ -16,6 +16,13 @@ describe('System.addressOf()', function () { }); }); +describe('System.version', function () { + it('gives a plausible number', function () { + expect(System.version).not.toBeLessThan(1000000); + expect(System.version).toBeLessThan(2000000); + }); +}); + describe('System.refcount()', function () { it('gives the correct number', function () { let o = new GObject.Object({}); diff --git a/installed-tests/js/testUtility.js b/installed-tests/js/testUtility.js new file mode 100644 index 000000000..63e3b1495 --- /dev/null +++ b/installed-tests/js/testUtility.js @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Philip Chimento + +import Utility from 'gi://Utility'; + +// A small library, part of gobject-introspection-tests, mainly used as a +// dependency of other tests. + +describe('Utility callback', function () { + it('recognizes callback correctly', function () { + Utility.dir_foreach('/path/', () => {}); + }); +}); + +describe('Utility object', function () { + it('recognizes callback correctly', function () { + const o = new Utility.Object(); + o.watch_dir('/path/', () => {}); + }); +}); + +describe('Utility unions/structs', function () { + xit('TaggedValue', function () { + const t = new Utility.TaggedValue(); + t.tag = 0xff; + expect(t.tag).toBe(0xff); + expect(t.value.v_pointer).toBeNull(); + t.value.v_integer = 0x7fff_ffff; + expect(t.value.v_integer).toBe(0x7fff_ffff); + t.value.v_double = Math.PI; + expect(t.value.v_double).toBe(Math.PI); + }).pend('Unions nested in structs not supported. Open an issue if you need this'); + + it('Byte', function () { + const b = new Utility.Byte(); + b.value = 0xcd; + expect(b.parts.first_nibble).toBe(0xc); + expect(b.parts.second_nibble).toBe(0xd); + }).pend('Unions must currently be registered as boxed types. Open an issue if you need this'); + + it('Buffer', function () { + const b = new Utility.Buffer(); + expect(b.length).toBe(0); + expect(b.data).toBe(null); + }); + + xit('Struct', function () { + const s = new Utility.Struct(); + s.field = 42; + s.bitfield1 = 0xf; + s.bitfield2 = 0x0; + expect(s.field).toBe(42); + expect(s.bitfield1).toBe(7); + expect(s.bitfield2).toBe(0); + expect(s.data).toBeInstanceOf(Uint8Array); + }).pend('Bitfields not supported. Open an issue if you need this'); + + xit('Union', function () { + const u = new Utility.Union(); + expect(u.pointer).toBeNull(); + u.integer = 0x7fff_ffff; + expect(u.integer).toBe(0x7fff_ffff); + u.real = Math.PI; + expect(u.real).toBe(Math.PI); + }).pend('Unions must currently be registered as boxed types. Open an issue if you need this'); +}); + +describe('Utility enums/flags', function () { + it('enum', function () { + expect(Utility.EnumType).toEqual(jasmine.objectContaining({ + A: 0, + B: 1, + C: 2, + })); + }); + + it('flags', function () { + expect(Utility.FlagType).toEqual(jasmine.objectContaining({ + A: 1, + B: 2, + C: 4, + })); + }); +}); diff --git a/installed-tests/meson.build b/installed-tests/meson.build index a56e0373a..cde58949d 100644 --- a/installed-tests/meson.build +++ b/installed-tests/meson.build @@ -8,8 +8,8 @@ simple_tests = [] tests_dependencies = [ - cjs_console, - cjs_private_typelib, + gjs_console, + gjs_private_typelib, ] # The test scripts need to be ported from shell scripts @@ -20,6 +20,10 @@ if cxx.get_argument_syntax() != 'msvc' 'CommandLineModules', 'Warnings', ] + + if not get_option('skip_gtk_tests') and have_gtk4 + simple_tests += 'Gtk4Warnings' + endif endif foreach test : simple_tests @@ -48,7 +52,7 @@ subdir('js') # Debugger script tests # -debugger_tests = [ +debugger_command_tests = [ 'backtrace', 'breakpoint', 'continue', @@ -67,7 +71,6 @@ debugger_tests = [ 'set', 'step', 'throw', - 'throw-ignored', 'until', ] @@ -76,7 +79,7 @@ if get_option('installed_tests') install_data('debugger-test.sh', install_dir: installed_tests_execdir) endif -foreach test : debugger_tests +foreach test : debugger_command_tests test_file = files('debugger' / '@0@.debugger'.format(test)) test('@0@ command'.format(test), debugger_test_driver, @@ -100,3 +103,43 @@ foreach test : debugger_tests install_dir: installed_tests_execdir / 'debugger') endif endforeach + +debugger_tests = [ + 'sourcemap dynamic module', + 'sourcemap separate module', + 'sourcemap separate', + 'sourcemap inlined', + 'sourcemap inlined module', + 'throw ignored', +] + +foreach test : debugger_tests + filename = test.replace(' ', '-') + test_file = files('debugger' / '@0@.debugger'.format(filename)) + + test(test, debugger_test_driver, + args: test_file, env: tests_environment, protocol: 'tap', + suite: 'Debugger', depends: tests_dependencies) + + test_description_subst = { + 'name': '@0@.debugger'.format(filename), + 'installed_tests_execdir': prefix / installed_tests_execdir, + } + configure_file(configuration: test_description_subst, + input: 'debugger.test.in', + output: '@0@.test'.format(filename), + install: get_option('installed_tests'), + install_dir: installed_tests_metadir) + + if get_option('installed_tests') + install_data(test_file, install_dir: installed_tests_execdir / 'debugger') + install_data('debugger' / '@0@.debugger.js'.format(filename), + 'debugger' / '@0@.debugger.output'.format(filename), + install_dir: installed_tests_execdir / 'debugger') + endif +endforeach + +if get_option('installed_tests') + install_data('debugger' / 'sourcemap-number-module.js', + install_dir: installed_tests_execdir / 'debugger') +endif diff --git a/installed-tests/minijasmine.cpp b/installed-tests/minijasmine.cpp index 00488e6db..e6502c10b 100644 --- a/installed-tests/minijasmine.cpp +++ b/installed-tests/minijasmine.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -37,12 +37,14 @@ main(int argc, char **argv) setlocale(LC_ALL, ""); + GIRepository* repo = gi_repository_dup_default(); if (g_getenv("GJS_USE_UNINSTALLED_FILES") != NULL) { - g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR")); + gi_repository_prepend_search_path(repo, g_getenv("TOP_BUILDDIR")); } else { - g_irepository_prepend_search_path(INSTTESTDIR); - g_irepository_prepend_library_path(INSTTESTDIR); + gi_repository_prepend_search_path(repo, INSTTESTDIR); + gi_repository_prepend_library_path(repo, INSTTESTDIR); } + g_clear_object(&repo); const char *coverage_prefix = g_getenv("GJS_UNIT_COVERAGE_PREFIX"); const char *coverage_output_path = g_getenv("GJS_UNIT_COVERAGE_OUTPUT"); diff --git a/installed-tests/scripts/common.sh b/installed-tests/scripts/common.sh old mode 100644 new mode 100755 diff --git a/installed-tests/scripts/testCommandLine.sh b/installed-tests/scripts/testCommandLine.sh old mode 100644 new mode 100755 index 4a06e8d25..e529ec2d6 --- a/installed-tests/scripts/testCommandLine.sh +++ b/installed-tests/scripts/testCommandLine.sh @@ -114,6 +114,27 @@ GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 3, () => loop.quit()); loop.run(); EOF +cat <int.js +const Format = imports.format; +const output = imports.format.vprintf('%Id', [60]); +print(output + .split('') + .map(c => c.codePointAt(0).toString(16).padStart(4, '0')) + .join(' ')); +EOF + +# this script prints out all files in current directory +# useful to test encodings and filename type marshalling +cat <printFiles.js +const {GLib, Gio} = imports.gi; +const cd = Gio.File.new_for_path('.'); +const iter = cd.enumerate_children("standard::name", null, null); +let f; +while (f = iter.next_file(null)) { + f.get_name() +} +EOF + total=0 report () { @@ -183,6 +204,19 @@ report "Basic unicode encoding (accents, etc) should be functioning properly for $gjs -c 'imports.system.exit((ARGV[0] !== "☭") ? 1 : 0)' "☭" report "Unicode encoding for symbols should be functioning properly for ARGV and imports." +# ensure unicode paths are supported +mkdir Код +touch Код/🍁.js +$gjs -m Код/🍁.js +report "Unicode pathed encoding should work for module run." +rm -r Код + +# non UTF8 file names throws error +bash -c "touch $'\xff'" +G_FILENAME_ENCODING=utf8 $gjs -m printFiles.js 2>&1 | grep -q 'Gjs-CRITICAL.*Could not convert filename string to UTF-8 for string: \\377' +report "Throws error if filename is not UTF8" +bash -c "rm ''$'\377'" + # gjs --help prints GJS help $gjs --help >/dev/null report "--help should succeed" @@ -227,11 +261,11 @@ report "--help after -c should not print anything" # report_xfail "-I after script file should not be added to search path" # fi G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//') -$gjs help.js --help -I sentinel 2>&1 | grep -q 'Cjs-WARNING.*--include-path' +$gjs help.js --help -I sentinel 2>&1 | grep -q 'Gjs-WARNING.*--include-path' report "-I after script should succeed but give a warning" -$gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-prefix' +$gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Gjs-WARNING.*--coverage-prefix' report "--coverage-prefix after script should succeed but give a warning" -$gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-output' +$gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Gjs-WARNING.*--coverage-output' report "--coverage-output after script should succeed but give a warning" rm -f foo/coverage.lcov G_DEBUG="$OLD_G_DEBUG" @@ -257,7 +291,7 @@ $gjs -c 'imports.system.exit(0)' && ! stat gjs-*.syscap > /dev/null 2>&1 report "no profiling data should be dumped without --profile" # Skip some tests if built without profiler support -if $gjs --profile -c 1 2>&1 | grep -q 'Cjs-Message.*Profiler is disabled'; then +if $gjs --profile -c 1 2>&1 | grep -q 'Gjs-Message.*Profiler is disabled'; then reason="profiler is disabled" skip "--profile should dump profiling data to the default file name" "$reason" skip "--profile with argument should dump profiling data to the named file" "$reason" @@ -284,7 +318,7 @@ report "interpreter should run queued promise jobs before finishing" test -n "${output##*Should not be printed*}" report "interpreter should stop running jobs when one calls System.exit()" -$gjs -c "Promise.resolve().then(() => { throw new Error(); });" 2>&1 | grep -q 'Cjs-WARNING.*Unhandled promise rejection.*[sS]tack trace' +$gjs -c "Promise.resolve().then(() => { throw new Error(); });" 2>&1 | grep -q 'Gjs-WARNING.*Unhandled promise rejection.*[sS]tack trace' report "unhandled promise rejection should be reported" test -z "$($gjs awaitcatch.js)" report "catching an await expression should not cause unhandled rejection" @@ -341,7 +375,21 @@ else skip "exit after first System.exit call in a signal callback" "running under valgrind" fi -rm -f exit.js help.js promise.js awaitcatch.js doublegi.js argv.js \ +# https://gitlab.gnome.org/GNOME/gjs/-/issues/671 +output=$(LC_ALL=C $gjs int.js) +test "$output" = "0036 0030" +report "%Id prints Latin digits in C locale $output" +output=$(LC_ALL=en_CA $gjs int.js) +test "$output" = "0036 0030" +report "%Id prints Latin digits in en_CA locale $output" +output=$(LC_ALL=fa_IR $gjs int.js) +test "$output" = "06f6 06f0" +report "%Id prints Persian digits in fa_IR locale $output" +output=$(LC_ALL=ar_EG $gjs int.js) +test "$output" = "0666 0660" +report "%Id prints Arabic digits in ar_EG locale $output" + +rm -f exit.js help.js promise.js awaitcatch.js doublegi.js argv.js int.js \ signalexit.js promiseexit.js echo "1..$total" diff --git a/installed-tests/scripts/testCommandLineModules.sh b/installed-tests/scripts/testCommandLineModules.sh old mode 100644 new mode 100755 diff --git a/installed-tests/scripts/testExamples.sh b/installed-tests/scripts/testExamples.sh old mode 100644 new mode 100755 diff --git a/installed-tests/scripts/testGtk4Warnings.sh b/installed-tests/scripts/testGtk4Warnings.sh new file mode 100755 index 000000000..fdad2e044 --- /dev/null +++ b/installed-tests/scripts/testGtk4Warnings.sh @@ -0,0 +1,111 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2025 Gary Li + +if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then + gjs="$TOP_BUILDDIR/cjs-console" +else + gjs="cjs-console" +fi + +total=0 + +report () { + exit_code=$? + total=$((total + 1)) + if test $exit_code -eq 0; then + echo "ok $total - $1" + else + echo "not ok $total - $1" + fi +} + +cat <<'EOF' >gcWrapperWarning.js +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk?version=4.0'; + +Gtk.init(); +const encoder = new TextEncoder(); +const Window = GObject.registerClass({ + GTypeName: 'Window', + Template: encoder.encode(` + + + `), + Properties: { + 'model': GObject.ParamSpec.object('model', '', '', + GObject.ParamFlags.READWRITE, Gtk.StringList), + }, +}, class Window extends Gtk.Window { + _init(props = {}) { + super._init(props); + this.child.factory = new Gtk.BuilderListItemFactory({bytes: new GLib.Bytes(encoder.encode(` + + + + `))}); + } +}); + +const Row = GObject.registerClass({ + GTypeName: 'Row', + Template: encoder.encode(` + + + `), + Properties: { + 'string-object': GObject.ParamSpec.object('string-object', '', '', + GObject.ParamFlags.READWRITE, Gtk.StringObject), + }, +}, class Row extends Gtk.Box { +}); + +const loop = GLib.MainLoop.new(null, false); +const win = new Window({model: Gtk.StringList.new(['test'])}); +let weak = new WeakRef(win); +win.connect('close-request', () => loop.quit()); +GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + weak.deref()?.close(); + return false; +}); +win.present(); +loop.run(); +EOF + +$gjs -m gcWrapperWarning.js 2>&1 | \ + grep -q 'Wrapper for GObject.*was disposed, cannot set property string-object' +test $? -eq 0 +report "Issue 443 GObject wrapper disposed warning" + +rm -f gcWrapperWarning.js + +echo "1..$total" \ No newline at end of file diff --git a/installed-tests/scripts/testWarnings.sh b/installed-tests/scripts/testWarnings.sh old mode 100644 new mode 100755 diff --git a/js.gresource.xml b/js.gresource.xml index e26c392ba..feb98852c 100644 --- a/js.gresource.xml +++ b/js.gresource.xml @@ -4,6 +4,14 @@ + modules/internal/internalLoader.js + modules/internal/source-map/array-set.js + modules/internal/source-map/base64-vlq.js + modules/internal/source-map/base64.js + modules/internal/source-map/binary-search.js + modules/internal/source-map/extractUrl.js + modules/internal/source-map/source-map-consumer.js + modules/internal/source-map/util.js modules/internal/loader.js diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 43feb2edf..000000000 --- a/jsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "lib": ["es2020"], - } -} \ No newline at end of file diff --git a/libgjs-private/gjs-util.c b/libgjs-private/gjs-util.c index ae65287f5..7c94d86c7 100644 --- a/libgjs-private/gjs-util.c +++ b/libgjs-private/gjs-util.c @@ -6,29 +6,20 @@ #include -#include /* for setlocale */ +#include /* for errno */ +#include /* for setlocale/duplocale/uselocale/newlocale/freelocale */ #include #include /* for size_t */ #include #include -#include +#include #include #include /* for bindtextdomain, bind_textdomain_codeset, textdomain */ #include "libgjs-private/gjs-util.h" #include "util/console.h" -char * -gjs_format_int_alternative_output(int n) -{ -#ifdef HAVE_PRINTF_ALTERNATIVE_INT - return g_strdup_printf("%Id", n); -#else - return g_strdup_printf("%d", n); -#endif -} - GType gjs_locale_category_get_type(void) { static size_t gjs_locale_category_get_type = 0; if (g_once_init_enter(&gjs_locale_category_get_type)) { @@ -53,19 +44,169 @@ GType gjs_locale_category_get_type(void) { return gjs_locale_category_get_type; } +typedef struct { + locale_t id; + char* name; + char* prior_name; +} GjsLocale; + +#define UNSET_LOCALE_ID ((locale_t)0) + +static void gjs_clear_locale_id(locale_t* id) { + if (id == NULL) + return; + + if (*id == UNSET_LOCALE_ID) + return; + + freelocale(*id); + *id = UNSET_LOCALE_ID; +} + +static locale_t gjs_steal_locale_id(locale_t* id) { + locale_t stolen_id = *id; + *id = UNSET_LOCALE_ID; + return stolen_id; +} + +static gboolean gjs_set_locale_id(locale_t* locale_id_pointer, + locale_t new_locale_id) { + if (*locale_id_pointer == new_locale_id) + return FALSE; + + if (*locale_id_pointer != UNSET_LOCALE_ID) + freelocale(*locale_id_pointer); + *locale_id_pointer = new_locale_id; + + return TRUE; +} + +static int gjs_locale_category_get_mask(GjsLocaleCategory category) { + /* It's tempting to just return (1 << category) but the header file + * says not to do that. + */ + switch (category) { + case GJS_LOCALE_CATEGORY_ALL: + return LC_ALL_MASK; + case GJS_LOCALE_CATEGORY_COLLATE: + return LC_COLLATE_MASK; + case GJS_LOCALE_CATEGORY_CTYPE: + return LC_CTYPE_MASK; + case GJS_LOCALE_CATEGORY_MESSAGES: + return LC_MESSAGES_MASK; + case GJS_LOCALE_CATEGORY_MONETARY: + return LC_MONETARY_MASK; + case GJS_LOCALE_CATEGORY_NUMERIC: + return LC_NUMERIC_MASK; + case GJS_LOCALE_CATEGORY_TIME: + return LC_TIME_MASK; + default: + break; + } + + return 0; +} +static size_t get_number_of_locale_categories(void) { + return __builtin_popcount(LC_ALL_MASK) + 1; +} + +static void gjs_locales_free(GjsLocale** locales) { + size_t number_of_categories = get_number_of_locale_categories(); + size_t i; + + for (i = 0; i < number_of_categories; i++) { + GjsLocale* locale = locales[i]; + gjs_clear_locale_id(&locale->id); + g_clear_pointer(&locale->name, g_free); + g_clear_pointer(&locale->prior_name, g_free); + } + + g_free(locales); +} + +static GjsLocale* gjs_locales_new(void) { + size_t number_of_categories = get_number_of_locale_categories(); + GjsLocale* locales; + + locales = g_new0(GjsLocale, number_of_categories); + + return locales; +} + +static GPrivate gjs_private_locale_key = + G_PRIVATE_INIT((GDestroyNotify)gjs_locales_free); + /** - * gjs_setlocale: + * gjs_set_thread_locale: * @category: * @locale: (allow-none): * * Returns: */ -const char * -gjs_setlocale(GjsLocaleCategory category, const char *locale) -{ - /* According to man setlocale(3), the return value may be allocated in - * static storage. */ - return (const char *) setlocale(category, locale); +const char* gjs_set_thread_locale(GjsLocaleCategory category, + const char* locale_name) { + locale_t new_locale_id = UNSET_LOCALE_ID, old_locale_id = UNSET_LOCALE_ID; + GjsLocale *locales = NULL, *locale = NULL; + int category_mask; + char* prior_name = NULL; + gboolean success = FALSE; + int errno_save; + + locales = g_private_get(&gjs_private_locale_key); + + if (locales == NULL) { + locales = gjs_locales_new(); + g_private_set(&gjs_private_locale_key, locales); + } + locale = &locales[category]; + + if (locale_name == NULL) { + if (locale->name != NULL) + return locale->name; + + return setlocale(category, NULL); + } + + old_locale_id = uselocale(UNSET_LOCALE_ID); + if (old_locale_id == UNSET_LOCALE_ID) + goto out; + + old_locale_id = duplocale(old_locale_id); + if (old_locale_id == UNSET_LOCALE_ID) + goto out; + + category_mask = gjs_locale_category_get_mask(category); + + if (category_mask == 0) + goto out; + + new_locale_id = newlocale(category_mask, locale_name, old_locale_id); + + if (new_locale_id == UNSET_LOCALE_ID) + goto out; + old_locale_id = UNSET_LOCALE_ID; /* was moved into new_locale_id */ + + prior_name = g_strdup(setlocale(category, NULL)); + + if (uselocale(new_locale_id) == UNSET_LOCALE_ID) + goto out; + + g_set_str(&locale->prior_name, prior_name); + gjs_set_locale_id(&locale->id, gjs_steal_locale_id(&new_locale_id)); + g_set_str(&locale->name, setlocale(category, NULL)); + + success = TRUE; +out: + g_clear_pointer(&prior_name, g_free); + errno_save = errno; + gjs_clear_locale_id(&old_locale_id); + gjs_clear_locale_id(&new_locale_id); + errno = errno_save; + + if (!success) + return NULL; + + return locale->prior_name; } void @@ -125,7 +266,6 @@ GBinding* gjs_g_object_bind_property_full( to_closure, from_closure); } -#if GLIB_CHECK_VERSION(2, 72, 0) void gjs_g_binding_group_bind_full( GBindingGroup* source, const char* source_property, GObject* target, const char* target_property, GBindingFlags flags, @@ -147,30 +287,26 @@ void gjs_g_binding_group_bind_full( target_property, flags, to_closure, from_closure); } -#endif #undef G_CLOSURE_NOTIFY static GParamSpec* gjs_gtk_container_class_find_child_property( GIObjectInfo* container_info, GObject* container, const char* property) { - GIBaseInfo* class_info = NULL; - GIBaseInfo* find_child_property_fun = NULL; - GIArgument ret; GIArgument find_child_property_args[2]; - class_info = g_object_info_get_class_struct(container_info); - find_child_property_fun = - g_struct_info_find_method(class_info, "find_child_property"); + GIStructInfo* class_info = gi_object_info_get_class_struct(container_info); + GIFunctionInfo* find_child_property_fun = + gi_struct_info_find_method(class_info, "find_child_property"); find_child_property_args[0].v_pointer = G_OBJECT_GET_CLASS(container); find_child_property_args[1].v_string = (char*)property; - g_function_info_invoke(find_child_property_fun, find_child_property_args, 2, - NULL, 0, &ret, NULL); + gi_function_info_invoke(find_child_property_fun, find_child_property_args, + 2, NULL, 0, &ret, NULL); - g_clear_pointer(&class_info, g_base_info_unref); - g_clear_pointer(&find_child_property_fun, g_base_info_unref); + g_clear_pointer(&class_info, gi_base_info_unref); + g_clear_pointer(&find_child_property_fun, gi_base_info_unref); return (GParamSpec*)ret.v_pointer; } @@ -179,16 +315,15 @@ void gjs_gtk_container_child_set_property(GObject* container, GObject* child, const char* property, const GValue* value) { GParamSpec* pspec = NULL; - GIBaseInfo* base_info = NULL; - GIBaseInfo* child_set_property_fun = NULL; - GIObjectInfo* container_info; + GIFunctionInfo* child_set_property_fun = NULL; GValue value_arg = G_VALUE_INIT; GIArgument ret; GIArgument child_set_property_args[4]; - base_info = g_irepository_find_by_name(NULL, "Gtk", "Container"); - container_info = (GIObjectInfo*)base_info; + GIRepository* repo = gi_repository_dup_default(); + GIObjectInfo* container_info = + GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "Container")); pspec = gjs_gtk_container_class_find_child_property(container_info, container, property); @@ -212,22 +347,23 @@ void gjs_gtk_container_child_set_property(GObject* container, GObject* child, g_value_copy(value, &value_arg); } - child_set_property_fun = - g_object_info_find_method(container_info, "child_set_property"); + child_set_property_fun = GI_FUNCTION_INFO( + gi_object_info_find_method(container_info, "child_set_property")); child_set_property_args[0].v_pointer = container; child_set_property_args[1].v_pointer = child; child_set_property_args[2].v_string = (char*)property; child_set_property_args[3].v_pointer = &value_arg; - g_function_info_invoke(child_set_property_fun, child_set_property_args, 4, - NULL, 0, &ret, NULL); + gi_function_info_invoke(child_set_property_fun, child_set_property_args, 4, + NULL, 0, &ret, NULL); g_value_unset(&value_arg); out: - g_clear_pointer(&base_info, g_base_info_unref); - g_clear_pointer(&child_set_property_fun, g_base_info_unref); + g_clear_pointer(&container_info, gi_base_info_unref); + g_clear_pointer(&child_set_property_fun, gi_base_info_unref); + g_clear_object(&repo); } /** @@ -281,10 +417,11 @@ void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func, */ GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy) { + GIRepository* repo = gi_repository_dup_default(); GIObjectInfo* container_info = - g_irepository_find_by_name(NULL, "Gtk", "CustomSorter"); - GIBaseInfo* custom_sorter_new_fun = - g_object_info_find_method(container_info, "new"); + GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "CustomSorter")); + GIFunctionInfo* custom_sorter_new_fun = + GI_FUNCTION_INFO(gi_object_info_find_method(container_info, "new")); GIArgument ret; GIArgument custom_sorter_new_args[3]; @@ -292,11 +429,12 @@ GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func, custom_sorter_new_args[1].v_pointer = user_data; custom_sorter_new_args[2].v_pointer = destroy; - g_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3, - NULL, 0, &ret, NULL); + gi_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3, + NULL, 0, &ret, NULL); - g_clear_pointer(&container_info, g_base_info_unref); - g_clear_pointer(&custom_sorter_new_fun, g_base_info_unref); + g_clear_pointer(&container_info, gi_base_info_unref); + g_clear_pointer(&custom_sorter_new_fun, gi_base_info_unref); + g_clear_object(&repo); return (GObject*)ret.v_pointer; } @@ -321,10 +459,11 @@ void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter, GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy) { + GIRepository* repo = gi_repository_dup_default(); GIObjectInfo* container_info = - g_irepository_find_by_name(NULL, "Gtk", "CustomSorter"); - GIBaseInfo* set_sort_func_fun = - g_object_info_find_method(container_info, "set_sort_func"); + GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "CustomSorter")); + GIFunctionInfo* set_sort_func_fun = GI_FUNCTION_INFO( + gi_object_info_find_method(container_info, "set_sort_func")); GIArgument unused_ret; GIArgument set_sort_func_args[4]; @@ -333,13 +472,15 @@ void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter, set_sort_func_args[2].v_pointer = user_data; set_sort_func_args[3].v_pointer = destroy; - g_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0, - &unused_ret, NULL); + gi_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0, + &unused_ret, NULL); - g_clear_pointer(&container_info, g_base_info_unref); - g_clear_pointer(&set_sort_func_fun, g_base_info_unref); + g_clear_pointer(&container_info, gi_base_info_unref); + g_clear_pointer(&set_sort_func_fun, gi_base_info_unref); + g_clear_object(&repo); } +static bool log_writer_cleared = false; static void* log_writer_user_data = NULL; static GDestroyNotify log_writer_user_data_free = NULL; static GThread* log_writer_thread = NULL; @@ -350,9 +491,10 @@ static GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level, void* user_data) { g_assert(log_writer_thread); - // If the wrapper is called from a thread other than the one that set it, + // If the log writer function has been cleared with log_set_writer_default() + // or the wrapper is called from a thread other than the one that set it, // return unhandled so the fallback logger is used. - if (g_thread_self() != log_writer_thread) + if (log_writer_cleared || g_thread_self() != log_writer_thread) return g_log_writer_default(log_level, fields, n_fields, NULL); GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data; @@ -414,10 +556,10 @@ void gjs_log_set_writer_default() { log_writer_user_data_free(log_writer_user_data); } - g_log_set_writer_func(g_log_writer_default, NULL, NULL); log_writer_user_data_free = NULL; log_writer_user_data = NULL; - log_writer_thread = NULL; + log_writer_thread = g_thread_self(); + log_writer_cleared = true; } /** diff --git a/libgjs-private/gjs-util.h b/libgjs-private/gjs-util.h index ef5f4ff8f..5f7cf8592 100644 --- a/libgjs-private/gjs-util.h +++ b/libgjs-private/gjs-util.h @@ -10,7 +10,6 @@ #include #include - #include #include @@ -18,10 +17,6 @@ G_BEGIN_DECLS -/* For imports.format */ -GJS_EXPORT -char * gjs_format_int_alternative_output (int n); - /** * GjsCompareDataFunc: * @a: a value @@ -88,8 +83,8 @@ typedef enum } GjsLocaleCategory; GJS_EXPORT -const char *gjs_setlocale (GjsLocaleCategory category, - const char *locale); +const char* gjs_set_thread_locale(GjsLocaleCategory category, + const char* locale); GJS_EXPORT void gjs_textdomain (const char *domain); GJS_EXPORT @@ -141,7 +136,6 @@ GBinding* gjs_g_object_bind_property_full( GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify); -#if GLIB_CHECK_VERSION(2, 72, 0) /** * gjs_g_binding_group_bind_full: * @source: @@ -163,7 +157,6 @@ void gjs_g_binding_group_bind_full( GjsBindingTransformFunc to_callback, void* to_data, GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify); -#endif /* For imports.overrides.Gtk */ GJS_EXPORT diff --git a/meson.build b/meson.build index aed16db6e..cfee62aa0 100644 --- a/meson.build +++ b/meson.build @@ -2,22 +2,10 @@ # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan -project( - 'cjs', - 'cpp', - 'c', - version : '128.1', - license : ['MIT', 'LGPL2+'], - meson_version : '>= 0.62.0', - default_options : [ - 'cpp_std=c++17', - 'cpp_rtti=false', - 'cpp_eh=none', - 'c_std=c99', - 'warning_level=2', - 'b_pch=true' - ] -) +project('cjs', 'cpp', 'c', version: '139.9', license: ['MIT', 'LGPL2+'], + meson_version: '>= 1.4', + default_options: ['cpp_std=c++17', 'cpp_rtti=false', 'cpp_eh=none', + 'c_std=c99', 'warning_level=2', 'b_pch=true' ]) # cpp_rtti: SpiderMonkey can be compiled with or without runtime type # information, and the default is without. We must match that option because we @@ -35,7 +23,7 @@ bindir = get_option('bindir') libdir = get_option('libdir') datadir = get_option('datadir') libexecdir = get_option('libexecdir') -cjsjsdir = datadir / api_name +gjsjsdir = datadir / api_name pkglibdir = libdir / meson.project_name() installed_tests_execdir = libexecdir / 'installed-tests' / meson.project_name() installed_tests_metadir = datadir / 'installed-tests' / meson.project_name() @@ -60,7 +48,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-128 headers on MSVC + '/Zc:preprocessor', # Required to consume the mozjs headers on MSVC # Ignore spurious compiler warnings for things that GLib and SpiderMonkey # header files commonly do @@ -137,23 +125,44 @@ endif null_dep = dependency('', required : false) # Note: Notify GNOME release team when adding or updating dependencies -glib_required_version = '>= 2.66.0' -glib = dependency('glib-2.0', version: glib_required_version, - fallback: ['glib', 'libglib_dep']) -gthread = dependency('gthread-2.0', version: glib_required_version, - fallback: ['glib', 'libgthread_dep']) -gobject = dependency('gobject-2.0', version: glib_required_version, - fallback: ['glib', 'libgobject_dep']) -gio = dependency('gio-2.0', version: glib_required_version, - fallback: ['glib', 'libgio_dep']) +glib_required_version = '>= 2.86.0' +glib = dependency('glib-2.0', version: glib_required_version, required: false) +gthread = dependency('gthread-2.0', version: glib_required_version, required: false) +gobject = dependency('gobject-2.0', version: glib_required_version, required: false) +gio = dependency('gio-2.0', version: glib_required_version, required: false) +gi = dependency('girepository-2.0', version: glib_required_version, required: false) +gir_deps = [] + +if not glib.found() + glib_project = subproject('glib', required: true, + default_options: ['introspection=enabled']) + + glib = glib_project.get_variable('libglib_dep') + gthread = glib_project.get_variable('libgthread_dep') + gobject = glib_project.get_variable('libgobject_dep') + gio = glib_project.get_variable('libgio_dep') + gi = glib_project.get_variable('libgirepository_dep') + + gir_deps = [ + glib_project.get_variable('glib_gir'), + glib_project.get_variable('gobject_gir'), + glib_project.get_variable('gio_gir'), + glib_project.get_variable('gio_platform_gir'), + ] + + if host_machine.system() == 'windows' + gir_deps += glib_project.get_variable('glib_win32_gir') + else + gir_deps += glib_project.get_variable('glib_unix_gir') + endif +endif + ffi = dependency('libffi', fallback: ['libffi', 'ffi_dep']) -gi = dependency('gobject-introspection-1.0', version: '>= 1.66.0', - fallback: ['gobject-introspection', 'girepo_dep']) cairo = dependency('cairo', fallback: ['cairo', 'libcairo_dep']) cairo_gobject = dependency('cairo-gobject', fallback: ['cairo', 'libcairogobject_dep']) cairo_xlib = dependency('cairo-xlib', required: false) -spidermonkey = dependency('mozjs-128') +spidermonkey = dependency('mozjs-140') sysprof_capture = dependency('sysprof-capture-4', required: get_option('profiler'), include_type: 'system', @@ -237,6 +246,17 @@ endif build_readline = readline.found() +have_gtk3 = dependency('gtk+-3.0', required: false).found() +gtk4_dep = dependency('gtk4', required: false) +have_gtk4 = gtk4_dep.found() + +if (not have_gtk3 and not have_gtk4 and not get_option('skip_gtk_tests')) + error('''You have neither GTK 3 nor 4 available. +GTK is not required, but without at least one of these versions you'll be +skipping a lot of tests. Configure with -Dskip_gtk_tests=true if that's +intentional.''') +endif + ### Check for library features ################################################# # Check if SpiderMonkey was compiled with --enable-debug. If this is the case, @@ -295,16 +315,6 @@ failed to execute. Most likely you should build it with a different configuration.''' + recommended_configuration) endif -have_printf_alternative_int = cc.compiles(''' -#include -int main(void) { - printf("%Id", (int)0); - return 0; -} -''', - args: ['-Werror', '-Wformat'], - name: 'printf() supports %I alternative int syntax') - ### Check for external programs ################################################ dtrace = find_program('dtrace', required: get_option('dtrace')) @@ -318,8 +328,10 @@ header_conf = configuration_data() versions = meson.project_version().split('.') major_version = versions[0].to_int() +minor_version = versions[1].to_int() +int_version = (major_version * 100 + minor_version) * 100 header_conf.set_quoted('VERSION', meson.project_version()) -header_conf.set('GJS_VERSION', major_version, +header_conf.set('GJS_VERSION', int_version, description: 'The GJS version as an integer') header_conf.set_quoted('PACKAGE_STRING', '@0@ @1@'.format(meson.project_name(), meson.project_version())) @@ -332,8 +344,6 @@ header_conf.set('DEBUG', not nondebug_spidermonkey, description: 'SpiderMonkey was compiled with --enable-debug') 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') if build_readline header_conf.set('HAVE_READLINE_READLINE_H', cxx.check_header('readline/readline.h', prefix: '#include ', @@ -357,8 +367,8 @@ if get_option('dtrace') arguments: ['-C', '-h', '-s', '@INPUT@', '-o', '@OUTPUT@']) probes_objfile_gen = generator(dtrace, output: '@BASENAME@.o', arguments: ['-G', '-s', '@INPUT@', '-o', '@OUTPUT@']) - probes_header = probes_header_gen.process('gi/cjs_gi_probes.d') - probes_objfile = probes_objfile_gen.process('gi/cjs_gi_probes.d') + probes_header = probes_header_gen.process('gi/gjs_gi_probes.d') + probes_objfile = probes_objfile_gen.process('gi/gjs_gi_probes.d') else probes_header = [] probes_objfile = [] @@ -374,11 +384,11 @@ endif ### Build library ############################################################## directory_defines = [ - '-DGJS_JS_DIR="@0@"'.format(prefix / cjsjsdir), + '-DGJS_JS_DIR="@0@"'.format(prefix / gjsjsdir), '-DPKGLIBDIR="@0@"'.format(prefix / pkglibdir), ] -cjs_public_headers = [ +gjs_public_headers = [ 'cjs/context.h', 'cjs/coverage.h', 'cjs/error-types.h', @@ -391,7 +401,7 @@ cjs_public_headers = [ # For historical reasons, some files live in gi/ # Some headers in the following list were formerly public -libcjs_sources = [ +libgjs_sources = [ 'gi/arg.cpp', 'gi/arg.h', 'gi/arg-inl.h', 'gi/arg-cache.cpp', 'gi/arg-cache.h', 'gi/boxed.cpp', 'gi/boxed.h', @@ -402,9 +412,10 @@ libcjs_sources = [ 'gi/fundamental.cpp', 'gi/fundamental.h', 'gi/function.cpp', 'gi/function.h', 'gi/gerror.cpp', 'gi/gerror.h', - 'gi/cjs_gi_trace.h', + 'gi/gjs_gi_trace.h', 'gi/gobject.cpp', 'gi/gobject.h', 'gi/gtype.cpp', 'gi/gtype.h', + 'gi/info.h', 'gi/interface.cpp', 'gi/interface.h', 'gi/ns.cpp', 'gi/ns.h', 'gi/object.cpp', 'gi/object.h', @@ -417,6 +428,7 @@ libcjs_sources = [ 'gi/value.cpp', 'gi/value.h', 'gi/wrapperutils.cpp', 'gi/wrapperutils.h', 'cjs/atoms.cpp', 'cjs/atoms.h', + 'cjs/auto.h', 'cjs/byteArray.cpp', 'cjs/byteArray.h', 'cjs/context.cpp', 'cjs/context-private.h', 'cjs/coverage.cpp', @@ -424,6 +436,7 @@ libcjs_sources = [ 'cjs/deprecation.cpp', 'cjs/deprecation.h', 'cjs/engine.cpp', 'cjs/engine.h', 'cjs/error-types.cpp', + 'cjs/gerror-result.h', 'cjs/global.cpp', 'cjs/global.h', 'cjs/importer.cpp', 'cjs/importer.h', 'cjs/internal.cpp', 'cjs/internal.h', @@ -458,14 +471,14 @@ libcjs_sources = [ 'modules/cairo.cpp', ] -# CjsPrivate introspection sources -libcjs_private_sources = [ +# GjsPrivate introspection sources +libgjs_private_sources = [ 'libgjs-private/gjs-gdbus-wrapper.c', 'libgjs-private/gjs-gdbus-wrapper.h', 'libgjs-private/gjs-match-info.c', 'libgjs-private/gjs-match-info.h', 'libgjs-private/gjs-util.c', 'libgjs-private/gjs-util.h', ] -libcjs_jsapi_sources = [ +libgjs_jsapi_sources = [ 'cjs/jsapi-class.h', 'cjs/jsapi-dynamic-class.cpp', 'cjs/jsapi-util-args.h', @@ -484,36 +497,38 @@ module_resource_srcs = gnome.compile_resources('js-resources', module_resource_lib = static_library('js-resources', module_resource_srcs, dependencies: gio, override_options: ['unity=off']) -libcjs_dependencies = [glib, gobject, gthread, gio, gi, ffi, cairo, +libgjs_dependencies = [glib, gobject, gthread, gio, gi, ffi, cairo, cairo_gobject, spidermonkey, readline, libatomic] pkg_dependencies = [glib, gobject, gthread, gio, gi, ffi, cairo, cairo_gobject, spidermonkey] if cairo_xlib.found() - libcjs_dependencies += cairo_xlib + libgjs_dependencies += cairo_xlib pkg_dependencies += cairo_xlib endif if build_readline - libcjs_dependencies += readline_deps + gio_unix = dependency('gio-unix-2.0', version: glib_required_version, + fallback: ['glib', 'libgiounix_dep']) + libgjs_dependencies += [readline_deps, gio_unix] endif -libcjs_cpp_args = ['-DGJS_COMPILATION'] + directory_defines +libgjs_cpp_args = ['-DGJS_COMPILATION'] + directory_defines # Check G-I and/or Meson on this one. -libcjs_cpp_args += ['-DG_LOG_DOMAIN="Cjs"'] +libgjs_cpp_args += ['-DG_LOG_DOMAIN="Gjs"'] if host_machine.system() == 'windows' # We need these defines to build properly for all Windows builds - libcjs_cpp_args += ['-DWIN32', '-DXP_WIN', '-DWIN32_LEAN_AND_MEAN'] + libgjs_cpp_args += ['-DWIN32', '-DXP_WIN', '-DWIN32_LEAN_AND_MEAN'] endif -# This dependency should provide everything that is needed to compile cjs except +# This dependency should provide everything that is needed to compile gjs except # the sources themselves, is used to compile both the static libraries and the # tests base_build_dep = declare_dependency( - compile_args: libcjs_cpp_args, - dependencies: libcjs_dependencies) + compile_args: libgjs_cpp_args, + dependencies: libgjs_dependencies) internal_build_dep = declare_dependency( compile_args: (release_build ? ['-DG_DISABLE_ASSERT'] : []), @@ -522,8 +537,8 @@ internal_build_dep = declare_dependency( build_profiler ? profiler_deps : [], ]) -libcjs_jsapi = static_library(meson.project_name() + '-jsapi', - libcjs_jsapi_sources, probes_header, probes_objfile, +libgjs_jsapi = static_library(meson.project_name() + '-jsapi', + libgjs_jsapi_sources, probes_header, probes_objfile, cpp_pch: 'cjs/cjs_pch.hh', dependencies: internal_build_dep, install: false) @@ -531,59 +546,57 @@ libcjs_jsapi = static_library(meson.project_name() + '-jsapi', # We need to create an internal static library to be able to link with the tests # that may use internal APIs. This is also used to generate the actual shared # library so that we compile its sources just once. -libcjs_internal = static_library('cjs-internal', - libcjs_sources, probes_header, probes_objfile, +libgjs_internal = static_library('gjs-internal', + libgjs_sources, probes_header, probes_objfile, cpp_pch: 'cjs/cjs_pch.hh', dependencies: internal_build_dep, - link_with: libcjs_jsapi) + link_with: libgjs_jsapi) link_args = [] -symbol_map = 'libcjs.map' -symbol_list = 'libcjs.symbols' +symbol_map = files('libcjs.map') +symbol_list = files('libcjs.symbols') # macOS linker link_args += cxx.get_supported_link_arguments([ - '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), - symbol_map), - '-Wl,-exported_symbols_list,@0@/@1@'.format(meson.current_source_dir(), - symbol_list), # macOS linker + '-Wl,--version-script,@0@'.format(symbol_map[0].full_path()), + '-Wl,-exported_symbols_list,@0@'.format(symbol_list[0].full_path()), ]) -libcjs = shared_library(meson.project_name(), - sources: libcjs_private_sources, +libgjs = shared_library(meson.project_name(), + sources: libgjs_private_sources, link_args: link_args, link_depends: [symbol_map, symbol_list], - link_whole: [libcjs_internal, module_resource_lib], + link_whole: [libgjs_internal, module_resource_lib], dependencies: base_build_dep, version: '0.0.0', soversion: '0', gnu_symbol_visibility: 'hidden', install: true) -install_headers(cjs_public_headers, subdir: api_name / 'cjs') +install_headers(gjs_public_headers, subdir: api_name / 'cjs') -# Allow using libcjs as a subproject -libcjs_dep = declare_dependency(link_with: [libcjs, libcjs_jsapi], +# Allow using libgjs as a subproject +libgjs_dep = declare_dependency(link_with: [libgjs, libgjs_jsapi], dependencies: base_build_dep, include_directories: top_include) -### Build CjsPrivate introspection library ##################################### +### Build GjsPrivate introspection library ##################################### -cjs_private_gir = gnome.generate_gir(libcjs, - includes: ['GObject-2.0', 'Gio-2.0'], sources: libcjs_private_sources, - namespace: 'CjsPrivate', nsversion: '1.0', identifier_prefix: 'Gjs', +gjs_private_gir = gnome.generate_gir(libgjs, + includes: ['GObject-2.0', 'Gio-2.0'], sources: libgjs_private_sources, + namespace: 'GjsPrivate', nsversion: '1.0', identifier_prefix: 'Gjs', symbol_prefix: 'gjs_', fatal_warnings: get_option('werror'), install: true, install_gir: false, install_dir_typelib: pkglibdir / 'girepository-1.0') -cjs_private_typelib = cjs_private_gir[1] +gjs_private_typelib = gjs_private_gir[1] ### Build cjs-console interpreter ############################################## -cjs_console_srcs = ['cjs/console.cpp'] +gjs_console_srcs = ['cjs/console.cpp'] -cjs_console = executable('cjs-console', cjs_console_srcs, - dependencies: libcjs_dep, install: true) +gjs_console = executable('cjs-console', gjs_console_srcs, + dependencies: libgjs_dep, install: true) -meson.add_install_script('build/symlink-cjs.py', bindir) +meson.add_install_script('build/symlink-gjs.py', bindir) ### Install data files ######################################################### -install_data('installed-tests/extra/gjs.supp', install_dir: cjsjsdir / 'valgrind') -install_data('installed-tests/extra/lsan.supp', install_dir: cjsjsdir / 'lsan') +install_data('installed-tests/extra/gjs.supp', install_dir: gjsjsdir / 'valgrind') +install_data('installed-tests/extra/lsan.supp', install_dir: gjsjsdir / 'lsan') if get_option('installed_tests') schemadir = datadir / 'glib-2.0' / 'schemas' @@ -593,21 +606,33 @@ endif ### Generate pkg-config file ################################################### -pkg.generate(libcjs, name: api_name, description: 'JS bindings for GObjects', - requires: [glib, gobject, gio], requires_private: pkg_dependencies, +requires_private_names = ['gthread-2.0', 'girepository-2.0', 'libffi', 'cairo', + 'cairo-gobject', 'mozjs-140'] +if pkg_dependencies.contains(cairo_xlib) + requires_private_names += 'cairo-xlib' +endif +pkg.generate(libgjs, name: api_name, description: 'JS bindings for GObjects', + requires: ['glib-2.0', 'gobject-2.0', 'gio-2.0'], + requires_private: requires_private_names, subdirs: api_name, variables: [ 'exec_prefix=${prefix}', 'datarootdir=${datadir}', - 'cjs_console=${bindir}/cjs-console', + 'gjs_console=${bindir}/cjs-console', + 'mozjs_dep_name=@0@'.format(spidermonkey.name()), ]) ### Test environment ########################################################### tests_environment = environment() gi_tests_builddir = meson.project_build_root() / 'subprojects' / 'gobject-introspection-tests' +glib_root_builddir = meson.project_build_root() / 'subprojects' / 'glib' +glib_builddir = glib_root_builddir / 'glib' +gobject_builddir = glib_root_builddir / 'gobject' +gi_builddir = glib_root_builddir / 'girepository' +gio_builddir = glib_root_builddir / 'gio' js_tests_builddir = meson.current_build_dir() / 'installed-tests' / 'js' -libcjs_test_tools_builddir = js_tests_builddir / 'libgjstesttools' +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()) @@ -615,26 +640,35 @@ tests_environment.set('GJS_USE_UNINSTALLED_FILES', '1') tests_environment.set('GJS_PATH', '') tests_environment.set('GJS_DEBUG_OUTPUT', 'stderr') tests_environment.prepend('GI_TYPELIB_PATH', meson.current_build_dir(), - gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) + gi_tests_builddir, js_tests_builddir, libgjs_test_tools_builddir, + gi_builddir / 'introspection') tests_environment.prepend('LD_LIBRARY_PATH', meson.current_build_dir(), - gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) + gi_tests_builddir, js_tests_builddir, libgjs_test_tools_builddir, + glib_builddir, gobject_builddir, gio_builddir, gi_builddir) tests_environment.prepend('DYLD_LIBRARY_PATH', meson.current_build_dir(), - gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) + gi_tests_builddir, js_tests_builddir, libgjs_test_tools_builddir) tests_environment.set('G_FILENAME_ENCODING', 'latin1') # Workaround for https://github.com/google/sanitizers/issues/1322 tests_environment.set('ASAN_OPTIONS', 'intercept_tls_get_addr=0') +lsan_suppressions = files('installed-tests/extra/lsan.supp') tests_environment.set('LSAN_OPTIONS', 'fast_unwind_on_malloc=0,exitcode=23,suppressions=@0@'.format( - meson.current_source_dir() / 'installed-tests' / 'extra' / 'lsan.supp')) + lsan_suppressions[0].full_path())) +tsan_suppressions = files('installed-tests/extra/tsan.supp') 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')) + tsan_suppressions[0].full_path())) tests_environment.set('G_SLICE', 'always-malloc') tests_environment.set('NO_AT_BRIDGE', '1') +tests_environment.set('GTK_A11Y', 'none') tests_environment.set('GSETTINGS_SCHEMA_DIR', js_tests_builddir) tests_environment.set('GSETTINGS_BACKEND', 'memory') tests_environment.set('G_DEBUG', 'fatal-warnings,fatal-criticals') +# Vulkan renderer may not be compatible in all environments e.g CI, use opengl instead +tests_environment.set('GSK_RENDERER', + gtk4_dep.version().version_compare('>= 4.19.4') ? 'gl' : 'ngl') + tests_locale = 'N/A' if cxx.get_argument_syntax() != 'msvc' result = run_command('build/choose-tests-locale.sh', check: false) @@ -678,11 +712,10 @@ valgrind_environment.set('VALGRIND', 'valgrind') glib_prefix = glib.get_variable(pkgconfig: 'prefix', default_value: '/usr') glib_suppresssions = (glib_prefix / 'share' / 'glib-2.0' / 'valgrind' / 'glib.supp') -cjs_suppressions = (meson.current_source_dir() / 'installed-tests' / 'extra' / - 'cjs.supp') +gjs_suppressions = files('installed-tests/extra/gjs.supp') valgrind_args = [ '--suppressions=@0@'.format(glib_suppresssions), - '--suppressions=@0@'.format(cjs_suppressions), + '--suppressions=@0@'.format(gjs_suppressions[0].full_path()), '--leak-check=full', '--num-callers=15', '--trace-children=yes', @@ -724,6 +757,10 @@ endif if get_option('skip_gtk_tests') warning('Not using GTK, not all tests will be run.') +elif not have_gtk3 + warning('Not using GTK 3, not all tests will be run.') +elif not have_gtk4 + warning('Not using GTK 4, not all tests will be run.') endif if get_option('skip_dbus_tests') @@ -740,7 +777,7 @@ summary({ 'libexecdir': prefix / libexecdir, }, section: 'Directories') locations = [] -foreach dep: [ffi, glib, gi, spidermonkey, readline, sysprof_capture] +foreach dep: [ffi, glib, spidermonkey, readline, sysprof_capture] if dep.type_name() == 'pkgconfig' locations += 'in @0@'.format(dep.get_variable(pkgconfig: 'prefix')) else @@ -750,16 +787,15 @@ endforeach summary({ 'libffi': '@0@ (@1@)'.format(ffi.version(), locations[0]), 'GLib': '@0@ (@1@)'.format(glib.version(), locations[1]), - 'GObject introspection': '@0@ (@1@)'.format(gi.version(), locations[2]), 'SpiderMonkey': '@0@ (@1@, @2@ build)'.format(spidermonkey.version(), - locations[3], nondebug_spidermonkey ? 'release' : 'debug'), + locations[2], nondebug_spidermonkey ? 'release' : 'debug'), }, section: 'Dependencies') if build_readline - summary('Readline', '(@0@)'.format(locations[4]), section: 'Dependencies') + summary('Readline', '(@0@)'.format(locations[3]), section: 'Dependencies') endif if build_profiler summary('Sysprof', - '@0@ (@1@)'.format(sysprof_capture.version(), locations[5]), + '@0@ (@1@)'.format(sysprof_capture.version(), locations[4]), section: 'Dependencies') endif summary({ @@ -784,7 +820,5 @@ meson.add_devenv({'GJS_USE_UNINSTALLED_FILES': '1'}) ### Maintainer scripts ######################################################### -run_target('maintainer-upload-release', - command: ['build/maintainer-upload-release.sh', - meson.project_name(), - meson.project_version()]) +run_target('maintainer-tag-release', + command: ['build/maintainer-tag-release.sh', meson.project_version()]) diff --git a/modules/cairo-context.cpp b/modules/cairo-context.cpp index 05a031819..0497381b4 100644 --- a/modules/cairo-context.cpp +++ b/modules/cairo-context.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include // for JS::NewArrayObject @@ -27,6 +27,7 @@ #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/foreign.h" +#include "cjs/auto.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" @@ -922,19 +923,19 @@ const JSFunctionSpec CairoContext::proto_funcs[] = { JS_FS_END}; // clang-format on -[[nodiscard]] static bool context_to_gi_argument( +GJS_JSAPI_RETURN_CONVENTION static bool context_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, argument_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(context, "%s may not be null", display_name.get()); return false; } - gjs_arg_unset(arg); + gjs_arg_unset(arg); return true; } diff --git a/modules/cairo-image-surface.cpp b/modules/cairo-image-surface.cpp index 89f3d1ab3..61fc3c3d3 100644 --- a/modules/cairo-image-surface.cpp +++ b/modules/cairo-image-surface.cpp @@ -14,6 +14,7 @@ #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey +#include "cjs/auto.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -57,7 +58,7 @@ createFromPNG_func(JSContext *context, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); - GjsAutoChar filename; + Gjs::AutoChar filename; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "createFromPNG", argv, "F", diff --git a/modules/cairo-path.cpp b/modules/cairo-path.cpp index b6b0726c6..d940cf326 100644 --- a/modules/cairo-path.cpp +++ b/modules/cairo-path.cpp @@ -13,6 +13,14 @@ #include #include // for JS_NewObjectWithGivenProto +#include "gi/arg-inl.h" +#include "gi/arg.h" +#include "gi/cwrapper.h" +#include "gi/foreign.h" +#include "cjs/auto.h" +#include "cjs/enum-utils.h" +#include "cjs/jsapi-util.h" +#include "cjs/macros.h" #include "modules/cairo-private.h" // clang-format off @@ -24,7 +32,7 @@ const JSPropertySpec CairoPath::proto_props[] = { /* * CairoPath::take_c_ptr(): * Same as CWrapper::from_c_ptr(), but always takes ownership of the pointer - * rather than copying it. It's not possible to copy a cairo_path_t*. + * rather than copying it. */ JSObject* CairoPath::take_c_ptr(JSContext* cx, cairo_path_t* ptr) { JS::RootedObject proto(cx, CairoPath::prototype(cx)); @@ -48,3 +56,74 @@ void CairoPath::finalize_impl(JS::GCContext*, cairo_path_t* path) { return; cairo_path_destroy(path); } + +GJS_JSAPI_RETURN_CONVENTION static bool path_to_gi_argument( + JSContext* cx, JS::Value value, const char* arg_name, + GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, + GIArgument* arg) { + if (value.isNull()) { + if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; + gjs_throw(cx, "%s may not be null", display_name.get()); + return false; + } + + gjs_arg_unset(arg); + return true; + } + + if (!value.isObject()) { + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; + gjs_throw(cx, "%s is not a Cairo.Path", display_name.get()); + return false; + } + + JS::RootedObject path_wrapper{cx, &value.toObject()}; + cairo_path_t* s = CairoPath::for_js(cx, path_wrapper); + if (!s) + return false; + if (transfer == GI_TRANSFER_EVERYTHING) + s = CairoPath::copy_ptr(s); + + gjs_arg_set(arg, s); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool path_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, + GIArgument* arg) { + JSObject* obj = CairoPath::from_c_ptr(cx, gjs_arg_get(arg)); + if (!obj) + return false; + + value_p.setObject(*obj); + return true; +} + +static bool path_release_argument(JSContext*, GITransfer transfer, + GIArgument* arg) { + if (transfer != GI_TRANSFER_NOTHING) + cairo_path_destroy(gjs_arg_get(arg)); + return true; +} + +void gjs_cairo_path_init(void) { + static GjsForeignInfo foreign_info = { + path_to_gi_argument, path_from_gi_argument, path_release_argument}; + gjs_struct_foreign_register("cairo", "Path", &foreign_info); +} + +// Adapted from PyGObject cairo code +cairo_path_t* CairoPath::copy_ptr(cairo_path_t* path) { + cairo_surface_t* surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t* cr = cairo_create(surface); + cairo_append_path(cr, path); + cairo_path_t* copy = cairo_copy_path(cr); + cairo_destroy(cr); + cairo_surface_destroy(surface); + + return copy; +} diff --git a/modules/cairo-pattern.cpp b/modules/cairo-pattern.cpp index 6922c5308..41d79ea5e 100644 --- a/modules/cairo-pattern.cpp +++ b/modules/cairo-pattern.cpp @@ -15,6 +15,12 @@ #include #include +#include "gi/arg-inl.h" +#include "gi/arg.h" +#include "gi/cwrapper.h" +#include "gi/foreign.h" +#include "cjs/auto.h" +#include "cjs/enum-utils.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" @@ -138,3 +144,64 @@ cairo_pattern_t* CairoPattern::for_js(JSContext* cx, return JS::GetMaybePtrFromReservedSlot( pattern_wrapper, CairoPattern::POINTER); } + +GJS_JSAPI_RETURN_CONVENTION static bool pattern_to_gi_argument( + JSContext* context, JS::Value value, const char* arg_name, + GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, + GIArgument* arg) { + if (value.isNull()) { + if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; + gjs_throw(context, "%s may not be null", display_name.get()); + return false; + } + + gjs_arg_unset(arg); + return true; + } + + if (!value.isObject()) { + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; + gjs_throw(context, "%s is not a Cairo.Pattern", display_name.get()); + return false; + } + + JS::RootedObject pattern_wrapper{context, &value.toObject()}; + cairo_pattern_t* s = CairoPattern::for_js(context, pattern_wrapper); + if (!s) + return false; + if (transfer == GI_TRANSFER_EVERYTHING) + cairo_pattern_reference(s); + + gjs_arg_set(arg, s); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool pattern_from_gi_argument(JSContext* cx, + JS::MutableHandleValue value_p, + GIArgument* arg) { + JSObject* obj = + CairoPattern::from_c_ptr(cx, gjs_arg_get(arg)); + if (!obj) + return false; + + value_p.setObject(*obj); + return true; +} + +static bool pattern_release_argument(JSContext*, GITransfer transfer, + GIArgument* arg) { + if (transfer != GI_TRANSFER_NOTHING) + cairo_pattern_destroy(gjs_arg_get(arg)); + return true; +} + +void gjs_cairo_pattern_init(void) { + static GjsForeignInfo foreign_info = {pattern_to_gi_argument, + pattern_from_gi_argument, + pattern_release_argument}; + gjs_struct_foreign_register("cairo", "Pattern", &foreign_info); +} diff --git a/modules/cairo-pdf-surface.cpp b/modules/cairo-pdf-surface.cpp index b0070d0b9..7b8fd1df8 100644 --- a/modules/cairo-pdf-surface.cpp +++ b/modules/cairo-pdf-surface.cpp @@ -20,11 +20,8 @@ # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey -#endif - -#include "cjs/jsapi-util.h" -#if CAIRO_HAS_PDF_SURFACE +# include "cjs/auto.h" # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" @@ -39,7 +36,7 @@ JSObject* CairoPDFSurface::new_proto(JSContext* cx, JSProtoKey) { cairo_surface_t* CairoPDFSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { - GjsAutoChar filename; + Gjs::AutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "PDFSurface", argv, "Fff", diff --git a/modules/cairo-private.h b/modules/cairo-private.h index 34260eb5c..ac8e9f504 100644 --- a/modules/cairo-private.h +++ b/modules/cairo-private.h @@ -120,7 +120,7 @@ void gjs_cairo_context_init(void); void gjs_cairo_surface_init(void); /* path */ - +void gjs_cairo_path_init(); class CairoPath : public CWrapper { friend CWrapperPointerOps; friend CWrapper; @@ -150,6 +150,7 @@ class CairoPath : public CWrapper { &CairoPath::class_ops, &CairoPath::class_spec}; public: + static cairo_path_t* copy_ptr(cairo_path_t* path); GJS_JSAPI_RETURN_CONVENTION static JSObject* take_c_ptr(JSContext* cx, cairo_path_t* ptr); }; @@ -387,6 +388,7 @@ class CairoSVGSurface { #endif // CAIRO_HAS_SVG_SURFACE /* pattern */ +void gjs_cairo_pattern_init(); class CairoPattern : public CWrapper { friend CWrapperPointerOps; diff --git a/modules/cairo-ps-surface.cpp b/modules/cairo-ps-surface.cpp index a1f396b3f..d17d3c162 100644 --- a/modules/cairo-ps-surface.cpp +++ b/modules/cairo-ps-surface.cpp @@ -20,11 +20,8 @@ # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey -#endif - -#include "cjs/jsapi-util.h" -#if CAIRO_HAS_PS_SURFACE +# include "cjs/auto.h" # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" @@ -39,7 +36,7 @@ JSObject* CairoPSSurface::new_proto(JSContext* cx, JSProtoKey) { cairo_surface_t* CairoPSSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { - GjsAutoChar filename; + Gjs::AutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "PSSurface", argv, "Fff", diff --git a/modules/cairo-region.cpp b/modules/cairo-region.cpp index 570887985..74d4f4433 100644 --- a/modules/cairo-region.cpp +++ b/modules/cairo-region.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -21,6 +21,7 @@ #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util-args.h" @@ -227,19 +228,19 @@ void CairoRegion::finalize_impl(JS::GCContext*, cairo_region_t* region) { cairo_region_destroy(region); } -[[nodiscard]] static bool region_to_gi_argument( +GJS_JSAPI_RETURN_CONVENTION static bool region_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, argument_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(context, "%s may not be null", display_name.get()); return false; } - gjs_arg_unset(arg); + gjs_arg_unset(arg); return true; } @@ -249,7 +250,7 @@ void CairoRegion::finalize_impl(JS::GCContext*, cairo_region_t* region) { if (!CairoRegion::for_js_typecheck(context, obj, ®ion)) return false; if (transfer == GI_TRANSFER_EVERYTHING) - cairo_region_destroy(region); + cairo_region_reference(region); gjs_arg_set(arg, region); return true; diff --git a/modules/cairo-surface.cpp b/modules/cairo-surface.cpp index 5369d3b5d..1de71f3be 100644 --- a/modules/cairo-surface.cpp +++ b/modules/cairo-surface.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -23,6 +23,7 @@ #include "gi/arg.h" #include "gi/cwrapper.h" #include "gi/foreign.h" +#include "cjs/auto.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" @@ -45,7 +46,7 @@ writeToPNG_func(JSContext *context, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); - GjsAutoChar filename; + Gjs::AutoChar filename; if (!gjs_parse_call_args(context, "writeToPNG", argv, "F", "filename", &filename)) @@ -324,25 +325,25 @@ cairo_surface_t* CairoSurface::for_js(JSContext* cx, surface_wrapper, CairoSurface::POINTER); } -[[nodiscard]] static bool surface_to_gi_argument( +GJS_JSAPI_RETURN_CONVENTION static bool surface_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, argument_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(context, "%s may not be null", display_name.get()); return false; } - gjs_arg_unset(arg); + gjs_arg_unset(arg); return true; } if (!value.isObject()) { - GjsAutoChar display_name = - gjs_argument_display_name(arg_name, argument_type); + Gjs::AutoChar display_name{ + gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(context, "%s is not a Cairo.Surface", display_name.get()); return false; } @@ -352,7 +353,7 @@ cairo_surface_t* CairoSurface::for_js(JSContext* cx, if (!s) return false; if (transfer == GI_TRANSFER_EVERYTHING) - cairo_surface_destroy(s); + cairo_surface_reference(s); gjs_arg_set(arg, s); return true; diff --git a/modules/cairo-svg-surface.cpp b/modules/cairo-svg-surface.cpp index d8ccedf56..d082d1ebf 100644 --- a/modules/cairo-svg-surface.cpp +++ b/modules/cairo-svg-surface.cpp @@ -20,11 +20,8 @@ # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey -#endif - -#include "cjs/jsapi-util.h" -#if CAIRO_HAS_SVG_SURFACE +# include "cjs/auto.h" # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" @@ -39,7 +36,7 @@ JSObject* CairoSVGSurface::new_proto(JSContext* cx, JSProtoKey) { cairo_surface_t* CairoSVGSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { - GjsAutoChar filename; + Gjs::AutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "SVGSurface", argv, "Fff", diff --git a/modules/cairo.cpp b/modules/cairo.cpp index 482d141fc..8fcdea43c 100644 --- a/modules/cairo.cpp +++ b/modules/cairo.cpp @@ -66,8 +66,15 @@ gjs_js_define_cairo_stuff(JSContext *context, return false; gjs_cairo_surface_init(); + if (!CairoPattern::create_prototype(context, module)) + return false; + gjs_cairo_pattern_init(); + + if (!CairoPath::create_prototype(context, module)) + return false; + gjs_cairo_path_init(); + return CairoImageSurface::create_prototype(context, module) && - CairoPath::create_prototype(context, module) && #if CAIRO_HAS_PS_SURFACE CairoPSSurface::create_prototype(context, module) && #endif @@ -77,7 +84,6 @@ gjs_js_define_cairo_stuff(JSContext *context, #if CAIRO_HAS_SVG_SURFACE CairoSVGSurface::create_prototype(context, module) && #endif - CairoPattern::create_prototype(context, module) && CairoGradient::create_prototype(context, module) && CairoLinearGradient::create_prototype(context, module) && CairoRadialGradient::create_prototype(context, module) && diff --git a/modules/console.cpp b/modules/console.cpp index 860254c3c..3760dffd0 100644 --- a/modules/console.cpp +++ b/modules/console.cpp @@ -27,6 +27,10 @@ #include #include // for g_fprintf +#ifdef HAVE_READLINE_READLINE_H +# include +#endif + #include #include #include // for JS_EncodeStringToUTF8 @@ -44,18 +48,23 @@ #include #include #include // for JS_NewPlainObject +#include #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/console.h" +#include "util/console.h" namespace mozilla { union Utf8Unit; } +using mozilla::Maybe, mozilla::Nothing, mozilla::Some; + static void gjs_console_warning_reporter(JSContext*, JSErrorReport* report) { JS::PrintError(stderr, report, /* reportWarnings = */ true); } @@ -92,7 +101,7 @@ class AutoReportException { if (!stack_str) { g_printerr("(Unable to print stack trace)\n"); } else { - GjsAutoChar encoded_stack_str{g_filename_from_utf8( + Gjs::AutoChar encoded_stack_str{g_filename_from_utf8( stack_str.get(), -1, nullptr, nullptr, nullptr)}; if (!encoded_stack_str) g_printerr("(Unable to print stack trace)\n"); @@ -140,23 +149,72 @@ class AutoCatchCtrlC { sigjmp_buf AutoCatchCtrlC::jump_buffer; #endif // HAVE_SIGNAL_H -[[nodiscard]] static bool gjs_console_readline(char** bufp, - const char* prompt) { #ifdef HAVE_READLINE_READLINE_H - char *line; - line = readline(prompt); - if (!line) +// Readline only has a global handler anyway, so may as well use global data +static Maybe rl_async_line{}; +static bool rl_async_done = true; + +static gboolean on_stdin_ready(GPollableInputStream* pollable, void*) { + while (g_pollable_input_stream_is_readable(pollable)) + rl_callback_read_char(); + return TRUE; // don't remove source +} + +static void on_readline_complete(char* line) { + rl_async_line = line ? Some(line) : Nothing(); + rl_async_done = true; + // This needs to be called from the callback handler, otherwise an extra + // prompt is displayed. + rl_callback_handler_remove(); +} +#endif + +[[nodiscard]] +static bool gjs_console_readline(std::string* bufp, const char* prompt, + const char* repl_history_path + [[maybe_unused]]) { +#ifdef HAVE_READLINE_READLINE_H + g_assert(rl_async_done && "should not attempt two parallel readline calls"); + + rl_callback_handler_install(prompt, on_readline_complete); + rl_async_done = false; + + Gjs::AutoUnref stdin_stream{ + g_unix_input_stream_new(fileno(rl_instream), /* close = */ false)}; + Gjs::AutoUnref stdin_source{g_pollable_input_stream_create_source( + G_POLLABLE_INPUT_STREAM(stdin_stream.get()), nullptr)}; + g_source_set_callback(stdin_source, G_SOURCE_FUNC(on_stdin_ready), nullptr, + nullptr); + + Gjs::AutoPointer + main_context{g_main_context_ref_thread_default()}; + unsigned tag = g_source_attach(stdin_source, main_context); + stdin_source.release(); + + while (!rl_async_done) { + // Yield to other things while waiting for input + while (g_main_context_pending(main_context)) + g_main_context_iteration(main_context, /* may_block = */ false); + } + + g_source_remove(tag); + + if (!rl_async_line) return false; - if (line[0] != '\0') - add_history(line); - *bufp = line; + + *bufp = rl_async_line.extract(); + + if ((*bufp)[0] != '\0') { + add_history(bufp->c_str()); + gjs_console_write_repl_history(repl_history_path); + } #else // !HAVE_READLINE_READLINE_H char line[256]; fprintf(stdout, "%s", prompt); fflush(stdout); if (!fgets(line, sizeof line, stdin)) return false; - *bufp = g_strdup(line); + *bufp = line; #endif // !HAVE_READLINE_READLINE_H return true; } @@ -229,9 +287,9 @@ gjs_console_interact(JSContext *context, JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); volatile bool eof, exit_warning; // accessed after setjmp() JS::RootedObject global{context, JS::CurrentGlobalOrNull(context)}; - char* temp_buf; volatile int lineno; // accessed after setjmp() volatile int startline; // accessed after setjmp() + GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); #ifndef HAVE_READLINE_READLINE_H int rl_end = 0; // nonzero if using readline and any text is typed in @@ -244,7 +302,6 @@ gjs_console_interact(JSContext *context, // Separate initialization from declaration because of possible overwriting // when siglongjmp() jumps into this function eof = exit_warning = false; - temp_buf = nullptr; lineno = 1; do { /* @@ -279,14 +336,15 @@ gjs_console_interact(JSContext *context, } #endif // HAVE_SIGNAL_H - if (!gjs_console_readline( - &temp_buf, startline == lineno ? "gjs> " : ".... ")) { + std::string temp_buf; + if (!gjs_console_readline(&temp_buf, + startline == lineno ? "gjs> " : ".... ", + gjs->repl_history_path())) { eof = true; break; } buffer += temp_buf; buffer += "\n"; - g_free(temp_buf); lineno++; } while (!JS_Utf8BufferIsCompilableUnit(context, global, buffer.c_str(), buffer.size())); @@ -298,7 +356,6 @@ gjs_console_interact(JSContext *context, } exit_warning = false; - GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); ok = gjs->run_jobs_fallible() && ok; if (!ok) { diff --git a/modules/core/.eslintrc.yml b/modules/core/.eslintrc.yml deleted file mode 100644 index 6c9c0253c..000000000 --- a/modules/core/.eslintrc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - jsdoc/require-jsdoc: 'off' diff --git a/modules/core/_format.js b/modules/core/_format.js index 9444c0a61..38340d70f 100644 --- a/modules/core/_format.js +++ b/modules/core/_format.js @@ -5,7 +5,7 @@ /* exported vprintf */ -const CjsPrivate = imports.gi.CjsPrivate; +let numberFormatter = null; function vprintf(string, args) { let i = 0; @@ -47,10 +47,12 @@ function vprintf(string, args) { break; case 'd': { let intV = parseInt(getArg()); - if (hasAlternativeIntFlag) - s = CjsPrivate.format_int_alternative_output(intV); - else + if (hasAlternativeIntFlag) { + numberFormatter ??= new Intl.NumberFormat(); + s = numberFormatter.format(intV); + } else { s = intV.toString(); + } break; } case 'x': diff --git a/modules/core/_gettext.js b/modules/core/_gettext.js index ff18d669d..e5400b125 100644 --- a/modules/core/_gettext.js +++ b/modules/core/_gettext.js @@ -19,19 +19,19 @@ gettext, LocaleCategory, ngettext, pgettext, setlocale, textdomain */ */ const GLib = imports.gi.GLib; -const CjsPrivate = imports.gi.CjsPrivate; +const GjsPrivate = imports.gi.GjsPrivate; -var LocaleCategory = CjsPrivate.LocaleCategory; +var LocaleCategory = GjsPrivate.LocaleCategory; function setlocale(category, locale) { - return CjsPrivate.setlocale(category, locale); + return GjsPrivate.set_thread_locale(category, locale); } function textdomain(dom) { - return CjsPrivate.textdomain(dom); + return GjsPrivate.textdomain(dom); } function bindtextdomain(dom, location) { - return CjsPrivate.bindtextdomain(dom, location); + return GjsPrivate.bindtextdomain(dom, location); } function gettext(msgid) { diff --git a/modules/core/overrides/.eslintrc.yml b/modules/core/overrides/.eslintrc.yml deleted file mode 100644 index 8a5f8fd93..000000000 --- a/modules/core/overrides/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2018 Philip Chimento -rules: - no-unused-vars: - - error - - varsIgnorePattern: ^_init$ diff --git a/modules/core/overrides/GLib.js b/modules/core/overrides/GLib.js index 90dba115a..fa509217e 100644 --- a/modules/core/overrides/GLib.js +++ b/modules/core/overrides/GLib.js @@ -360,23 +360,23 @@ function _init() { GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', variantFields)); }; - // CjsPrivate depends on GLib so we cannot import it + // GjsPrivate depends on GLib so we cannot import it // before GLib is fully resolved. this.log_set_writer_func_variant = function (...args) { - const {log_set_writer_func} = imports.gi.CjsPrivate; + const {log_set_writer_func} = imports.gi.GjsPrivate; log_set_writer_func(...args); }; this.log_set_writer_default = function (...args) { - const {log_set_writer_default} = imports.gi.CjsPrivate; + const {log_set_writer_default} = imports.gi.GjsPrivate; log_set_writer_default(...args); }; this.log_set_writer_func = function (writer_func) { - const {log_set_writer_func} = imports.gi.CjsPrivate; + const {log_set_writer_func} = imports.gi.GjsPrivate; if (typeof writer_func !== 'function') { log_set_writer_func(writer_func); @@ -544,17 +544,11 @@ function _init() { if (matchInfoPatched) return; - const {MatchInfo} = imports.gi.CjsPrivate; + const {MatchInfo} = imports.gi.GjsPrivate; const originalMatchInfoMethods = new Set(Object.keys(oldMatchInfo.prototype)); const overriddenMatchInfoMethods = new Set(Object.keys(MatchInfo.prototype)); - const symmetricDifference = new Set(originalMatchInfoMethods); - for (const method of overriddenMatchInfoMethods) { - if (symmetricDifference.has(method)) - symmetricDifference.delete(method); - else - symmetricDifference.add(method); - } + const symmetricDifference = originalMatchInfoMethods.symmetricDifference(overriddenMatchInfoMethods); if (symmetricDifference.size !== 0) throw new Error(`Methods of GMatchInfo and GjsMatchInfo don't match: ${[...symmetricDifference]}`); @@ -563,8 +557,8 @@ function _init() { } // We can't monkeypatch GLib.MatchInfo directly at override time, because - // importing CjsPrivate requires GLib. So this monkeypatches GLib.MatchInfo - // with a Proxy that overwrites itself with the real CjsPrivate.MatchInfo + // importing GjsPrivate requires GLib. So this monkeypatches GLib.MatchInfo + // with a Proxy that overwrites itself with the real GjsPrivate.MatchInfo // as soon as you try to do anything with it. const allProxyOperations = ['apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', @@ -581,21 +575,21 @@ function _init() { this.Regex.prototype.match = function (...args) { patchMatchInfo(GLib); - return imports.gi.CjsPrivate.regex_match(this, ...args); + return imports.gi.GjsPrivate.regex_match(this, ...args); }; this.Regex.prototype.match_full = function (...args) { patchMatchInfo(GLib); - return imports.gi.CjsPrivate.regex_match_full(this, ...args); + return imports.gi.GjsPrivate.regex_match_full(this, ...args); }; this.Regex.prototype.match_all = function (...args) { patchMatchInfo(GLib); - return imports.gi.CjsPrivate.regex_match_all(this, ...args); + return imports.gi.GjsPrivate.regex_match_all(this, ...args); }; this.Regex.prototype.match_all_full = function (...args) { patchMatchInfo(GLib); - return imports.gi.CjsPrivate.regex_match_all_full(this, ...args); + return imports.gi.GjsPrivate.regex_match_all_full(this, ...args); }; } diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js index 2cdb1aa11..bbf15036e 100644 --- a/modules/core/overrides/GObject.js +++ b/modules/core/overrides/GObject.js @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2017 Philip Chimento , const Gi = imports._gi; -const {CjsPrivate, GLib} = imports.gi; +const {GjsPrivate, GLib} = imports.gi; const {_checkAccessors, _registerType} = imports._common; const Legacy = imports._legacy; @@ -299,6 +299,16 @@ function _registerGObjectType(klass) { requiredInterfaces.forEach(iface => _copyInterfacePrototypeDescriptors(klass.prototype, iface.prototype)); + Object.getOwnPropertyNames(klass) + .filter(name => name.startsWith('vfunc_')) + .forEach(name => { + const prop = Object.getOwnPropertyDescriptor(klass, name); + if (!(prop.value instanceof Function)) + return; + + giPrototype[Gi.hook_up_vfunc_symbol](name.slice(6), klass[name], true); + }); + Object.getOwnPropertyNames(klass.prototype) .filter(name => name.startsWith('vfunc_') || name.startsWith('on_')) .forEach(name => { @@ -539,21 +549,21 @@ function _init() { configurable: false, enumerable: false, get() { - return CjsPrivate.param_spec_get_flags(this); + return GjsPrivate.param_spec_get_flags(this); }, }, 'value_type': { configurable: false, enumerable: false, get() { - return CjsPrivate.param_spec_get_value_type(this); + return GjsPrivate.param_spec_get_value_type(this); }, }, 'owner_type': { configurable: false, enumerable: false, get() { - return CjsPrivate.param_spec_get_owner_type(this); + return GjsPrivate.param_spec_get_owner_type(this); }, }, }); @@ -682,14 +692,12 @@ function _init() { }; GObject.Object.prototype.bind_property_full = function (...args) { - return CjsPrivate.g_object_bind_property_full(this, ...args); + return GjsPrivate.g_object_bind_property_full(this, ...args); }; - if (GObject.BindingGroup !== undefined) { - GObject.BindingGroup.prototype.bind_full = function (...args) { - return CjsPrivate.g_binding_group_bind_full(this, ...args); - }; - } + GObject.BindingGroup.prototype.bind_full = function (...args) { + return GjsPrivate.g_binding_group_bind_full(this, ...args); + }; // fake enum for signal accumulators, keep in sync with gi/object.c GObject.AccumulatorType = { @@ -901,4 +909,87 @@ introspectable. Use GObject.signal_handlers_disconnect_by_func() instead.'); GObject.Object.prototype.ref = unsupportedRefcountingMethod; GObject.Object.prototype.ref_sink = unsupportedRefcountingMethod; GObject.Object.prototype.unref = unsupportedRefcountingMethod; + + const gValConstructor = GObject.Value; + GObject.Value = function (...args) { + const v = new gValConstructor(); + if (args.length !== 2) + return v; + + const type = args[0], val = args[1]; + v.init(type); + switch (v.g_type) { + case GObject.TYPE_BOOLEAN: + v.set_boolean(val); + break; + case GObject.TYPE_BOXED: + v.set_boxed(val); + break; + case GObject.TYPE_CHAR: + v.set_schar(val); + break; + case GObject.TYPE_DOUBLE: + v.set_double(val); + break; + case GObject.TYPE_FLOAT: + v.set_float(val); + break; + case GObject.TYPE_GTYPE: + v.set_gtype(val); + break; + case GObject.TYPE_INT: + v.set_int(val); + break; + case GObject.TYPE_INT64: + v.set_int64(val); + break; + case GObject.TYPE_LONG: + v.set_long(val); + break; + case GObject.TYPE_OBJECT: + v.set_object(val); + break; + case GObject.TYPE_PARAM: + v.set_param(val); + break; + case GObject.TYPE_STRING: + v.set_string(val); + break; + case GObject.TYPE_UCHAR: + v.set_uchar(val); + break; + case GObject.TYPE_UINT: + v.set_uint(val); + break; + case GObject.TYPE_UINT64: + v.set_uint64(val); + break; + case GObject.TYPE_ULONG: + v.set_ulong(val); + break; + case GObject.TYPE_VARIANT: + v.set_variant(val); + break; + // case TYPE_POINTER omitted + default: + if (GObject.type_is_a(v.g_type, GObject.TYPE_FLAGS)) + v.set_flag(val); + else if (GObject.type_is_a(v.g_type, GObject.TYPE_ENUM)) + v.set_enum(val); + else if (GObject.type_is_a(v.g_type, GObject.TYPE_BOXED)) + v.set_boxed(val); + else if (GObject.type_is_a(v.g_type, GObject.TYPE_OBJECT)) + v.set_object(val); + else + throw new TypeError(`Invalid type argument ${type} to GObject.Value constructor!`); + } + + return v; + }; + GObject.Value.prototype = gValConstructor.prototype; + GObject.Value.prototype.constructor = GObject.Value; + GObject.Value.$gtype = gValConstructor.$gtype; + Object.entries(gValConstructor).forEach(([k, v]) => { + GObject.Value[k] = v; + }); } diff --git a/modules/core/overrides/Gio.js b/modules/core/overrides/Gio.js index 27041a1af..063c8e5a4 100644 --- a/modules/core/overrides/Gio.js +++ b/modules/core/overrides/Gio.js @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: 2011 Giovanni Campagna var GLib = imports.gi.GLib; -var CjsPrivate = imports.gi.CjsPrivate; +var GjsPrivate = imports.gi.GjsPrivate; var Signals = imports.signals; -const { warnDeprecatedOncePerCallsite, PLATFORM_SPECIFIC_TYPELIB } = imports._print; +const {warnDeprecatedOncePerCallsite, PLATFORM_SPECIFIC_TYPELIB} = imports._print; var Gio; // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with @@ -404,7 +404,7 @@ function _wrapJSObject(interfaceInfo, jsObj) { info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo); info.cache_build(); - var impl = new CjsPrivate.DBusImplementation({g_interface_info: info}); + var impl = new GjsPrivate.DBusImplementation({g_interface_info: info}); impl.connect('handle-method-call', function (self, methodName, parameters, invocation) { return _handleMethodCall.call(jsObj, info, self, methodName, parameters, invocation); }); @@ -481,25 +481,24 @@ function _warnNotIntrospectable(funcName, replacement) { function _init() { Gio = this; let GioPlatform = {}; + let platformName = ''; Gio.Application.prototype.runAsync = GLib.MainLoop.prototype.runAsync; - if (GLib.MAJOR_VERSION > 2 || - (GLib.MAJOR_VERSION === 2 && GLib.MINOR_VERSION >= 86)) { - // Redefine Gio functions with platform-specific implementations to be - // backward compatible with gi-repository 1.0, however when possible we - // notify a deprecation warning, to ensure that the surrounding code is - // updated. + // Redefine Gio functions with platform-specific implementations to be + // backward compatible with gi-repository 1.0, however when possible we + // notify a deprecation warning, to ensure that the surrounding code is + // updated. + try { + GioPlatform = imports.gi.GioUnix; + platformName = 'Unix'; + } catch { try { - GioPlatform = imports.gi.GioUnix; - } catch { - try { - GioPlatform = imports.gi.GioWin32; - } catch {} - } + GioPlatform = imports.gi.GioWin32; + platformName = 'Win32'; + } catch {} } - const platformName = `${GioPlatform?.__name__?.slice(3 /* 'Gio'.length */)}`; const platformNameLower = platformName.toLowerCase(); Object.entries(Object.getOwnPropertyDescriptors(GioPlatform)).forEach(([prop, desc]) => { let genericProp = prop; @@ -588,16 +587,16 @@ function _init() { _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo); Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo; - Gio.DBusExportedObject = CjsPrivate.DBusImplementation; + Gio.DBusExportedObject = GjsPrivate.DBusImplementation; Gio.DBusExportedObject.wrapJSObject = _wrapJSObject; // ListStore Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator; Gio.ListStore.prototype.insert_sorted = function (item, compareFunc) { - return CjsPrivate.list_store_insert_sorted(this, item, compareFunc); + return GjsPrivate.list_store_insert_sorted(this, item, compareFunc); }; Gio.ListStore.prototype.sort = function (compareFunc) { - return CjsPrivate.list_store_sort(this, compareFunc); + return GjsPrivate.list_store_sort(this, compareFunc); }; // Promisify diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js index 27b650bff..91a59e038 100644 --- a/modules/core/overrides/Gtk.js +++ b/modules/core/overrides/Gtk.js @@ -3,12 +3,12 @@ // SPDX-FileCopyrightText: 2013 Giovanni Campagna const Legacy = imports._legacy; -const {Gio, CjsPrivate, GLib, GObject} = imports.gi; +const {Gio, GjsPrivate, GLib, GObject} = imports.gi; const {_createBuilderConnectFunc, _createClosure, _registerType} = imports._common; const Gi = imports._gi; let Gtk; -let BuilderScope; +let TemplateBuilderScope; function _init() { Gtk = this; @@ -23,14 +23,14 @@ function _init() { if (Gtk.Container && Gtk.Container.prototype.child_set_property) { Gtk.Container.prototype.child_set_property = function (child, property, value) { - CjsPrivate.gtk_container_child_set_property(this, child, property, value); + GjsPrivate.gtk_container_child_set_property(this, child, property, value); }; } if (Gtk.CustomSorter) { - Gtk.CustomSorter.new = CjsPrivate.gtk_custom_sorter_new; + Gtk.CustomSorter.new = GjsPrivate.gtk_custom_sorter_new; Gtk.CustomSorter.prototype.set_sort_func = function (sortFunc) { - CjsPrivate.gtk_custom_sorter_set_sort_func(this, sortFunc); + GjsPrivate.gtk_custom_sorter_set_sort_func(this, sortFunc); }; } @@ -73,20 +73,22 @@ function _init() { }; } - if (Gtk.BuilderScope) { - BuilderScope = GObject.registerClass({ - Implements: [Gtk.BuilderScope], - }, class extends GObject.Object { - vfunc_create_closure(builder, handlerName, flags, connectObject) { - const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED; - const thisArg = builder.get_current_object(); - return Gi.associateClosure( - connectObject ?? thisArg, - _createClosure(builder, thisArg, handlerName, swapped, connectObject) - ); - } - }); - } + // Everything after this is GTK4-only, for the Gtk.Builder JS implementation + if (!Gtk.BuilderScope) + return; + + TemplateBuilderScope = GObject.registerClass({ + Implements: [Gtk.BuilderScope], + }, class extends GObject.Object { + vfunc_create_closure(builder, handlerName, flags, connectObject) { + const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED; + const thisArg = builder.get_current_object(); + return Gi.associateClosure( + connectObject ?? thisArg, + _createClosure(builder, thisArg, handlerName, swapped, connectObject) + ); + } + }); } function _registerWidgetType(klass) { @@ -132,8 +134,8 @@ function _registerWidgetType(klass) { Gtk.Widget.set_template.call(klass, template); } - if (BuilderScope) - Gtk.Widget.set_template_scope.call(klass, new BuilderScope()); + if (TemplateBuilderScope) + Gtk.Widget.set_template_scope.call(klass, new TemplateBuilderScope()); else Gtk.Widget.set_connect_func.call(klass, _createBuilderConnectFunc(klass)); } diff --git a/modules/esm/.eslintrc.yml b/modules/esm/.eslintrc.yml deleted file mode 100644 index 84e48aef0..000000000 --- a/modules/esm/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -extends: '../../.eslintrc.yml' -parserOptions: - sourceType: 'module' - ecmaVersion: 2022 diff --git a/modules/esm/console.js b/modules/esm/console.js index c38604a0b..4779d15fa 100644 --- a/modules/esm/console.js +++ b/modules/esm/console.js @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh -const DEFAULT_LOG_DOMAIN = 'Cjs-Console'; +const DEFAULT_LOG_DOMAIN = 'Gjs-Console'; // A line-by-line implementation of https://console.spec.whatwg.org/. @@ -116,7 +116,7 @@ class Console { */ clear() { this.#groupIndentation = ''; - imports.gi.CjsPrivate.clear_terminal(); + imports.gi.GjsPrivate.clear_terminal(); } /** diff --git a/modules/internal/.eslintrc.yml b/modules/internal/.eslintrc.yml deleted file mode 100644 index 7c1877934..000000000 --- a/modules/internal/.eslintrc.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -extends: ../../.eslintrc.yml -parserOptions: - sourceType: 'module' - ecmaVersion: 2022 -globals: - ARGV: off - Debugger: readonly - GIRepositoryGType: off - imports: off - Intl: readonly - log: off - logError: off - print: off - printerr: off - moduleGlobalThis: readonly - compileModule: readonly - compileInternalModule: readonly - loadResourceOrFile: readonly - loadResourceOrFileAsync: readonly - parseURI: readonly - uriExists: readonly - resolveRelativeResourceOrFile: readonly - setGlobalModuleLoader: readonly - setModulePrivate: readonly - getRegistry: readonly diff --git a/modules/internal/environment.d.ts b/modules/internal/environment.d.ts new file mode 100644 index 000000000..3c1e4029a --- /dev/null +++ b/modules/internal/environment.d.ts @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2020 Evan Welsh + +declare var moduleGlobalThis: Global; + +declare var atob: (text: string) => string; +declare var compileInternalModule: CompileFunc; +declare var compileModule: CompileFunc; +declare var getRegistry: (global: Global) => Map; +declare var getSourceMapRegistry: + (global: Global) => Map; +declare var loadResourceOrFile: (uri: string) => string; +declare var loadResourceOrFileAsync: (uri: string) => Promise; +declare var parseURI: (uri: string) => Uri; +declare var resolveRelativeResourceOrFile: + (uri: string, relativePath: string) => Uri; +declare var setGlobalModuleLoader: + (global: Global, loader: InternalModuleLoader) => void; +declare var setModulePrivate: (module: Module, private: ModulePrivate) => void; +declare var uriExists: (uri: string) => boolean; + +/** + * Use '__internal: never' to prevent any object from being type compatible with + * Module because it is an internal type. + */ +declare type Module = { __internal: never }; +declare type Global = typeof globalThis; +declare type SchemeHandler = { + load(uri: Uri): string; + loadAsync(uri: Uri): Promise; +}; + +declare type Query = { [key: string]: string | undefined }; +declare type CompileFunc = (uri: string, source: string) => Module; +declare type ResolvedModule = [Module, string, string]; + +declare type Uri = { + uri: string; + uriWithQuery: string; + scheme: string; + host: string; + path: string; + query: Query; +}; diff --git a/modules/internal/internalLoader.js b/modules/internal/internalLoader.js new file mode 100644 index 000000000..299824474 --- /dev/null +++ b/modules/internal/internalLoader.js @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2020 Evan Welsh + +// eslint-disable-next-line spaced-comment +/// +// @ts-check + +/** + * Thrown when there is an error importing a module. + */ +export class ImportError extends moduleGlobalThis.Error { + name = 'ImportError'; +} + +/** + * ModulePrivate is the "private" object of every module. + */ +export class ModulePrivate { + /** + * + * @param {string} id the module's identifier + * @param {string} uri the module's URI + * @param {boolean} [internal] whether this module is "internal" + */ + constructor(id, uri, internal = false) { + this.id = id; + this.uri = uri; + this.internal = internal; + } +} + +/** + * Returns whether a string represents a relative path (e.g. ./, ../) + * + * @param {string} path a path to check if relative + * @returns {boolean} + */ +function isRelativePath(path) { + // Check if the path is relative. Note that this doesn't mean "relative + // path" in the GLib sense, as in "not absolute" — it means a relative path + // module specifier, which must start with a '.' or '..' path component. + return path.startsWith('./') || path.startsWith('../'); +} + +/** + * Handles resolving and loading URIs. + * + * @class + */ +export class InternalModuleLoader { + /** + * @param {typeof globalThis} global the global object to handle module + * resolution + * @param {CompileFunc} compileFunc the function to compile a source into a + * module for a particular global object. Should be + * compileInternalModule() for InternalModuleLoader, but overridden in + * ModuleLoader + */ + constructor(global, compileFunc) { + this.global = global; + this.compileFunc = compileFunc; + } + + /** + * Determines whether a module gets access to import.meta.importSync(). + * + * @param {Uri} uri real URI of the module (file:/// or resource:///) + */ + isInternal(uri) { + void uri; + return false; + } + + /** + * Loads a file or resource URI synchronously + * + * @param {Uri} uri the file or resource URI to load + * @returns {string} the contents of the module + */ + loadURI(uri) { + if (uri.scheme === 'file' || uri.scheme === 'resource') + return loadResourceOrFile(uri.uri); + + throw new ImportError(`Unsupported URI scheme for importing: ${uri.scheme ?? uri}`); + } + + /** + * Resolves an import specifier given an optional parent importer. + * + * @param {string} specifier the import specifier + * @param {Uri | null} [parentURI] the URI of the module importing the specifier + * @returns {Uri} + */ + resolveSpecifier(specifier, parentURI = null) { + try { + const uri = parseURI(specifier); + + if (uri) + return uri; + } catch (err) { + // If it can't be parsed as a URI, try a relative path or return null. + } + + if (isRelativePath(specifier)) { + if (!parentURI) + throw new ImportError('Cannot import relative path when module path is unknown.'); + + return resolveRelativeResourceOrFile(parentURI.uriWithQuery, specifier); + } + + throw new ImportError(`Module not found: ${specifier}`); + } + + /** + * Compiles a module source text with the module's URI + * + * @param {ModulePrivate} priv a module private object + * @param {string} text the module source text to compile + * @returns {Module} + */ + compileModule(priv, text) { + const compiled = this.compileFunc(priv.uri, text); + + setModulePrivate(compiled, priv); + + return compiled; + } + + /** + * @param {string} specifier the specifier (e.g. relative path, root package) to resolve + * @param {Uri | null} importingModuleURI the URI of the module + * triggering this resolve + * + * @returns {ResolvedModule} + */ + resolveModule(specifier, importingModuleURI) { + const registry = getRegistry(this.global); + + // Check if the module has already been loaded + let module = registry.get(specifier); + if (module) + return [module, '', '']; + + // 1) Resolve path and URI-based imports. + const uri = this.resolveSpecifier(specifier, importingModuleURI); + + module = registry.get(uri.uriWithQuery); + + // Check if module is already loaded (relative handling) + if (module) + return [module, '', '']; + + const text = this.loadURI(uri); + const internal = this.isInternal(uri); + const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); + const compiled = this.compileModule(priv, text); + + registry.set(uri.uriWithQuery, compiled); + return [compiled, text, uri.uri]; + } + + /** + * Called by SpiderMonkey as part of gjs_module_resolve(). + * + * @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 {Module} + */ + moduleResolveHook(importingModulePriv, specifier) { + const importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; + const [resolved] = this.resolveModule(specifier, importingModuleURI); + return resolved; + } + + /** + * Called by SpiderMonkey as part of gjs_module_load(). + * + * @param {string} id - the module specifier + * @param {string} uri - the URI where the module is to be found + * @returns {Module} + */ + moduleLoadHook(id, uri) { + const priv = new ModulePrivate(id, uri); + + const text = this.loadURI(parseURI(uri)); + const compiled = this.compileModule(priv, text); + + const registry = getRegistry(this.global); + registry.set(id, compiled); + + return compiled; + } +} + +export const internalModuleLoader = new InternalModuleLoader(globalThis, compileInternalModule); +setGlobalModuleLoader(globalThis, internalModuleLoader); diff --git a/modules/internal/loader.js b/modules/internal/loader.js index 03aa8f698..7729f2f8f 100644 --- a/modules/internal/loader.js +++ b/modules/internal/loader.js @@ -1,216 +1,15 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh -/** @typedef {{ uri: string; scheme: string; host: string; path: string; query: Query }} Uri */ +// eslint-disable-next-line spaced-comment +/// +// @ts-check -/** - * Use '__internal: never' to prevent any object from being type compatible with Module - * because it is an internal type. - * - * @typedef {{__internal: never;}} Module - */ -/** @typedef {typeof moduleGlobalThis | typeof globalThis} Global */ -/** @typedef {{ load(uri: Uri): [contents: string, internal: boolean]; }} SchemeHandler */ -/** @typedef {{ [key: string]: string | undefined; }} Query */ -/** @typedef {(uri: string, contents: string) => Module} CompileFunc */ - -/** - * Thrown when there is an error importing a module. - */ -class ImportError extends moduleGlobalThis.Error { - name = 'ImportError'; -} - -/** - * ModulePrivate is the "private" object of every module. - */ -class ModulePrivate { - /** - * - * @param {string} id the module's identifier - * @param {string} uri the module's URI - * @param {boolean} [internal] whether this module is "internal" - */ - constructor(id, uri, internal = false) { - this.id = id; - this.uri = uri; - this.internal = internal; - } -} - -/** - * Returns whether a string represents a relative path (e.g. ./, ../) - * - * @param {string} path a path to check if relative - * @returns {boolean} - */ -function isRelativePath(path) { - // Check if the path is relative. Note that this doesn't mean "relative - // path" in the GLib sense, as in "not absolute" — it means a relative path - // module specifier, which must start with a '.' or '..' path component. - return path.startsWith('./') || path.startsWith('../'); -} +import {ImportError, InternalModuleLoader, ModulePrivate} from './internalLoader.js'; +import {extractUrl} from './source-map/extractUrl.js'; +import {SourceMapConsumer} from './source-map/source-map-consumer.js'; -/** - * Handles resolving and loading URIs. - * - * @class - */ -class InternalModuleLoader { - /** - * @param {typeof globalThis} global the global object to handle module - * resolution - * @param {(string, string) => import("../types").Module} compileFunc the - * function to compile a source into a module for a particular global - * object. Should be compileInternalModule() for InternalModuleLoader, - * but overridden in ModuleLoader - */ - constructor(global, compileFunc) { - this.global = global; - this.compileFunc = compileFunc; - } - - /** - * Loads a file or resource URI synchronously - * - * @param {Uri} uri the file or resource URI to load - * @returns {[contents: string, internal?: boolean] | null} - */ - loadURI(uri) { - if (uri.scheme === 'file' || uri.scheme === 'resource') - return [loadResourceOrFile(uri.uri)]; - - return null; - } - - /** - * Resolves an import specifier given an optional parent importer. - * - * @param {string} specifier the import specifier - * @param {string | null} [parentURI] the URI of the module importing the specifier - * @returns {Uri | null} - */ - resolveSpecifier(specifier, parentURI = null) { - try { - const uri = parseURI(specifier); - - if (uri) - return uri; - } catch (err) { - // If it can't be parsed as a URI, try a relative path or return null. - } - - if (isRelativePath(specifier)) { - if (!parentURI) - throw new ImportError('Cannot import relative path when module path is unknown.'); - - return this.resolveRelativePath(specifier, parentURI); - } - - return null; - } - - /** - * Resolves a path relative to a URI, throwing an ImportError if - * the parentURI isn't valid. - * - * @param {string} relativePath the relative path to resolve against the base URI - * @param {string} importingModuleURI the URI of the module triggering this - * resolve - * @returns {Uri} - */ - resolveRelativePath(relativePath, importingModuleURI) { - // Ensure the parent URI is valid. - parseURI(importingModuleURI); - - // Handle relative imports from URI-based modules. - const relativeURI = resolveRelativeResourceOrFile(importingModuleURI, relativePath); - if (!relativeURI) - throw new ImportError('File does not have a valid parent!'); - return parseURI(relativeURI); - } - - /** - * Compiles a module source text with the module's URI - * - * @param {ModulePrivate} priv a module private object - * @param {string} text the module source text to compile - * @returns {Module} - */ - compileModule(priv, text) { - const compiled = this.compileFunc(priv.uri, text); - - setModulePrivate(compiled, priv); - - return compiled; - } - - /** - * @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 {Module | null} - */ - resolveModule(specifier, importingModuleURI) { - const registry = getRegistry(this.global); - - // Check if the module has already been loaded - let module = registry.get(specifier); - if (module) - return module; - - // 1) Resolve path and URI-based imports. - const uri = this.resolveSpecifier(specifier, importingModuleURI); - if (uri) { - module = registry.get(uri.uriWithQuery); - - // Check if module is already loaded (relative handling) - if (module) - return module; - - const result = this.loadURI(uri); - if (!result) - return null; - - const [text, internal = false] = result; - - const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); - const compiled = this.compileModule(priv, text); - - registry.set(uri.uriWithQuery, compiled); - return compiled; - } - - return null; - } - - moduleResolveHook(importingModulePriv, specifier) { - const resolved = this.resolveModule(specifier, importingModulePriv.uri ?? null); - if (!resolved) - throw new ImportError(`Module not found: ${specifier}`); - - return resolved; - } - - moduleLoadHook(id, uri) { - const priv = new ModulePrivate(id, uri); - - const result = this.loadURI(parseURI(uri)); - // result can only be null if `this` is InternalModuleLoader. If `this` - // is ModuleLoader, then loadURI() will have thrown - if (!result) - throw new ImportError(`URI not found: ${uri}`); - - const [text] = result; - const compiled = this.compileModule(priv, text); - - const registry = getRegistry(this.global); - registry.set(id, compiled); - - return compiled; - } -} +const DATA_URI_PREFIX = 'data:application/json;base64,'; class ModuleLoader extends InternalModuleLoader { /** @@ -259,6 +58,21 @@ class ModuleLoader extends InternalModuleLoader { return builtURIs; } + /** + * Overrides InternalModuleLoader.isInternal + * + * @param {Uri} uri real URI of the module (file:/// or resource:///) + */ + isInternal(uri) { + const s = uri.uri; + for (const internalURIPattern of this.moduleURIs) { + const [start, end] = internalURIPattern.split('*'); + if (s.startsWith(start) && s.endsWith(end)) + return true; + } + return false; + } + /** * @param {string} scheme the URI scheme to register * @param {SchemeHandler} handler a handler @@ -271,6 +85,7 @@ class ModuleLoader extends InternalModuleLoader { * Overrides InternalModuleLoader.loadURI * * @param {Uri} uri a Uri object to load + * @returns {string} */ loadURI(uri) { if (uri.scheme) { @@ -280,42 +95,75 @@ class ModuleLoader extends InternalModuleLoader { return loader.load(uri); } - const result = super.loadURI(uri); - - if (result) - return result; - - throw new ImportError(`Invalid module URI: ${uri.uri}`); + return super.loadURI(uri); } /** - * Resolves a bare specifier like 'system' against internal resources, - * erroring if no resource is found. + * Overrides InternalModuleLoader.resolveSpecifier. Adds the behaviour of + * resolving a bare specifier like 'system' against internal resources. * - * @param {string} specifier the module specifier to resolve for an import - * @returns {import("./internalLoader").Module} + * @param {string} specifier the import specifier + * @param {Uri | null} [parentURI] the URI of the module importing the + * specifier + * @returns {Uri} */ - resolveBareSpecifier(specifier) { - // 2) Resolve internal imports. - - const uri = this.buildInternalURIs(specifier).find(uriExists); - - if (!uri) - throw new ImportError(`Unknown module: '${specifier}'`); - - const parsed = parseURI(uri); - if (parsed.scheme !== 'file' && parsed.scheme !== 'resource') - throw new ImportError('Only file:// and resource:// URIs are currently supported.'); - - const text = loadResourceOrFile(parsed.uri); - const priv = new ModulePrivate(specifier, uri, true); - const compiled = this.compileModule(priv, text); - - const registry = getRegistry(this.global); - if (!registry.has(specifier)) - registry.set(specifier, compiled); + resolveSpecifier(specifier, parentURI = null) { + try { + return super.resolveSpecifier(specifier, parentURI); + } catch (error) { + // On failure due to bare specifiers, try resolving the bare + // specifier to an internal module + if (!specifier.startsWith('.')) { + const realURI = this.buildInternalURIs(specifier).find(uriExists); + if (realURI) + return parseURI(realURI); + } + + // Re-throw other errors + throw error; + } + } - return compiled; + /** + * Populates the source map registry of a given module + * Extracts the source map URL from the given code, parses the source map and build the SourceMapConsumer + * This function will fail gracefully and not throw + * + * @param {string} text The JS code of the module + * @param {string} uri The URI of the module or file with the sourceMappingURL definition + * @param {string} [absoluteUri] The Absolute URI of the file containing the + * sourceMappingURL definition. This is only used for non-module files. + */ + populateSourceMap(text, uri, absoluteUri) { + if (!text) + return; + const sourceMapUrl = extractUrl(text); + if (!sourceMapUrl) + return; + + let jsonText = null; + try { + // check if we have an inlined data uri + if (sourceMapUrl?.startsWith(DATA_URI_PREFIX)) { + jsonText = atob(sourceMapUrl.substring(DATA_URI_PREFIX.length)); + } else { + // load the source map resource or file + // resolve the source map file relative to the source file + const sourceMapUri = resolveRelativeResourceOrFile(absoluteUri ?? uri, + `./${sourceMapUrl}`); + jsonText = this.loadURI(sourceMapUri); + } + } catch (e) {} + + if (jsonText) { + try { + const sourceMapRegistry = getSourceMapRegistry(this.global); + const sourceMap = JSON.parse(jsonText); + const consumer = new SourceMapConsumer(sourceMap); + + sourceMapRegistry.set(uri, consumer); + } catch (e) {} + } } /** @@ -326,14 +174,28 @@ class ModuleLoader extends InternalModuleLoader { * 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} + * @returns {Module} */ moduleResolveHook(importingModulePriv, specifier) { - const module = this.resolveModule(specifier, importingModulePriv?.uri); - if (module) - return module; + const importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; + const [module, text, uri] = this.resolveModule(specifier, importingModuleURI); - return this.resolveBareSpecifier(specifier); + this.populateSourceMap(text, uri); + + return module; + } + + /** + * Overrides InternalModuleLoader.moduleLoadHook + * + * @param {string} id - the module specifier + * @param {string} uri - the URI where the module is to be found + * @returns {Module} + */ + moduleLoadHook(id, uri) { + const text = this.loadURI(parseURI(uri)); + this.populateSourceMap(text, uri); + return super.moduleLoadHook(id, uri); } /** @@ -347,7 +209,6 @@ class ModuleLoader extends InternalModuleLoader { * @returns {Promise} */ async moduleResolveAsyncHook(importingModulePriv, specifier) { - const importingModuleURI = importingModulePriv?.uri; const registry = getRegistry(this.global); // Check if the module has already been loaded @@ -356,44 +217,39 @@ class ModuleLoader extends InternalModuleLoader { return module; // 1) Resolve path and URI-based imports. + const importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; const uri = this.resolveSpecifier(specifier, importingModuleURI); - if (uri) { - module = registry.get(uri.uriWithQuery); - - // Check if module is already loaded (relative handling) - if (module) - return module; - const result = await this.loadURIAsync(uri); - if (!result) - return null; + module = registry.get(uri.uriWithQuery); - // Check if module loaded while awaiting. - module = registry.get(uri.uriWithQuery); - if (module) - return module; + // Check if module is already loaded (relative handling) + if (module) + return module; - const [text, internal = false] = result; + const text = await this.loadURIAsync(uri); - const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); - const compiled = this.compileModule(priv, text); + // Check if module loaded while awaiting. + module = registry.get(uri.uriWithQuery); + if (module) + return module; - registry.set(uri.uriWithQuery, compiled); - return compiled; - } + const internal = this.isInternal(uri); + const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); + const compiled = this.compileModule(priv, text); - // 2) Resolve internal imports. + registry.set(uri.uriWithQuery, compiled); - return this.resolveBareSpecifier(specifier); + this.populateSourceMap(text, uri.uri); + return compiled; } /** * Loads a file or resource URI asynchronously * * @param {Uri} uri the file or resource URI to load - * @returns {Promise<[string] | [string, boolean] | null>} + * @returns {Promise} */ - async loadURIAsync(uri) { + loadURIAsync(uri) { if (uri.scheme) { const loader = this.schemeHandlers.get(uri.scheme); @@ -401,12 +257,10 @@ class ModuleLoader extends InternalModuleLoader { return loader.loadAsync(uri); } - if (uri.scheme === 'file' || uri.scheme === 'resource') { - const result = await loadResourceOrFileAsync(uri.uri); - return [result]; - } + if (uri.scheme === 'file' || uri.scheme === 'resource') + return loadResourceOrFileAsync(uri.uri); - return null; + throw new ImportError(`Unsupported URI scheme for importing: ${uri.scheme ?? uri}`); } } @@ -436,13 +290,13 @@ moduleLoader.registerScheme('gi', { const namespace = uri.host; const version = uri.query.version; - return [generateGIModule(namespace, version), true]; + return generateGIModule(namespace, version); }, /** * @param {Uri} uri the URI to load asynchronously */ loadAsync(uri) { // gi: only does string manipulation, so it is safe to use the same code for sync and async. - return this.load(uri); + return Promise.resolve(this.load(uri)); }, }); diff --git a/modules/internal/source-map/array-set.js b/modules/internal/source-map/array-set.js new file mode 100644 index 000000000..0192faaa4 --- /dev/null +++ b/modules/internal/source-map/array-set.js @@ -0,0 +1,124 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/array-set.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +import * as util from './util.js'; +var has = Object.prototype.hasOwnProperty; +var hasNativeMap = typeof Map !== "undefined"; + +/** + * A data structure which is a combination of an array and a set. Adding a new + * member is O(1), testing for membership is O(1), and finding the index of an + * element is O(1). Removing elements from the set is not supported. Only + * strings are supported for membership. + */ +export function ArraySet() { + this._array = []; + this._set = hasNativeMap ? new Map() : Object.create(null); +} + +/** + * Static method for creating ArraySet instances from an existing array. + */ +ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { + var set = new ArraySet(); + for (var i = 0, len = aArray.length; i < len; i++) { + set.add(aArray[i], aAllowDuplicates); + } + return set; +}; + +/** + * Return how many unique items are in this ArraySet. If duplicates have been + * added, than those do not count towards the size. + * + * @returns Number + */ +ArraySet.prototype.size = function ArraySet_size() { + return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length; +}; + +/** + * Add the given string to this set. + * + * @param String aStr + */ +ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { + var sStr = hasNativeMap ? aStr : util.toSetString(aStr); + var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr); + var idx = this._array.length; + if (!isDuplicate || aAllowDuplicates) { + this._array.push(aStr); + } + if (!isDuplicate) { + if (hasNativeMap) { + this._set.set(aStr, idx); + } else { + this._set[sStr] = idx; + } + } +}; + +/** + * Is the given string a member of this set? + * + * @param String aStr + */ +ArraySet.prototype.has = function ArraySet_has(aStr) { + if (hasNativeMap) { + return this._set.has(aStr); + } else { + var sStr = util.toSetString(aStr); + return has.call(this._set, sStr); + } +}; + +/** + * What is the index of the given string in the array? + * + * @param String aStr + */ +ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { + if (hasNativeMap) { + var idx = this._set.get(aStr); + if (idx >= 0) { + return idx; + } + } else { + var sStr = util.toSetString(aStr); + if (has.call(this._set, sStr)) { + return this._set[sStr]; + } + } + + throw new Error('"' + aStr + '" is not in the set.'); +}; + +/** + * What is the element at the given index? + * + * @param Number aIdx + */ +ArraySet.prototype.at = function ArraySet_at(aIdx) { + if (aIdx >= 0 && aIdx < this._array.length) { + return this._array[aIdx]; + } + throw new Error('No element indexed by ' + aIdx); +}; + +/** + * Returns the array representation of this set (which has the proper indices + * indicated by indexOf). Note that this is a copy of the internal array used + * for storing the members so that no one can mess with internal state. + */ +ArraySet.prototype.toArray = function ArraySet_toArray() { + return this._array.slice(); +}; diff --git a/modules/internal/source-map/base64-vlq.js b/modules/internal/source-map/base64-vlq.js new file mode 100644 index 000000000..0aa678ce8 --- /dev/null +++ b/modules/internal/source-map/base64-vlq.js @@ -0,0 +1,147 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/base64-vlq.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import * as base64 from './base64.js'; + +// A single base 64 digit can contain 6 bits of data. For the base 64 variable +// length quantities we use in the source map spec, the first bit is the sign, +// the next four bits are the actual value, and the 6th bit is the +// continuation bit. The continuation bit tells us whether there are more +// digits in this value following this digit. +// +// Continuation +// | Sign +// | | +// V V +// 101011 + +var VLQ_BASE_SHIFT = 5; + +// binary: 100000 +var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + +// binary: 011111 +var VLQ_BASE_MASK = VLQ_BASE - 1; + +// binary: 100000 +var VLQ_CONTINUATION_BIT = VLQ_BASE; + +/** + * Converts from a two-complement value to a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ +function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; +} + +/** + * Converts to a two-complement value from a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ +function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; +} + +/** + * Returns the base 64 VLQ encoded value. + */ +function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; +}; + +/** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string via the out parameter. + */ +function base64VLQ_decode(aStr, aIndex, aOutParam) { + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (aIndex >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + + digit = base64.decode(aStr.charCodeAt(aIndex++)); + if (digit === -1) { + throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); + } + + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + aOutParam.value = fromVLQSigned(result); + aOutParam.rest = aIndex; +}; + +export { base64VLQ_decode as decode, base64VLQ_encode as encode } diff --git a/modules/internal/source-map/base64.js b/modules/internal/source-map/base64.js new file mode 100644 index 000000000..9e2d73065 --- /dev/null +++ b/modules/internal/source-map/base64.js @@ -0,0 +1,74 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/base64.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); + +/** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ +function encode(number) { + if (0 <= number && number < intToCharMap.length) { + return intToCharMap[number]; + } + throw new TypeError("Must be between 0 and 63: " + number); +}; + +/** + * Decode a single base 64 character code digit to an integer. Returns -1 on + * failure. + */ +function decode(charCode) { + var bigA = 65; // 'A' + var bigZ = 90; // 'Z' + + var littleA = 97; // 'a' + var littleZ = 122; // 'z' + + var zero = 48; // '0' + var nine = 57; // '9' + + var plus = 43; // '+' + var slash = 47; // '/' + + var littleOffset = 26; + var numberOffset = 52; + + // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ + if (bigA <= charCode && charCode <= bigZ) { + return (charCode - bigA); + } + + // 26 - 51: abcdefghijklmnopqrstuvwxyz + if (littleA <= charCode && charCode <= littleZ) { + return (charCode - littleA + littleOffset); + } + + // 52 - 61: 0123456789 + if (zero <= charCode && charCode <= nine) { + return (charCode - zero + numberOffset); + } + + // 62: + + if (charCode == plus) { + return 62; + } + + // 63: / + if (charCode == slash) { + return 63; + } + + // Invalid base64 digit. + return -1; +}; + +export { decode, encode }; diff --git a/modules/internal/source-map/binary-search.js b/modules/internal/source-map/binary-search.js new file mode 100644 index 000000000..c8071573e --- /dev/null +++ b/modules/internal/source-map/binary-search.js @@ -0,0 +1,118 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/binary-search.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +const GREATEST_LOWER_BOUND = 1; +const LEAST_UPPER_BOUND = 2; + +/** + * Recursive implementation of binary search. + * + * @param aLow Indices here and lower do not contain the needle. + * @param aHigh Indices here and higher do not contain the needle. + * @param aNeedle The element being searched for. + * @param aHaystack The non-empty array being searched. + * @param aCompare Function which takes two elements and returns -1, 0, or 1. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + */ +function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { + // This function terminates when one of the following is true: + // + // 1. We find the exact element we are looking for. + // + // 2. We did not find the exact element, but we can return the index of + // the next-closest element. + // + // 3. We did not find the exact element, and there is no next-closest + // element than the one we are searching for, so we return -1. + var mid = Math.floor((aHigh - aLow) / 2) + aLow; + var cmp = aCompare(aNeedle, aHaystack[mid], true); + if (cmp === 0) { + // Found the element we are looking for. + return mid; + } + else if (cmp > 0) { + // Our needle is greater than aHaystack[mid]. + if (aHigh - mid > 1) { + // The element is in the upper half. + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); + } + + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == LEAST_UPPER_BOUND) { + return aHigh < aHaystack.length ? aHigh : -1; + } else { + return mid; + } + } + else { + // Our needle is less than aHaystack[mid]. + if (mid - aLow > 1) { + // The element is in the lower half. + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); + } + + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == LEAST_UPPER_BOUND) { + return mid; + } else { + return aLow < 0 ? -1 : aLow; + } + } +} + +/** + * This is an implementation of binary search which will always try and return + * the index of the closest element if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. + * + * @param aNeedle The element you are looking for. + * @param aHaystack The array that is being searched. + * @param aCompare A function which takes the needle and an element in the + * array and returns -1, 0, or 1 depending on whether the needle is less + * than, equal to, or greater than the element, respectively. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. + */ +function search(aNeedle, aHaystack, aCompare, aBias) { + if (aHaystack.length === 0) { + return -1; + } + + var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, + aCompare, aBias || GREATEST_LOWER_BOUND); + if (index < 0) { + return -1; + } + + // We have found either the exact element, or the next-closest element than + // the one we are searching for. However, there may be more than one such + // element. Make sure we always return the smallest of these. + while (index - 1 >= 0) { + if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { + break; + } + --index; + } + + return index; +}; + +export { search, LEAST_UPPER_BOUND, GREATEST_LOWER_BOUND }; diff --git a/modules/internal/source-map/extractUrl.js b/modules/internal/source-map/extractUrl.js new file mode 100644 index 000000000..f93d5c6b6 --- /dev/null +++ b/modules/internal/source-map/extractUrl.js @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: CC-BY-3.0 +// SPDX-FileCopyrightText: 2024 Source Maps Task Group (TC39-TG4) +// Reference implementation in Source Map V3 Standard 6.2.2.1 +// @ts-nocheck +export function extractUrl(source) { + const JS_NEWLINE = /^/m; + + // This RegExp will always match one of the following: + // - single-line comments + // - "single-line" multi-line comments + // - unclosed multi-line comments + // - just trailing whitespaces + // - a code character + // The loop below differentiates between all these cases. + const JS_COMMENT = /\s*(?:\/\/(?.*)|\/\*(?.*?)\*\/|\/\*.*|$|(?[^\/]+))/muy; + + const PATTERN = /^[@#]\s*sourceMappingURL=(\S*?)\s*$/; + + let lastURL = null; + for (const line of source.split(JS_NEWLINE)) { + JS_COMMENT.lastIndex = 0; + while (JS_COMMENT.lastIndex < line.length) { + const exec = JS_COMMENT.exec(line); + if (!exec) break; // added + let commentMatch = exec.groups; + let comment = commentMatch.single ?? commentMatch.multi; + if (comment != null) { + let match = PATTERN.exec(comment); + if (match !== null) lastURL = match[1]; + } else if (commentMatch.code != null) { + lastURL = null; + } else { + // We found either trailing whitespaces or an unclosed comment. + // Assert: JS_COMMENT.lastIndex === line.length + } + } + } + return lastURL; +} diff --git a/modules/internal/source-map/source-map-consumer.js b/modules/internal/source-map/source-map-consumer.js new file mode 100644 index 000000000..0f8aed7b6 --- /dev/null +++ b/modules/internal/source-map/source-map-consumer.js @@ -0,0 +1,1148 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/source-map-consumer.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +import * as util from './util.js'; +import * as binarySearch from './binary-search.js'; +import { ArraySet } from './array-set.js'; +import * as base64VLQ from './base64-vlq.js'; + +function SourceMapConsumer(aSourceMap, aSourceMapURL) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = util.parseSourceMapInput(aSourceMap); + } + + return sourceMap.sections != null + ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL) + : new BasicSourceMapConsumer(sourceMap, aSourceMapURL); +} + +SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) { + return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL); +} + +/** + * The version of the source mapping spec that we are consuming. + */ +SourceMapConsumer.prototype._version = 3; + +// `__generatedMappings` and `__originalMappings` are arrays that hold the +// parsed mapping coordinates from the source map's "mappings" attribute. They +// are lazily instantiated, accessed via the `_generatedMappings` and +// `_originalMappings` getters respectively, and we only parse the mappings +// and create these arrays once queried for a source location. We jump through +// these hoops because there can be many thousands of mappings, and parsing +// them is expensive, so we only want to do it if we must. +// +// Each object in the arrays is of the form: +// +// { +// generatedLine: The line number in the generated code, +// generatedColumn: The column number in the generated code, +// source: The path to the original source file that generated this +// chunk of code, +// originalLine: The line number in the original source that +// corresponds to this chunk of generated code, +// originalColumn: The column number in the original source that +// corresponds to this chunk of generated code, +// name: The name of the original symbol which generated this chunk of +// code. +// } +// +// All properties except for `generatedLine` and `generatedColumn` can be +// `null`. +// +// `_generatedMappings` is ordered by the generated positions. +// +// `_originalMappings` is ordered by the original positions. + +SourceMapConsumer.prototype.__generatedMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { + configurable: true, + enumerable: true, + get: function () { + if (!this.__generatedMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__generatedMappings; + } +}); + +SourceMapConsumer.prototype.__originalMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { + configurable: true, + enumerable: true, + get: function () { + if (!this.__originalMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__originalMappings; + } +}); + +SourceMapConsumer.prototype._charIsMappingSeparator = + function SourceMapConsumer_charIsMappingSeparator(aStr, index) { + var c = aStr.charAt(index); + return c === ";" || c === ","; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +SourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + throw new Error("Subclasses must implement _parseMappings"); + }; + +SourceMapConsumer.GENERATED_ORDER = 1; +SourceMapConsumer.ORIGINAL_ORDER = 2; + +SourceMapConsumer.GREATEST_LOWER_BOUND = 1; +SourceMapConsumer.LEAST_UPPER_BOUND = 2; + +/** + * Iterate over each mapping between an original source/line/column and a + * generated line/column in this source map. + * + * @param Function aCallback + * The function that is called with each mapping. + * @param Object aContext + * Optional. If specified, this object will be the value of `this` every + * time that `aCallback` is called. + * @param aOrder + * Either `SourceMapConsumer.GENERATED_ORDER` or + * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to + * iterate over the mappings sorted by the generated file's line/column + * order or the original's source/line/column order, respectively. Defaults to + * `SourceMapConsumer.GENERATED_ORDER`. + */ +SourceMapConsumer.prototype.eachMapping = + function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { + var context = aContext || null; + var order = aOrder || SourceMapConsumer.GENERATED_ORDER; + + var mappings; + switch (order) { + case SourceMapConsumer.GENERATED_ORDER: + mappings = this._generatedMappings; + break; + case SourceMapConsumer.ORIGINAL_ORDER: + mappings = this._originalMappings; + break; + default: + throw new Error("Unknown order of iteration."); + } + + var sourceRoot = this.sourceRoot; + mappings.map(function (mapping) { + var source = mapping.source === null ? null : this._sources.at(mapping.source); + source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL); + return { + source: source, + generatedLine: mapping.generatedLine, + generatedColumn: mapping.generatedColumn, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name === null ? null : this._names.at(mapping.name) + }; + }, this).forEach(aCallback, context); + }; + +/** + * Returns all generated line and column information for the original source, + * line, and column provided. If no column is provided, returns all mappings + * corresponding to a either the line we are searching for or the next + * closest line that has any mappings. Otherwise, returns all mappings + * corresponding to the given line and either the column we are searching for + * or the next closest column that has any offsets. + * + * The only argument is an object with the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. The line number is 1-based. + * - column: Optional. the column number in the original source. + * The column number is 0-based. + * + * and an array of objects is returned, each with the following properties: + * + * - line: The line number in the generated source, or null. The + * line number is 1-based. + * - column: The column number in the generated source, or null. + * The column number is 0-based. + */ +SourceMapConsumer.prototype.allGeneratedPositionsFor = + function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { + var line = util.getArg(aArgs, 'line'); + + // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping + // returns the index of the closest mapping less than the needle. By + // setting needle.originalColumn to 0, we thus find the last mapping for + // the given line, provided such a mapping exists. + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: line, + originalColumn: util.getArg(aArgs, 'column', 0) + }; + + needle.source = this._findSourceIndex(needle.source); + if (needle.source < 0) { + return []; + } + + var mappings = []; + + var index = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + binarySearch.LEAST_UPPER_BOUND); + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (aArgs.column === undefined) { + var originalLine = mapping.originalLine; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we found. Since + // mappings are sorted, this is guaranteed to find all mappings for + // the line we found. + while (mapping && mapping.originalLine === originalLine) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } else { + var originalColumn = mapping.originalColumn; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we were searching for. + // Since mappings are sorted, this is guaranteed to find all mappings for + // the line we are searching for. + while (mapping && + mapping.originalLine === line && + mapping.originalColumn == originalColumn) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } + } + + return mappings; + }; + + +/** + * A BasicSourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The first parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: Optional. The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * The second parameter, if given, is a string whose value is the URL + * at which the source map was found. This URL is used to compute the + * sources array. + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ +function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = util.parseSourceMapInput(aSourceMap); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + if (sourceRoot) { + sourceRoot = util.normalize(sourceRoot); + } + + sources = sources + .map(String) + // Some source maps produce relative source paths like "./foo.js" instead of + // "foo.js". Normalize these first so that future comparisons will succeed. + // See bugzil.la/1090768. + .map(util.normalize) + // Always ensure that absolute sources are internally stored relative to + // the source root, if the source root is absolute. Not doing this would + // be particularly problematic when the source root is a prefix of the + // source (valid, but why??). See github issue #199 and bugzil.la/1188982. + .map(function (source) { + return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) + ? util.relative(sourceRoot, source) + : source; + }); + + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names.map(String), true); + this._sources = ArraySet.fromArray(sources, true); + + this._absoluteSources = this._sources.toArray().map(function (s) { + return util.computeSourceURL(sourceRoot, s, aSourceMapURL); + }); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this._sourceMapURL = aSourceMapURL; + this.file = file; +} + +BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; + +/** + * Utility function to find the index of a source. Returns -1 if not + * found. + */ +BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) { + var relativeSource = aSource; + if (this.sourceRoot != null) { + relativeSource = util.relative(this.sourceRoot, relativeSource); + } + + if (this._sources.has(relativeSource)) { + return this._sources.indexOf(relativeSource); + } + + // Maybe aSource is an absolute URL as returned by |sources|. In + // this case we can't simply undo the transform. + var i; + for (i = 0; i < this._absoluteSources.length; ++i) { + if (this._absoluteSources[i] == aSource) { + return i; + } + } + + return -1; +}; + +/** + * Create a BasicSourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @param String aSourceMapURL + * The URL at which the source map can be found (optional) + * @returns BasicSourceMapConsumer + */ +BasicSourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) { + var smc = Object.create(BasicSourceMapConsumer.prototype); + + var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + smc._sourceMapURL = aSourceMapURL; + smc._absoluteSources = smc._sources.toArray().map(function (s) { + return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL); + }); + + // Because we are modifying the entries (by converting string sources and + // names to indices into the sources and names ArraySets), we have to make + // a copy of the entry or else bad things happen. Shared mutable state + // strikes again! See github issue #191. + + var generatedMappings = aSourceMap._mappings.toArray().slice(); + var destGeneratedMappings = smc.__generatedMappings = []; + var destOriginalMappings = smc.__originalMappings = []; + + for (var i = 0, length = generatedMappings.length; i < length; i++) { + var srcMapping = generatedMappings[i]; + var destMapping = new Mapping; + destMapping.generatedLine = srcMapping.generatedLine; + destMapping.generatedColumn = srcMapping.generatedColumn; + + if (srcMapping.source) { + destMapping.source = sources.indexOf(srcMapping.source); + destMapping.originalLine = srcMapping.originalLine; + destMapping.originalColumn = srcMapping.originalColumn; + + if (srcMapping.name) { + destMapping.name = names.indexOf(srcMapping.name); + } + + destOriginalMappings.push(destMapping); + } + + destGeneratedMappings.push(destMapping); + } + + smc.__originalMappings.sort(util.compareByOriginalPositions); + + return smc; + }; + +/** + * The version of the source mapping spec that we are consuming. + */ +BasicSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { + get: function () { + return this._absoluteSources.slice(); + } +}); + +/** + * Provide the JIT with a nice shape / hidden class. + */ +function Mapping() { + this.generatedLine = 0; + this.generatedColumn = 0; + this.source = null; + this.originalLine = null; + this.originalColumn = null; + this.name = null; +} + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +BasicSourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var length = aStr.length; + var index = 0; + var cachedSegments = {}; + var temp = {}; + var originalMappings = []; + var generatedMappings = []; + var mapping, str, segment, end, value; + + while (index < length) { + if (aStr.charAt(index) === ';') { + generatedLine++; + index++; + previousGeneratedColumn = 0; + } + else if (aStr.charAt(index) === ',') { + index++; + } + else { + mapping = new Mapping(); + mapping.generatedLine = generatedLine; + + // Because each offset is encoded relative to the previous one, + // many segments often have the same encoding. We can exploit this + // fact by caching the parsed variable length fields of each segment, + // allowing us to avoid a second parse if we encounter the same + // segment again. + for (end = index; end < length; end++) { + if (this._charIsMappingSeparator(aStr, end)) { + break; + } + } + str = aStr.slice(index, end); + + segment = cachedSegments[str]; + if (segment) { + index += str.length; + } else { + segment = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + segment.push(value); + } + + if (segment.length === 2) { + throw new Error('Found a source, but no line and column'); + } + + if (segment.length === 3) { + throw new Error('Found a source and line, but no column'); + } + + cachedSegments[str] = segment; + } + + // Generated column. + mapping.generatedColumn = previousGeneratedColumn + segment[0]; + previousGeneratedColumn = mapping.generatedColumn; + + if (segment.length > 1) { + // Original source. + mapping.source = previousSource + segment[1]; + previousSource += segment[1]; + + // Original line. + mapping.originalLine = previousOriginalLine + segment[2]; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + + // Original column. + mapping.originalColumn = previousOriginalColumn + segment[3]; + previousOriginalColumn = mapping.originalColumn; + + if (segment.length > 4) { + // Original name. + mapping.name = previousName + segment[4]; + previousName += segment[4]; + } + } + + generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + originalMappings.push(mapping); + } + } + } + + generatedMappings.sort(util.compareByGeneratedPositionsDeflated); + this.__generatedMappings = generatedMappings; + + originalMappings.sort(util.compareByOriginalPositions); + this.__originalMappings = originalMappings; + }; + +/** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ +BasicSourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator, aBias) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator, aBias); + }; + +/** + * Compute the last column for each generated mapping. The last column is + * inclusive. + */ +BasicSourceMapConsumer.prototype.computeColumnSpans = + function SourceMapConsumer_computeColumnSpans() { + for (var index = 0; index < this._generatedMappings.length; ++index) { + var mapping = this._generatedMappings[index]; + + // Mappings do not contain a field for the last generated columnt. We + // can come up with an optimistic estimate, however, by assuming that + // mappings are contiguous (i.e. given two consecutive mappings, the + // first mapping ends where the second one starts). + if (index + 1 < this._generatedMappings.length) { + var nextMapping = this._generatedMappings[index + 1]; + + if (mapping.generatedLine === nextMapping.generatedLine) { + mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; + continue; + } + } + + // The last mapping for each line spans the entire line. + mapping.lastGeneratedColumn = Infinity; + } + }; + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. The line number + * is 1-based. + * - column: The column number in the generated source. The column + * number is 0-based. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. The + * line number is 1-based. + * - column: The column number in the original source, or null. The + * column number is 0-based. + * - name: The original identifier, or null. + */ +BasicSourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositionsDeflated, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._generatedMappings[index]; + + if (mapping.generatedLine === needle.generatedLine) { + var source = util.getArg(mapping, 'source', null); + if (source !== null) { + source = this._sources.at(source); + source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL); + } + var name = util.getArg(mapping, 'name', null); + if (name !== null) { + name = this._names.at(name); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: name + }; + } + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +BasicSourceMapConsumer.prototype.hasContentsOfAllSources = + function BasicSourceMapConsumer_hasContentsOfAllSources() { + if (!this.sourcesContent) { + return false; + } + return this.sourcesContent.length >= this._sources.size() && + !this.sourcesContent.some(function (sc) { return sc == null; }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +BasicSourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + if (!this.sourcesContent) { + return null; + } + + var index = this._findSourceIndex(aSource); + if (index >= 0) { + return this.sourcesContent[index]; + } + + var relativeSource = aSource; + if (this.sourceRoot != null) { + relativeSource = util.relative(this.sourceRoot, relativeSource); + } + + var url; + if (this.sourceRoot != null + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = relativeSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + relativeSource)) { + return this.sourcesContent[this._sources.indexOf("/" + relativeSource)]; + } + } + + // This function is used recursively from + // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we + // don't want to throw if we can't find the source - we just want to + // return null, so we provide a flag to exit gracefully. + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + relativeSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. The line number + * is 1-based. + * - column: The column number in the original source. The column + * number is 0-based. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. The + * line number is 1-based. + * - column: The column number in the generated source, or null. + * The column number is 0-based. + */ +BasicSourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var source = util.getArg(aArgs, 'source'); + source = this._findSourceIndex(source); + if (source < 0) { + return { + line: null, + column: null, + lastColumn: null + }; + } + + var needle = { + source: source, + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (mapping.source === needle.source) { + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }; + } + } + + return { + line: null, + column: null, + lastColumn: null + }; + }; + + +/** + * An IndexedSourceMapConsumer instance represents a parsed source map which + * we can query for information. It differs from BasicSourceMapConsumer in + * that it takes "indexed" source maps (i.e. ones with a "sections" field) as + * input. + * + * The first parameter is a raw source map (either as a JSON string, or already + * parsed to an object). According to the spec for indexed source maps, they + * have the following attributes: + * + * - version: Which version of the source map spec this map is following. + * - file: Optional. The generated file this source map is associated with. + * - sections: A list of section definitions. + * + * Each value under the "sections" field has two fields: + * - offset: The offset into the original specified at which this section + * begins to apply, defined as an object with a "line" and "column" + * field. + * - map: A source map definition. This source map could also be indexed, + * but doesn't have to be. + * + * Instead of the "map" field, it's also possible to have a "url" field + * specifying a URL to retrieve a source map from, but that's currently + * unsupported. + * + * Here's an example source map, taken from the source map spec[0], but + * modified to omit a section which uses the "url" field. + * + * { + * version : 3, + * file: "app.js", + * sections: [{ + * offset: {line:100, column:10}, + * map: { + * version : 3, + * file: "section.js", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AAAA,E;;ABCDE;" + * } + * }], + * } + * + * The second parameter, if given, is a string whose value is the URL + * at which the source map was found. This URL is used to compute the + * sources array. + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt + */ +function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = util.parseSourceMapInput(aSourceMap); + } + + var version = util.getArg(sourceMap, 'version'); + var sections = util.getArg(sourceMap, 'sections'); + + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + this._sources = new ArraySet(); + this._names = new ArraySet(); + + var lastOffset = { + line: -1, + column: 0 + }; + this._sections = sections.map(function (s) { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error('Support for url field in sections not implemented.'); + } + var offset = util.getArg(s, 'offset'); + var offsetLine = util.getArg(offset, 'line'); + var offsetColumn = util.getArg(offset, 'column'); + + if (offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { + throw new Error('Section offsets must be ordered and non-overlapping.'); + } + lastOffset = offset; + + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer: new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL) + } + }); +} + +IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; + +/** + * The version of the source mapping spec that we are consuming. + */ +IndexedSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { + get: function () { + var sources = []; + for (var i = 0; i < this._sections.length; i++) { + for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { + sources.push(this._sections[i].consumer.sources[j]); + } + } + return sources; + } +}); + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. The line number + * is 1-based. + * - column: The column number in the generated source. The column + * number is 0-based. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. The + * line number is 1-based. + * - column: The column number in the original source, or null. The + * column number is 0-based. + * - name: The original identifier, or null. + */ +IndexedSourceMapConsumer.prototype.originalPositionFor = + function IndexedSourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + // Find the section containing the generated position we're trying to map + // to an original position. + var sectionIndex = binarySearch.search(needle, this._sections, + function(needle, section) { + var cmp = needle.generatedLine - section.generatedOffset.generatedLine; + if (cmp) { + return cmp; + } + + return (needle.generatedColumn - + section.generatedOffset.generatedColumn); + }); + var section = this._sections[sectionIndex]; + + if (!section) { + return { + source: null, + line: null, + column: null, + name: null + }; + } + + return section.consumer.originalPositionFor({ + line: needle.generatedLine - + (section.generatedOffset.generatedLine - 1), + column: needle.generatedColumn - + (section.generatedOffset.generatedLine === needle.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + bias: aArgs.bias + }); + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = + function IndexedSourceMapConsumer_hasContentsOfAllSources() { + return this._sections.every(function (s) { + return s.consumer.hasContentsOfAllSources(); + }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +IndexedSourceMapConsumer.prototype.sourceContentFor = + function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + var content = section.consumer.sourceContentFor(aSource, true); + if (content) { + return content; + } + } + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. The line number + * is 1-based. + * - column: The column number in the original source. The column + * number is 0-based. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. The + * line number is 1-based. + * - column: The column number in the generated source, or null. + * The column number is 0-based. + */ +IndexedSourceMapConsumer.prototype.generatedPositionFor = + function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + // Only consider this section if the requested source is in the list of + // sources of the consumer. + if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) { + continue; + } + var generatedPosition = section.consumer.generatedPositionFor(aArgs); + if (generatedPosition) { + var ret = { + line: generatedPosition.line + + (section.generatedOffset.generatedLine - 1), + column: generatedPosition.column + + (section.generatedOffset.generatedLine === generatedPosition.line + ? section.generatedOffset.generatedColumn - 1 + : 0) + }; + return ret; + } + } + + return { + line: null, + column: null + }; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +IndexedSourceMapConsumer.prototype._parseMappings = + function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { + this.__generatedMappings = []; + this.__originalMappings = []; + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + var sectionMappings = section.consumer._generatedMappings; + for (var j = 0; j < sectionMappings.length; j++) { + var mapping = sectionMappings[j]; + + var source = section.consumer._sources.at(mapping.source); + source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL); + this._sources.add(source); + source = this._sources.indexOf(source); + + var name = null; + if (mapping.name) { + name = section.consumer._names.at(mapping.name); + this._names.add(name); + name = this._names.indexOf(name); + } + + // The mappings coming from the consumer for the section have + // generated positions relative to the start of the section, so we + // need to offset them to be relative to the start of the concatenated + // generated file. + var adjustedMapping = { + source: source, + generatedLine: mapping.generatedLine + + (section.generatedOffset.generatedLine - 1), + generatedColumn: mapping.generatedColumn + + (section.generatedOffset.generatedLine === mapping.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: name + }; + + this.__generatedMappings.push(adjustedMapping); + if (typeof adjustedMapping.originalLine === 'number') { + this.__originalMappings.push(adjustedMapping); + } + } + } + + this.__generatedMappings.sort(util.compareByGeneratedPositionsDeflated); + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + +export { BasicSourceMapConsumer, IndexedSourceMapConsumer, SourceMapConsumer } \ No newline at end of file diff --git a/modules/internal/source-map/util.js b/modules/internal/source-map/util.js new file mode 100644 index 000000000..0c8588965 --- /dev/null +++ b/modules/internal/source-map/util.js @@ -0,0 +1,485 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors +// Upstream https://github.com/mozilla/source-map/blob/master/lib/util.js +// @ts-nocheck + +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +/** + * This is a helper function for getting values from parameter/options + * objects. + * + * @param args The object we are extracting values from + * @param name The name of the property we are getting. + * @param defaultValue An optional value to return if the property is missing + * from the object. If this is not specified and the property is missing, an + * error will be thrown. + */ +export function getArg(aArgs, aName, aDefaultValue) { + if (aName in aArgs) { + return aArgs[aName]; + } else if (arguments.length === 3) { + return aDefaultValue; + } else { + throw new Error('"' + aName + '" is a required argument.'); + } +} + +var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/; +var dataUrlRegexp = /^data:.+\,.+$/; + +export function urlParse(aUrl) { + var match = aUrl.match(urlRegexp); + if (!match) { + return null; + } + return { + scheme: match[1], + auth: match[2], + host: match[3], + port: match[4], + path: match[5] + }; +} + +export function urlGenerate(aParsedUrl) { + var url = ''; + if (aParsedUrl.scheme) { + url += aParsedUrl.scheme + ':'; + } + url += '//'; + if (aParsedUrl.auth) { + url += aParsedUrl.auth + '@'; + } + if (aParsedUrl.host) { + url += aParsedUrl.host; + } + if (aParsedUrl.port) { + url += ":" + aParsedUrl.port + } + if (aParsedUrl.path) { + url += aParsedUrl.path; + } + return url; +} + +export function isAbsolute(aPath) { + return aPath.charAt(0) === '/' || urlRegexp.test(aPath); +}; + +/** + * Normalizes a path, or the path portion of a URL: + * + * - Replaces consecutive slashes with one slash. + * - Removes unnecessary '.' parts. + * - Removes unnecessary '/..' parts. + * + * Based on code in the Node.js 'path' core module. + * + * @param aPath The path or url to normalize. + */ +export function normalize(aPath) { + var isAbsolute2 = (aPath) => { + return aPath.charAt(0) === '/' || urlRegexp.test(aPath); + }; + var path = aPath; + var url = urlParse(aPath); + if (url) { + if (!url.path) { + return aPath; + } + path = url.path; + } + var isAbsolute = isAbsolute2(path); + + var parts = path.split(/\/+/); + for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { + part = parts[i]; + if (part === '.') { + parts.splice(i, 1); + } else if (part === '..') { + up++; + } else if (up > 0) { + if (part === '') { + // The first part is blank if the path is absolute. Trying to go + // above the root is a no-op. Therefore we can remove all '..' parts + // directly after the root. + parts.splice(i + 1, up); + up = 0; + } else { + parts.splice(i, 2); + up--; + } + } + } + path = parts.join('/'); + + if (path === '') { + path = isAbsolute ? '/' : '.'; + } + + if (url) { + url.path = path; + return urlGenerate(url); + } + return path; +} + +/** + * Joins two paths/URLs. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be joined with the root. + * + * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a + * scheme-relative URL: Then the scheme of aRoot, if any, is prepended + * first. + * - Otherwise aPath is a path. If aRoot is a URL, then its path portion + * is updated with the result and aRoot is returned. Otherwise the result + * is returned. + * - If aPath is absolute, the result is aPath. + * - Otherwise the two paths are joined with a slash. + * - Joining for example 'http://' and 'www.example.com' is also supported. + */ +export function join(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + if (aPath === "") { + aPath = "."; + } + var aPathUrl = urlParse(aPath); + var aRootUrl = urlParse(aRoot); + if (aRootUrl) { + aRoot = aRootUrl.path || '/'; + } + + // `join(foo, '//www.example.org')` + if (aPathUrl && !aPathUrl.scheme) { + if (aRootUrl) { + aPathUrl.scheme = aRootUrl.scheme; + } + return urlGenerate(aPathUrl); + } + + if (aPathUrl || aPath.match(dataUrlRegexp)) { + return aPath; + } + + // `join('http://', 'www.example.com')` + if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { + aRootUrl.host = aPath; + return urlGenerate(aRootUrl); + } + + var joined = aPath.charAt(0) === '/' + ? aPath + : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); + + if (aRootUrl) { + aRootUrl.path = joined; + return urlGenerate(aRootUrl); + } + return joined; +} + +/** + * Make a path relative to a URL or another path. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be made relative to aRoot. + */ +export function relative(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + + aRoot = aRoot.replace(/\/$/, ''); + + // It is possible for the path to be above the root. In this case, simply + // checking whether the root is a prefix of the path won't work. Instead, we + // need to remove components from the root one by one, until either we find + // a prefix that fits, or we run out of components to remove. + var level = 0; + while (aPath.indexOf(aRoot + '/') !== 0) { + var index = aRoot.lastIndexOf("/"); + if (index < 0) { + return aPath; + } + + // If the only part of the root that is left is the scheme (i.e. http://, + // file:///, etc.), one or more slashes (/), or simply nothing at all, we + // have exhausted all components, so the path is not relative to the root. + aRoot = aRoot.slice(0, index); + if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { + return aPath; + } + + ++level; + } + + // Make sure we add a "../" for each component we removed from the root. + return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); +} + +var supportsNullProto = (function () { + var obj = Object.create(null); + return !('__proto__' in obj); +}()); + +function identity (s) { + return s; +} + +/** + * Because behavior goes wacky when you set `__proto__` on objects, we + * have to prefix all the strings in our set with an arbitrary character. + * + * See https://github.com/mozilla/source-map/pull/31 and + * https://github.com/mozilla/source-map/issues/30 + * + * @param String aStr + */ +function toSetString2(aStr) { + if (isProtoString(aStr)) { + return '$' + aStr; + } + + return aStr; +} +export const toSetString = supportsNullProto ? identity : toSetString2; + +function fromSetString2(aStr) { + if (isProtoString(aStr)) { + return aStr.slice(1); + } + + return aStr; +} +export const fromSetString = supportsNullProto ? identity : fromSetString2; + +function isProtoString(s) { + if (!s) { + return false; + } + + var length = s.length; + + if (length < 9 /* "__proto__".length */) { + return false; + } + + if (s.charCodeAt(length - 1) !== 95 /* '_' */ || + s.charCodeAt(length - 2) !== 95 /* '_' */ || + s.charCodeAt(length - 3) !== 111 /* 'o' */ || + s.charCodeAt(length - 4) !== 116 /* 't' */ || + s.charCodeAt(length - 5) !== 111 /* 'o' */ || + s.charCodeAt(length - 6) !== 114 /* 'r' */ || + s.charCodeAt(length - 7) !== 112 /* 'p' */ || + s.charCodeAt(length - 8) !== 95 /* '_' */ || + s.charCodeAt(length - 9) !== 95 /* '_' */) { + return false; + } + + for (var i = length - 10; i >= 0; i--) { + if (s.charCodeAt(i) !== 36 /* '$' */) { + return false; + } + } + + return true; +} + +/** + * Comparator between two mappings where the original positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same original source/line/column, but different generated + * line and column the same. Useful when searching for a mapping with a + * stubbed out mapping. + */ +export function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { + var cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0 || onlyCompareOriginal) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} + +/** + * Comparator between two mappings with deflated source and name indices where + * the generated positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same generated line and column, but different + * source/name/original line and column the same. Useful when searching for a + * mapping with a stubbed out mapping. + */ +export function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0 || onlyCompareGenerated) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} + +function strcmp(aStr1, aStr2) { + if (aStr1 === aStr2) { + return 0; + } + + if (aStr1 === null) { + return 1; // aStr2 !== null + } + + if (aStr2 === null) { + return -1; // aStr1 !== null + } + + if (aStr1 > aStr2) { + return 1; + } + + return -1; +} + +/** + * Comparator between two mappings with inflated source and name strings where + * the generated positions are compared. + */ +export function compareByGeneratedPositionsInflated(mappingA, mappingB) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} + +/** + * Strip any JSON XSSI avoidance prefix from the string (as documented + * in the source maps specification), and then parse the string as + * JSON. + */ +export function parseSourceMapInput(str) { + return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, '')); +} + +/** + * Compute the URL of a source given the the source root, the source's + * URL, and the source map's URL. + */ +export function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) { + sourceURL = sourceURL || ''; + + if (sourceRoot) { + // This follows what Chrome does. + if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') { + sourceRoot += '/'; + } + // The spec says: + // Line 4: An optional source root, useful for relocating source + // files on a server or removing repeated values in the + // “sources” entry. This value is prepended to the individual + // entries in the “source” field. + sourceURL = sourceRoot + sourceURL; + } + + // Historically, SourceMapConsumer did not take the sourceMapURL as + // a parameter. This mode is still somewhat supported, which is why + // this code block is conditional. However, it's preferable to pass + // the source map URL to SourceMapConsumer, so that this function + // can implement the source URL resolution algorithm as outlined in + // the spec. This block is basically the equivalent of: + // new URL(sourceURL, sourceMapURL).toString() + // ... except it avoids using URL, which wasn't available in the + // older releases of node still supported by this library. + // + // The spec says: + // If the sources are not absolute URLs after prepending of the + // “sourceRoot”, the sources are resolved relative to the + // SourceMap (like resolving script src in a html document). + if (sourceMapURL) { + var parsed = urlParse(sourceMapURL); + if (!parsed) { + throw new Error("sourceMapURL could not be parsed"); + } + if (parsed.path) { + // Strip the last path component, but keep the "/". + var index = parsed.path.lastIndexOf('/'); + if (index >= 0) { + parsed.path = parsed.path.substring(0, index + 1); + } + } + sourceURL = join(urlGenerate(parsed), sourceURL); + } + + return normalize(sourceURL); +} diff --git a/modules/print.cpp b/modules/print.cpp index 9f029ef8e..463b046a9 100644 --- a/modules/print.cpp +++ b/modules/print.cpp @@ -210,8 +210,7 @@ static bool warn_deprecated_once_per_callsite(JSContext* cx, unsigned argc, return true; } - std::vector format_args_str; - std::vector format_args; + std::vector format_args; for (size_t ix = 1; ix < args.length(); ix++) { g_assert(args[ix].isString() && "warnDeprecatedOncePerCallsite subsequent arguments must be " @@ -220,8 +219,7 @@ static bool warn_deprecated_once_per_callsite(JSContext* cx, unsigned argc, JS::UniqueChars format_arg = JS_EncodeStringToUTF8(cx, v_format_arg); if (!format_arg) return false; - format_args_str.emplace_back(format_arg.get()); - format_args.emplace_back(format_args_str.back().c_str()); + format_args.emplace_back(format_arg.get()); } _gjs_warn_deprecated_once_per_callsite( @@ -240,13 +238,13 @@ static constexpr JSFunctionSpec funcs[] = { JS_FN("warnDeprecatedOncePerCallsite", warn_deprecated_once_per_callsite, 1, GJS_MODULE_PROP_FLAGS), JS_FS_END}; -// clang-format on static constexpr JSPropertySpec props[] = { JSPropertySpec::int32Value("PLATFORM_SPECIFIC_TYPELIB", GJS_MODULE_PROP_FLAGS, GjsDeprecationMessageId::PlatformSpecificTypelib), JS_PS_END}; +// clang-format on bool gjs_define_print_stuff(JSContext* context, JS::MutableHandleObject module) { diff --git a/modules/script/.eslintrc.yml b/modules/script/.eslintrc.yml deleted file mode 100644 index 6c9c0253c..000000000 --- a/modules/script/.eslintrc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - jsdoc/require-jsdoc: 'off' diff --git a/modules/script/_bootstrap/.eslintrc.yml b/modules/script/_bootstrap/.eslintrc.yml deleted file mode 100644 index fda875779..000000000 --- a/modules/script/_bootstrap/.eslintrc.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - require-jsdoc: "off" -globals: - log: "off" - logError: "off" - print: "off" - printerr: "off" diff --git a/modules/script/_bootstrap/debugger.js b/modules/script/_bootstrap/debugger.js index 84fd8d7f0..b440090c7 100644 --- a/modules/script/_bootstrap/debugger.js +++ b/modules/script/_bootstrap/debugger.js @@ -1,5 +1,5 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ -/* global debuggee, quit, loadNative, readline, uneval */ +/* global debuggee, quit, loadNative, readline, uneval, getSourceMapRegistry */ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2011 Mozilla Foundation and contributors @@ -40,6 +40,29 @@ function dvToString(v) { return s; } +// Build a nested tree of all private fields wherever they reside. Each level has KV tuples and their descendents: +// {cur: [[key1, value1], ...], children: {key1: {...next level}}} +function getProperties(dv, result, seen = new WeakSet()) { + if (!dv || seen.has(dv)) + return; + + if (typeof dv === 'object') + seen.add(dv); + + const privateKVs = dv.getOwnPrivateProperties?.().map(k => [k.description, dv.getProperty(k).return]) ?? []; + const nonPrivateKVs = dv.getOwnPropertyNames?.().concat(dv.getOwnPropertySymbols()).map(k => [k, dv.getProperty(k).return]) ?? []; + result.cur = privateKVs; + + result.children = {}; + // a private field can be under a non-private field + privateKVs.concat(nonPrivateKVs).forEach(([k, v]) => { + result.children[k] = {}; + getProperties(v, result.children[k], seen); + }); + // prettyPrint in the debuggee compartment needs access to the original private field value and not Debugger.Object + result.cur.forEach(kv => kv[1]?.unsafeDereference && (kv[1] = kv[1].unsafeDereference())); +} + function debuggeeValueToString(dv, style = {pretty: options.pretty}) { // Special sentinel values returned by Debugger.Environment.getVariable() if (typeof dv === 'object' && dv !== null) { @@ -67,7 +90,9 @@ function debuggeeValueToString(dv, style = {pretty: options.pretty}) { if (style.brief) return [dvrepr, dvrepr]; - const str = exec('imports._print.getPrettyPrintFunction(globalThis)(v)', {v: dv}); + const properties = {}; + getProperties(dv, properties); + const str = exec('imports._print.getPrettyPrintFunction(globalThis)(v, extra)', {v: dv, extra: dv.makeDebuggeeValue(properties)}); if ('throw' in str) { if (style.noerror) return [dvrepr, undefined]; @@ -137,10 +162,34 @@ Object.defineProperty(Debugger.Frame.prototype, 'line', { }, }); +Object.defineProperty(Debugger.Frame.prototype, 'column', { + configurable: true, + enumerable: false, + get() { + return this.script?.getOffsetLocation(this.offset).columnNumber ?? null; + }, +}); + Debugger.Script.prototype.describeOffset = function describeOffset(offset) { const {lineNumber, columnNumber} = this.getOffsetLocation(offset); const url = this.url || ''; - return `${url}:${lineNumber}:${columnNumber}`; + const registry = getSourceMapRegistry(); + const consumer = registry.get(url); + + let description = `${url}:${lineNumber}:${columnNumber}`; + const original = consumer?.originalPositionFor({line: lineNumber, column: columnNumber}); + + if (original?.source || Number.isInteger(original?.line) || Number.isInteger(original?.column)) + description += ' -> '; + + if (original?.source) + description += original.source; + if (Number.isInteger(original?.line)) + description += `:${original.line}`; + if (Number.isInteger(original?.column)) + description += `:${original.column + 1}`; + + return description; }; function showFrame(f, n, option = {btCommand: false, fullOption: false}) { @@ -262,9 +311,9 @@ function listCommand(option) { print('No frame to list from'); return; } - let lineNumber = focusedFrame.line; + let lineNumber = focusedFrame.line, columnNumber = focusedFrame.column; if (option === '') { - printSurroundingLines(lineNumber); + printSurroundingLines(lineNumber, columnNumber); return; } let currentLine = Number(option); @@ -275,11 +324,23 @@ function listCommand(option) { print('Unknown option'); } -function printSurroundingLines(currentLine = 1) { - let sourceLines = focusedFrame.script.source.text.split('\n'); - let lastLine = sourceLines.length - 1; +function printSurroundingLines(currentLine = 1, columnNumber = 1) { + const registry = getSourceMapRegistry(); + const sourceUrl = focusedFrame.script.source.url; + const consumer = registry.get(sourceUrl); + const originalObj = consumer?.originalPositionFor({line: currentLine, column: columnNumber - 1}); + const sourceMapContents = originalObj?.source ? consumer?.sourceContentFor(originalObj.source, true) : null; + let sourceLines; + if (sourceMapContents) { + sourceLines = sourceMapContents.split('\n'); + currentLine = originalObj?.line ?? 1; + } else { + sourceLines = focusedFrame.script.source.text.split('\n'); + } + const lastLine = sourceLines.length; let maxLineLimit = Math.min(lastLine, currentLine + 5); let minLineLimit = Math.max(1, currentLine - 5); + for (let i = minLineLimit; i < maxLineLimit + 1; i++) { if (i === currentLine) { const code = colorCode('1'); @@ -395,9 +456,11 @@ function keysCommand(rest) { } const names = dv.getOwnPropertyNames(); const symbols = dv.getOwnPropertySymbols(); + const privateFields = dv.getOwnPrivateProperties(); const keys = [ ...names.map(s => `"${s}"`), ...symbols.map(s => `Symbol("${s.description}")`), + ...privateFields.map(s => `${s.description}`), ]; if (keys.length === 0) print('No own properties'); @@ -940,6 +1003,9 @@ function onInitialEnterFrame(frame) { var dbg = new Debugger(); dbg.onNewPromise = function ({promiseID, promiseAllocationSite}) { + // if the promise was not allocated by the script, allocation site is null + if (!promiseAllocationSite) + return undefined; const site = promiseAllocationSite.toString().split('\n')[0]; print(`Promise ${promiseID} started from ${site}`); return undefined; diff --git a/modules/script/_bootstrap/default.js b/modules/script/_bootstrap/default.js index 871d2bf74..5b6b1918c 100644 --- a/modules/script/_bootstrap/default.js +++ b/modules/script/_bootstrap/default.js @@ -38,17 +38,25 @@ value.toString === Date.prototype.toString; } - function prettyPrint(value) { + function prettyPrint(value, extra) { switch (typeof value) { case 'object': if (value === null) return 'null'; - if (_hasStandardToString(value)) { - const printedObjects = new WeakSet(); - return formatObject(value, printedObjects); + if (_hasStandardToString(value)) + return formatObject(value, extra); + + if (!value.toString) { + let str = formatObject(value, extra); + // object has null prototype either from Object.create(null) or cases like the module namespace object + if (Object.getPrototypeOf(value) === null) + str = `[Object: null prototype] ${str}`; + + return str; } - // If the object has a nonstandard toString, prefer that + + // Prefer the non-standard toString return value.toString(); case 'function': if (value.toString === Function.prototype.toString) @@ -71,18 +79,21 @@ return `${key}`; } - function formatObject(obj, printedObjects) { + function formatObject(obj, properties, printedObjects = new WeakSet()) { printedObjects.add(obj); if (Array.isArray(obj) || _isTypedArray(obj)) - return formatArray(obj, printedObjects).toString(); + return formatArray(obj, properties, printedObjects).toString(); if (obj instanceof Date) return formatDate(obj); const formattedObject = []; - const keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)); - for (const propertyKey of keys) { - const value = obj[propertyKey]; + let keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).map(k => [k, obj[k]]); + // properties is only passed to us in the debugger + if (properties?.cur) + keys = keys.concat(properties.cur); + + for (const [propertyKey, value] of keys) { const key = formatPropertyKey(propertyKey); switch (typeof value) { case 'object': @@ -91,7 +102,7 @@ else if (value === null) formattedObject.push(`${key}: null`); else if (_hasStandardToString(value)) - formattedObject.push(`${key}: ${formatObject(value, printedObjects)}`); + formattedObject.push(`${key}: ${formatObject(value, properties?.children[propertyKey], printedObjects)}`); else formattedObject.push(`${key}: ${value.toString()}`); break; @@ -113,13 +124,13 @@ : `{ ${formattedObject.join(', ')} }`; } - function formatArray(arr, printedObjects) { + function formatArray(arr, properties, printedObjects) { const formattedArray = []; for (const [key, value] of arr.entries()) { if (printedObjects.has(value)) formattedArray[key] = '[Circular]'; else - formattedArray[key] = prettyPrint(value); + formattedArray[key] = prettyPrint(value, properties?.children[key]); } return `[${formattedArray.join(', ')}]`; } diff --git a/modules/script/package.js b/modules/script/package.js index 9e5d217d6..61d9666e7 100644 --- a/modules/script/package.js +++ b/modules/script/package.js @@ -9,7 +9,7 @@ requireSymbol, run, start, version */ * This module provides a set of convenience APIs for building packaged * applications. */ -imports.gi.versions.GIRepository = '2.0'; +imports.gi.versions.GIRepository = '3.0'; const GLib = imports.gi.GLib; const GIRepository = imports.gi.GIRepository; @@ -54,10 +54,6 @@ function _runningFromMesonSource() { GLib.getenv('MESON_SOURCE_ROOT'); } -function _makeNamePath(n) { - return `/${n.replace(/\./g, '/')}`; -} - /** * Initialize directories and global variables. Must be called * before any of other API in Package is used. @@ -116,10 +112,17 @@ function init(params) { datadir = GLib.build_filenamev([prefix, 'share']); let libpath, girpath; + const loadResource = (path, baseName) => { + const resource = Gio.Resource.load(GLib.build_filenamev([path, + `${baseName}.src.gresource`])); + resource._register(); + + return `resource:///${baseName.replaceAll('.', '/')}/js`; + }; + if (_runningFromMesonSource()) { log('Running from Meson, using local files'); let bld = GLib.getenv('MESON_BUILD_ROOT'); - let src = GLib.getenv('MESON_SOURCE_ROOT'); pkglibdir = libpath = girpath = GLib.build_filenamev([bld, 'lib']); pkgdatadir = GLib.build_filenamev([bld, 'data']); @@ -127,13 +130,17 @@ function init(params) { _submoduledir = GLib.build_filenamev([bld, 'subprojects']); GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true); + const bldPath = GLib.build_filenamev([bld, 'src']); try { - let resource = Gio.Resource.load(GLib.build_filenamev([bld, 'src', - `${name}.src.gresource`])); - resource._register(); - moduledir = `resource://${_makeNamePath(name)}/js`; + moduledir = loadResource(bldPath, name); } catch (e) { - moduledir = GLib.build_filenamev([src, 'src']); + try { + moduledir = loadResource(bldPath, _pkgname); + name = _pkgname; + } catch { + const src = GLib.getenv('MESON_SOURCE_ROOT'); + moduledir = GLib.build_filenamev([src, 'src']); + } } } else if (_runningFromSource()) { log('Running from source tree, using local files'); @@ -157,25 +164,34 @@ function init(params) { localedir = GLib.build_filenamev([datadir, 'locale']); try { - let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, - `${name}.src.gresource`])); - resource._register(); - - moduledir = `resource://${_makeNamePath(name)}/js`; + moduledir = loadResource(pkgdatadir, name); } catch (e) { - moduledir = pkgdatadir; + try { + moduledir = loadResource(pkgdatadir, _pkgname); + name = _pkgname; + } catch { + moduledir = pkgdatadir; + } } } + const giRepo = GIRepository.Repository.dup_default(); + imports.searchPath.unshift(moduledir); - GIRepository.Repository.prepend_search_path(girpath); - GIRepository.Repository.prepend_library_path(libpath); + giRepo.prepend_search_path(girpath); + giRepo.prepend_library_path(libpath); try { let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, `${name}.data.gresource`])); resource._register(); - } catch (e) { } + } catch (e) { + try { + let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, + `${_pkgname}.data.gresource`])); + resource._register(); + } catch {} + } } /** diff --git a/modules/script/tweener/.eslintrc.yml b/modules/script/tweener/.eslintrc.yml deleted file mode 100644 index 493d9fed4..000000000 --- a/modules/script/tweener/.eslintrc.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Evan Welsh -rules: - jsdoc/require-param: 'off' - jsdoc/require-param-type: 'off' - jsdoc/require-returns: 'off' - jsdoc/require-jsdoc: 'off' - jsdoc/check-tag-names: 'off' - jsdoc/check-param-names: 'off' diff --git a/modules/system.cpp b/modules/system.cpp index 7cc3e0ef1..9e0b1152d 100644 --- a/modules/system.cpp +++ b/modules/system.cpp @@ -16,6 +16,7 @@ #include #include // for ResetTimeZone +#include // for ReportUncatchableException #include // for JS_GC #include #include @@ -30,6 +31,7 @@ #include "gi/object.h" #include "cjs/atoms.h" +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" @@ -54,7 +56,7 @@ gjs_address_of(JSContext *context, "object", &target_obj)) return false; - GjsAutoChar pointer_string = g_strdup_printf("%p", target_obj.get()); + Gjs::AutoChar pointer_string{g_strdup_printf("%p", target_obj.get())}; return gjs_string_from_utf8(context, pointer_string, argv.rval()); } @@ -74,7 +76,7 @@ static bool gjs_address_of_gobject(JSContext* cx, unsigned argc, return false; } - GjsAutoChar pointer_string = g_strdup_printf("%p", obj); + Gjs::AutoChar pointer_string{g_strdup_printf("%p", obj)}; return gjs_string_from_utf8(cx, pointer_string, argv.rval()); } @@ -126,7 +128,7 @@ gjs_dump_heap(JSContext *cx, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - GjsAutoChar filename; + Gjs::AutoChar filename; if (!gjs_parse_call_args(cx, "dumpHeap", args, "|F", "filename", &filename)) return false; @@ -172,7 +174,8 @@ gjs_exit(JSContext *context, GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); gjs->exit(ecode); - return false; /* without gjs_throw() == "throw uncatchable exception" */ + JS::ReportUncatchableException(context); + return false; } static bool gjs_clear_date_caches(JSContext*, unsigned argc, JS::Value* vp) { @@ -192,9 +195,9 @@ static bool write_gc_info(const char16_t* buf, uint32_t len, void* data) { auto* fp = static_cast(data); long bytes_written; // NOLINT(runtime/int): the GLib API requires this type - GjsAutoChar utf8 = g_utf16_to_utf8(reinterpret_cast(buf), + Gjs::AutoChar utf8{g_utf16_to_utf8(reinterpret_cast(buf), len, /* items_read = */ nullptr, - &bytes_written, /* error = */ nullptr); + &bytes_written, /* error = */ nullptr)}; if (!utf8) utf8 = g_strdup(""); @@ -205,7 +208,7 @@ static bool write_gc_info(const char16_t* buf, uint32_t len, void* data) { static bool gjs_dump_memory_info(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - GjsAutoChar filename; + Gjs::AutoChar filename; if (!gjs_parse_call_args(cx, "dumpMemoryInfo", args, "|F", "filename", &filename)) return false; diff --git a/subprojects/.wraplock b/subprojects/.wraplock new file mode 100644 index 000000000..e69de29bb diff --git a/subprojects/gobject-introspection-tests.wrap b/subprojects/gobject-introspection-tests.wrap index 9f288cb82..9a3e71b65 100644 --- a/subprojects/gobject-introspection-tests.wrap +++ b/subprojects/gobject-introspection-tests.wrap @@ -4,7 +4,7 @@ [wrap-git] directory=gobject-introspection-tests url=https://gitlab.gnome.org/GNOME/gobject-introspection-tests.git -revision=7adb7396928a0964a9d3ef7626f8d2b9639be50d +revision=67a03e86da0636c02cac2390ee2f0d5d981875a8 depth=1 [provide] diff --git a/subprojects/gobject-introspection-tests/.clang-format b/subprojects/gobject-introspection-tests/.clang-format index 8e24c324d..7b82a7654 100644 --- a/subprojects/gobject-introspection-tests/.clang-format +++ b/subprojects/gobject-introspection-tests/.clang-format @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento BasedOnStyle: GNU diff --git a/subprojects/gobject-introspection-tests/.editorconfig b/subprojects/gobject-introspection-tests/.editorconfig index 7d46d89e3..3f441c611 100644 --- a/subprojects/gobject-introspection-tests/.editorconfig +++ b/subprojects/gobject-introspection-tests/.editorconfig @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento root = true diff --git a/subprojects/gobject-introspection-tests/.gitlab-ci.yml b/subprojects/gobject-introspection-tests/.gitlab-ci.yml new file mode 100644 index 000000000..8703f9960 --- /dev/null +++ b/subprojects/gobject-introspection-tests/.gitlab-ci.yml @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Philip Chimento + +include: + - project: Infrastructure/freedesktop-ci-templates + file: templates/alpine.yml + ref: 6a40df92957c8ce9ee741aaccc5daaaf70545b1e + +stages: + - image-build + - check + +variables: + FDO_UPSTREAM_REPO: GNOME/gobject-introspection-tests + +.gitests.alpine: + variables: + FDO_DISTRIBUTION_TAG: '2024-09-11.0' + +build-alpine-image: + extends: + - .fdo.container-build@alpine + - .gitests.alpine + stage: image-build + variables: + FDO_DISTRIBUTION_PACKAGES: reuse + +license-check: + extends: + - .fdo.distribution-image@alpine + - .gitests.alpine + stage: check + dependencies: [] # don't check artifacts from container build + script: + - reuse --version + - reuse lint diff --git a/subprojects/gobject-introspection-tests/.reuse/dep5 b/subprojects/gobject-introspection-tests/.reuse/dep5 new file mode 100644 index 000000000..ba8bb7019 --- /dev/null +++ b/subprojects/gobject-introspection-tests/.reuse/dep5 @@ -0,0 +1,5 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: docs/* README.md +Copyright: No rights reserved +License: CC0-1.0 diff --git a/subprojects/gobject-introspection-tests/COPYING b/subprojects/gobject-introspection-tests/COPYING deleted file mode 100644 index 2f70ea47c..000000000 --- a/subprojects/gobject-introspection-tests/COPYING +++ /dev/null @@ -1,13 +0,0 @@ -gobject-introspection has two licenses; one for the typelib library, -and one for the tools. - -* The typelib libraries (girepository/) are licensed under the LGPLv2+. - See the file COPYING.LGPL. - -* The remaining code is GPLv2+ compatible (see the file COPYING.GPL) and - consists of a mix of GPLv2+, LGPLv2+ and MIT. See the license headers in - each file for details. - -In general where applicable files should have headers denoting their license -status; if they do not, please file a bug at -https://gitlab.gnome.org/GNOME/gobject-introspection/issues. diff --git a/subprojects/gobject-introspection-tests/COPYING.GPL b/subprojects/gobject-introspection-tests/COPYING.GPL deleted file mode 100644 index 7ac467c0e..000000000 --- a/subprojects/gobject-introspection-tests/COPYING.GPL +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/subprojects/gobject-introspection-tests/COPYING.LGPL b/subprojects/gobject-introspection-tests/COPYING.LGPL deleted file mode 100644 index 2d8788b4d..000000000 --- a/subprojects/gobject-introspection-tests/COPYING.LGPL +++ /dev/null @@ -1,481 +0,0 @@ - GNU LIBRARY GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the library GPL. It is - numbered 2 because it goes with version 2 of the ordinary GPL.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Library General Public License, applies to some -specially designated Free Software Foundation software, and to any -other libraries whose authors decide to use it. You can use it for -your libraries, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if -you distribute copies of the library, or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link a program with the library, you must provide -complete object files to the recipients so that they can relink them -with the library, after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - Our method of protecting your rights has two steps: (1) copyright -the library, and (2) offer you this license which gives you legal -permission to copy, distribute and/or modify the library. - - Also, for each distributor's protection, we want to make certain -that everyone understands that there is no warranty for this free -library. If the library is modified by someone else and passed on, we -want its recipients to know that what they have is not the original -version, so that any problems introduced by others will not reflect on -the original authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that companies distributing free -software will individually obtain patent licenses, thus in effect -transforming the program into proprietary software. To prevent this, -we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. - - Most GNU software, including some libraries, is covered by the ordinary -GNU General Public License, which was designed for utility programs. This -license, the GNU Library General Public License, applies to certain -designated libraries. This license is quite different from the ordinary -one; be sure to read it in full, and don't assume that anything in it is -the same as in the ordinary license. - - The reason we have a separate public license for some libraries is that -they blur the distinction we usually make between modifying or adding to a -program and simply using it. Linking a program with a library, without -changing the library, is in some sense simply using the library, and is -analogous to running a utility program or application program. However, in -a textual and legal sense, the linked executable is a combined work, a -derivative of the original library, and the ordinary General Public License -treats it as such. - - Because of this blurred distinction, using the ordinary General -Public License for libraries did not effectively promote software -sharing, because most developers did not use the libraries. We -concluded that weaker conditions might promote sharing better. - - However, unrestricted linking of non-free programs would deprive the -users of those programs of all benefit from the free status of the -libraries themselves. This Library General Public License is intended to -permit developers of non-free programs to use free libraries, while -preserving your freedom as a user of such programs to change the free -libraries that are incorporated in them. (We have not seen how to achieve -this as regards changes in header files, but we have achieved it as regards -changes in the actual functions of the Library.) The hope is that this -will lead to faster development of free libraries. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, while the latter only -works together with the library. - - Note that it is possible for a library to be covered by the ordinary -General Public License rather than by this special one. - - GNU LIBRARY GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library which -contains a notice placed by the copyright holder or other authorized -party saying it may be distributed under the terms of this Library -General Public License (also called "this License"). Each licensee is -addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also compile or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - c) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - d) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the source code distributed need not include anything that is normally -distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Library General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/subprojects/gobject-introspection-tests/LICENSES/CC0-1.0.txt b/subprojects/gobject-introspection-tests/LICENSES/CC0-1.0.txt new file mode 100644 index 000000000..0e259d42c --- /dev/null +++ b/subprojects/gobject-introspection-tests/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/subprojects/gobject-introspection-tests/LICENSES/GPL-2.0-or-later.txt b/subprojects/gobject-introspection-tests/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 000000000..17cb28643 --- /dev/null +++ b/subprojects/gobject-introspection-tests/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,117 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + +signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice diff --git a/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.0-or-later.txt b/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 000000000..843b00b56 --- /dev/null +++ b/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,175 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1991 Free Software Foundation, Inc. +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. + +The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. + +However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. + +GNU LIBRARY GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice + +That's all there is to it! diff --git a/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.1-or-later.txt b/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 000000000..c6487f4fd --- /dev/null +++ b/subprojects/gobject-introspection-tests/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,176 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/subprojects/gobject-introspection-tests/LICENSES/MIT.txt b/subprojects/gobject-introspection-tests/LICENSES/MIT.txt new file mode 100644 index 000000000..2071b23b0 --- /dev/null +++ b/subprojects/gobject-introspection-tests/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/subprojects/gobject-introspection-tests/annotation.c b/subprojects/gobject-introspection-tests/annotation.c index a60239adf..55576c29e 100644 --- a/subprojects/gobject-introspection-tests/annotation.c +++ b/subprojects/gobject-introspection-tests/annotation.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008-2009 Dan Winship SPDX-FileCopyrightText: 2008-2010 Colin Walters SPDX-FileCopyrightText: 2008-2011 Johan Dahlin @@ -79,6 +80,15 @@ regress_annotation_object_get_property (GObject *object, } } +static void +regress_annotation_object_finalize (GObject *gobj) +{ + RegressAnnotationObject *self = (RegressAnnotationObject *)gobj; + if (self->destroy_notify) + self->destroy_notify(self->user_data); + G_OBJECT_CLASS(regress_annotation_object_parent_class)->finalize(gobj); +} + static void regress_annotation_object_class_init (RegressAnnotationObjectClass *klass) { @@ -88,6 +98,7 @@ regress_annotation_object_class_init (RegressAnnotationObjectClass *klass) gobject_class->set_property = regress_annotation_object_set_property; gobject_class->get_property = regress_annotation_object_get_property; + gobject_class->finalize = regress_annotation_object_finalize; /** * RegressAnnotationObject::string-signal: @@ -637,11 +648,13 @@ regress_annotation_object_watch (RegressAnnotationObject *object G_GNUC_UNUSED, * Test overriding via the "Rename To" annotation. */ void -regress_annotation_object_watch_full (RegressAnnotationObject *object G_GNUC_UNUSED, +regress_annotation_object_watch_full (RegressAnnotationObject *object, RegressAnnotationForeachFunc func G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED, - GDestroyNotify destroy G_GNUC_UNUSED) + gpointer user_data, + GDestroyNotify destroy) { + object->user_data = user_data; + object->destroy_notify = destroy; } /** @@ -725,6 +738,9 @@ regress_annotation_object_extra_annos (RegressAnnotationObject *object G_GNUC_UN { } +static void *regress_annotation_user_data; +static GDestroyNotify regress_annotation_destroy_notify; + /** * regress_annotation_custom_destroy: * @callback: (destroy destroy) (closure data): Destroy notification @@ -734,9 +750,24 @@ regress_annotation_object_extra_annos (RegressAnnotationObject *object G_GNUC_UN */ void regress_annotation_custom_destroy (RegressAnnotationCallback callback G_GNUC_UNUSED, - RegressAnnotationNotifyFunc destroy G_GNUC_UNUSED, - gpointer data G_GNUC_UNUSED) + RegressAnnotationNotifyFunc destroy, + gpointer data) { + regress_annotation_user_data = data; + regress_annotation_destroy_notify = destroy; +} + +/** + * regress_annotation_custom_destroy_cleanup: + * + * Use in tests for bindings with managed memory, to clean up the callback + * passed to regress_annotation_custom_destroy(). + */ +void +regress_annotation_custom_destroy_cleanup (void) +{ + if (regress_annotation_destroy_notify) + regress_annotation_destroy_notify(regress_annotation_user_data); } /** @@ -807,7 +838,8 @@ regress_annotation_test_parsing_bug630862 (void) return NULL; } -/** +/* clang-format off */ +/** * regress_annotation_space_after_comment_bug631690: * * Explicitly test having a space after the ** here. @@ -816,6 +848,7 @@ void regress_annotation_space_after_comment_bug631690 (void) { } +/* clang-format on */ /** * regress_annotation_return_filename: diff --git a/subprojects/gobject-introspection-tests/annotation.h b/subprojects/gobject-introspection-tests/annotation.h index 7a620d3e2..4fc714799 100644 --- a/subprojects/gobject-introspection-tests/annotation.h +++ b/subprojects/gobject-introspection-tests/annotation.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008-2009 Dan Winship SPDX-FileCopyrightText: 2008-2010 Colin Walters SPDX-FileCopyrightText: 2008-2011 Johan Dahlin @@ -76,6 +77,9 @@ typedef void (*RegressAnnotationForeachFunc) (RegressAnnotationObject *object, struct _RegressAnnotationObject { GObject parent_instance; + /*< private >*/ + void *user_data; + GDestroyNotify destroy_notify; }; struct _RegressAnnotationObjectClass @@ -232,6 +236,9 @@ void regress_annotation_custom_destroy (RegressAnnotationCallback callback, RegressAnnotationNotifyFunc destroy, gpointer data); +GI_TEST_EXTERN +void regress_annotation_custom_destroy_cleanup (void); + GI_TEST_EXTERN char *regress_annotation_get_source_file (void); diff --git a/subprojects/gobject-introspection-tests/drawable.c b/subprojects/gobject-introspection-tests/drawable.c index 0c64159b3..6e9b26c87 100644 --- a/subprojects/gobject-introspection-tests/drawable.c +++ b/subprojects/gobject-introspection-tests/drawable.c @@ -1,9 +1,12 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008 Colin Walters SPDX-FileCopyrightText: 2008 Johan Bilien SPDX-FileCopyrightText: 2008 Johan Dahlin */ +#include + #include "drawable.h" G_DEFINE_ABSTRACT_TYPE (RegressTestInheritDrawable, regress_test_inherit_drawable, G_TYPE_OBJECT); @@ -60,5 +63,5 @@ regress_test_inherit_drawable_do_foo_maybe_throw (RegressTestInheritDrawable *dr GError **error) { if (x != 42) - g_set_error (error, 0, 12, "The answer should be 42!"); + g_set_error (error, G_IO_ERROR, 12, "The answer should be 42!"); } diff --git a/subprojects/gobject-introspection-tests/drawable.h b/subprojects/gobject-introspection-tests/drawable.h index 95bd9dc79..dff0d9875 100644 --- a/subprojects/gobject-introspection-tests/drawable.h +++ b/subprojects/gobject-introspection-tests/drawable.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008 Colin Walters SPDX-FileCopyrightText: 2008 Johan Bilien SPDX-FileCopyrightText: 2008 Johan Dahlin diff --git a/subprojects/gobject-introspection-tests/foo.c b/subprojects/gobject-introspection-tests/foo.c index ad5b2c318..80a821a06 100644 --- a/subprojects/gobject-introspection-tests/foo.c +++ b/subprojects/gobject-introspection-tests/foo.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2007-2010 Johan Dahlin SPDX-FileCopyrightText: 2008-2010 Colin Walters SPDX-FileCopyrightText: 2008 Jürg Billeter @@ -13,6 +14,7 @@ SPDX-FileCopyrightText: 2011 Torsten Schönfeld SPDX-FileCopyrightText: 2018 Christoph Reiter SPDX-FileCopyrightText: 2020 Centricular SPDX-FileCopyrightText: 2024 Simon McVittie +SPDX-FileCopyrightText: 2024 Philip Chimento */ #include @@ -30,12 +32,8 @@ SPDX-FileCopyrightText: 2024 Simon McVittie object */ typedef struct _RegressFooHidden RegressFooHidden; -int regress_foo_init_argv (int argc, char **argv); -int regress_foo_init_argv_address (int *argc, char ***argv); void regress_foo_private_function (RegressFooObject *regress_foo); -void regress_foo_test_unsigned (unsigned int uint); void regress_foo_do_regress_foo (RegressFooInterface *self, int x); -int regress_foo_enum_method (RegressFooEnumType regress_foo_enum); RegressFooHidden *regress_foo_hidden_copy (const RegressFooHidden *boxed); void regress_foo_hidden_free (RegressFooHidden *boxed); GType regress_foo_hidden_get_type (void); @@ -268,6 +266,13 @@ regress_foo_object_external_type (RegressFooObject *object G_GNUC_UNUSED) return NULL; } +void +regress_foo_object_various (RegressFooObject *object G_GNUC_UNUSED, + void *data G_GNUC_UNUSED, + GType some_type G_GNUC_UNUSED) +{ +} + void regress_foo_object_take_all (RegressFooObject *object G_GNUC_UNUSED, int x G_GNUC_UNUSED, @@ -318,6 +323,20 @@ regress_foo_object_dup_name (RegressFooObject *object G_GNUC_UNUSED) return g_strdup ("regress_foo"); } +void +regress_foo_object_handle_glyph (RegressFooObject *object G_GNUC_UNUSED, UtilityGlyph glyph G_GNUC_UNUSED) +{ +} + +gboolean +regress_foo_object_virtual_method (RegressFooObject *object, int first_param) +{ + RegressFooObjectClass *klass = REGRESS_FOO_OBJECT_GET_CLASS (object); + if (!klass->virtual_method) + return FALSE; + return klass->virtual_method (object, first_param); +} + /** * regress_foo_object_read: (virtual read_fn) * @object: obj @@ -333,6 +352,17 @@ regress_foo_object_read (RegressFooObject *object G_GNUC_UNUSED, { } +/** + * regress_foo_object_static_meth: + * + * Returns: + */ +int +regress_foo_object_static_meth (void) +{ + return 77; +} + /** * regress_foo_object_skipped_method: (skip) * @object: obj @@ -376,6 +406,11 @@ regress_foo_init (void) return REGRESS_FOO_SUCCESS_INT; } +/** + * regress_foo_init_argv: + * @argc: + * @argv: (array length=argc) (allow-none): + */ int regress_foo_init_argv (int argc G_GNUC_UNUSED, char **argv G_GNUC_UNUSED) @@ -383,6 +418,11 @@ regress_foo_init_argv (int argc G_GNUC_UNUSED, return REGRESS_FOO_SUCCESS_INT; } +/** + * regress_foo_init_argv_address: + * @argc: (inout): + * @argv: (array length=argc) (inout) (allow-none): + */ int regress_foo_init_argv_address (int *argc G_GNUC_UNUSED, char ***argv G_GNUC_UNUSED) @@ -413,6 +453,18 @@ regress_foo_enum_method (RegressFooEnumType regress_foo_enum G_GNUC_UNUSED) return 0; } +int +regress_foo_enum_type_method (RegressFooEnumType regress_foo_enum) +{ + return (regress_foo_enum + 1) % 3; +} + +RegressFooEnumType +regress_foo_enum_type_returnv (int x) +{ + return (x + 1) % 3; +} + GType regress_foo_flags_type_get_type (void) { @@ -530,6 +582,20 @@ regress_foo_brect_get_type (void) return our_type; } +RegressFooBRect * +regress_foo_brect_new (double x, double y) +{ + RegressFooBRect *retval = g_new(RegressFooBRect, 1); + retval->x = x; + retval->y = y; + return retval; +} + +void +regress_foo_brect_add (RegressFooBRect *b1 G_GNUC_UNUSED, RegressFooBRect *b2 G_GNUC_UNUSED) +{ +} + static RegressFooBUnion * regress_foo_bunion_copy (const RegressFooBUnion *boxed) { @@ -552,6 +618,12 @@ regress_foo_bunion_get_type (void) return our_type; } +RegressFooBUnion * +regress_foo_bunion_new (void) +{ + return g_new0(RegressFooBUnion, 1); +} + void regress_foo_test_unsigned (unsigned int uint G_GNUC_UNUSED) { @@ -605,7 +677,7 @@ regress_foo_rectangle_new (int x, int y, int width, int height) /** * regress_foo_rectangle_add: - * @r1: (inout): add to this rect + * @r1: add to this rect * @r2: source rectangle */ void @@ -614,6 +686,14 @@ regress_foo_rectangle_add (RegressFooRectangle *r1 G_GNUC_UNUSED, { } +void +regress_foo_method_external_references (UtilityObject *object G_GNUC_UNUSED, + UtilityEnumType e G_GNUC_UNUSED, + UtilityFlagType f G_GNUC_UNUSED, + UtilityStruct s G_GNUC_UNUSED) +{ +} + /* RegressFooHidden */ struct _RegressFooHidden @@ -824,6 +904,17 @@ regress_foo_test_varargs_callback3 (RegressFooVarargsCallback callback G_GNUC_UN { } +/** + * regress_foo_object_a_global_method: + * @obj: a #UtilityObject + * + * This shouldn't be scanned as a method of UtilityObject. + */ +void +regress_foo_object_a_global_method (UtilityObject *obj G_GNUC_UNUSED) +{ +} + /** * regress_foo_object_append_new_stack_layer: * diff --git a/subprojects/gobject-introspection-tests/foo.h b/subprojects/gobject-introspection-tests/foo.h index baaf92384..5813a626b 100644 --- a/subprojects/gobject-introspection-tests/foo.h +++ b/subprojects/gobject-introspection-tests/foo.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2007-2010 Johan Dahlin SPDX-FileCopyrightText: 2008-2010 Colin Walters SPDX-FileCopyrightText: 2008-2009, 2011-2012 Dan Winship @@ -50,6 +51,8 @@ SPDX-FileCopyrightText: 2024 Simon McVittie #define REGRESS_FOO_TYPE_OBJECT (regress_foo_object_get_type ()) #define REGRESS_FOO_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), REGRESS_FOO_TYPE_OBJECT, RegressFooObject)) #define REGRESS_FOO_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), REGRESS_FOO_TYPE_OBJECT)) +#define REGRESS_FOO_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), REGRESS_FOO_TYPE_OBJECT)) +#define REGRESS_FOO_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), REGRESS_FOO_TYPE_OBJECT, RegressFooObjectClass)) #define REGRESS_FOO_TYPE_SUBOBJECT (regress_foo_subobject_get_type ()) #define REGRESS_FOO_SUBOBJECT(subobject) (G_TYPE_CHECK_INSTANCE_CAST ((subobject), REGRESS_FOO_TYPE_SUBOBJECT, RegressFooSubobject)) @@ -141,6 +144,12 @@ struct _RegressFooObjectClass GI_TEST_EXTERN gint regress_foo_init (void); +GI_TEST_EXTERN +int regress_foo_init_argv (int argc, char **argv); + +GI_TEST_EXTERN +int regress_foo_init_argv_address (int *argc, char ***argv); + GI_TEST_EXTERN GType regress_foo_object_get_type (void) G_GNUC_CONST; @@ -204,9 +213,6 @@ struct _RegressFooSubobjectClass GI_TEST_EXTERN GType regress_foo_subobject_get_type (void) G_GNUC_CONST; -GI_TEST_EXTERN -RegressFooSubobject *regress_foo_subobject_new (void); - GI_TEST_EXTERN RegressFooObject *regress_foo_object_get_default (void); @@ -232,6 +238,9 @@ typedef enum GI_TEST_EXTERN GType regress_foo_enum_type_get_type (void); +GI_TEST_EXTERN +int regress_foo_enum_method (RegressFooEnumType regress_foo_enum); + GI_TEST_EXTERN int regress_foo_enum_type_method (RegressFooEnumType regress_foo_enum); @@ -436,7 +445,7 @@ GI_TEST_EXTERN int regress_foo_bunion_get_contained_type (RegressFooBUnion *bunion); GI_TEST_EXTERN -void regress_foo_test_unsigned_qualifier (unsigned int unsigned_param); +void regress_foo_test_unsigned (unsigned int unsigned_param); GI_TEST_EXTERN void regress_foo_test_unsigned_type (unsigned unsigned_param); diff --git a/subprojects/gobject-introspection-tests/gimarshallingtests.c b/subprojects/gobject-introspection-tests/gimarshallingtests.c index 9b9b1d4da..76fd8ff06 100644 --- a/subprojects/gobject-introspection-tests/gimarshallingtests.c +++ b/subprojects/gobject-introspection-tests/gimarshallingtests.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2010-2012 Collabora, Ltd. SPDX-FileCopyrightText: 2010 Johan Dahlin SPDX-FileCopyrightText: 2010 Sugar Labs @@ -23,7 +24,7 @@ SPDX-FileCopyrightText: 2014 Lionel Landwerlin SPDX-FileCopyrightText: 2014 RIFT.io, Inc. SPDX-FileCopyrightText: 2014 SuSE SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. -SPDX-FileCopyrightText: 2016-2018, 2023 Philip Chimento +SPDX-FileCopyrightText: 2016-2018, 2023, 2025 Philip Chimento SPDX-FileCopyrightText: 2017 Christoph Reiter SPDX-FileCopyrightText: 2018 Tomasz Miąsko SPDX-FileCopyrightText: 2019 Stéphane Seng @@ -32,10 +33,45 @@ SPDX-FileCopyrightText: 2020, 2024 Simon McVittie SPDX-FileCopyrightText: 2021 Carlos Garnacho */ +#include #include #include "gimarshallingtests.h" +/* Unaligned buffer, for testing that language bindings can deal with pointers + * allocated at arbitrary 1-byte alignments. */ +static guint8 *unaligned_buffer = NULL; +static const size_t UNALIGNED_BUFFER_SIZE = 33; + +/** + * gi_marshalling_tests_cleanup_unaligned_buffer: + * + * It's OK not to call this and just leak the buffer, but if you are running + * your tests with AddressSanitizer or valgrind, you should call this after + * completing each unaligned buffer test. + * + * We can't send an unaligned buffer as (transfer full) because g_free() won't + * work on it on Windows. + */ +void +gi_marshalling_tests_cleanup_unaligned_buffer (void) +{ + g_aligned_free_sized (unaligned_buffer, 8, UNALIGNED_BUFFER_SIZE); + unaligned_buffer = NULL; +} + +static const guint8 * +init_unaligned_buffer (void) +{ + if (unaligned_buffer) + gi_marshalling_tests_cleanup_unaligned_buffer (); + + unaligned_buffer = g_aligned_alloc0 (1, UNALIGNED_BUFFER_SIZE, 8); + for (size_t ix = 0; ix < UNALIGNED_BUFFER_SIZE; ix++) + unaligned_buffer[ix] = (uintptr_t) (unaligned_buffer + ix) & 0x07; + return unaligned_buffer + 1; +} + static void gi_marshalling_tests_boxed_struct_free (GIMarshallingTestsBoxedStruct *v); /* Booleans */ @@ -1600,6 +1636,17 @@ gi_marshalling_tests_utf8_none_in (const gchar *utf8) g_assert_cmpstr (GI_MARSHALLING_TESTS_CONSTANT_UTF8, ==, utf8); } +/** + * gi_marshalling_tests_utf8_full_in: + * @utf8: (transfer full): + */ +void +gi_marshalling_tests_utf8_full_in (gchar *utf8) +{ + g_assert_cmpstr (GI_MARSHALLING_TESTS_CONSTANT_UTF8, ==, utf8); + g_free (utf8); +} + /** * gi_marshalling_tests_utf8_as_uint8array_in: * @array: (array length=len) (element-type guint8): Byte data that happens to be UTF-8 @@ -1731,6 +1778,20 @@ gi_marshalling_tests_array_fixed_short_return (void) return shorts; } +/** + * gi_marshalling_tests_array_fixed_return_unaligned: + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + * + * Returns: (array fixed-size=32) (transfer none): + */ +const guint8 * +gi_marshalling_tests_array_fixed_return_unaligned (void) +{ + return init_unaligned_buffer (); +} + /** * gi_marshalling_tests_array_fixed_int_in: * @ints: (array fixed-size=4): @@ -1791,6 +1852,19 @@ gi_marshalling_tests_array_fixed_out_uninitialized (gint **v G_GNUC_UNUSED) return FALSE; } +/** + * gi_marshalling_tests_array_fixed_out_unaligned: + * @v: (out) (array fixed-size=32) (transfer none): + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + */ +void +gi_marshalling_tests_array_fixed_out_unaligned (const guint8 **v) +{ + *v = init_unaligned_buffer (); +} + /** * gi_marshalling_tests_array_fixed_out_struct: * @structs: (out) (array fixed-size=2) (transfer none): @@ -1893,6 +1967,22 @@ gi_marshalling_tests_array_return_etc (gint first, gint *length, gint last, gint return ints; } +/** + * gi_marshalling_tests_array_return_unaligned: + * @len: (out): + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + * + * Returns: (array length=len): + */ +const guint8 * +gi_marshalling_tests_array_return_unaligned (gsize *len) +{ + *len = UNALIGNED_BUFFER_SIZE - 1; + return init_unaligned_buffer (); +} + /** * gi_marshalling_tests_array_in: * @ints: (array length=length): @@ -2174,6 +2264,21 @@ gi_marshalling_tests_array_out_uninitialized (gint **v G_GNUC_UNUSED, gint *leng return FALSE; } +/** + * gi_marshalling_tests_array_out_unaligned: + * @v: (out) (array length=len) (transfer none): + * @len: + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + */ +void +gi_marshalling_tests_array_out_unaligned (const guint8 **v, gsize *len) +{ + *v = init_unaligned_buffer (); + *len = UNALIGNED_BUFFER_SIZE - 1; +} + /** * gi_marshalling_tests_array_out_etc: * @first: @@ -2327,6 +2432,23 @@ gi_marshalling_tests_array_zero_terminated_return_struct (void) return ret; } +/** + * gi_marshalling_tests_array_zero_terminated_return_sequential_struct: + * + * Returns: (array zero-terminated) (transfer full): + */ +GIMarshallingTestsBoxedStruct * +gi_marshalling_tests_array_zero_terminated_return_sequential_struct (void) +{ + GIMarshallingTestsBoxedStruct *ret = (GIMarshallingTestsBoxedStruct *) g_new0 (GIMarshallingTestsBoxedStruct, 4); + + ret[0].long_ = 42; + ret[1].long_ = 43; + ret[2].long_ = 44; + + return ret; +} + /** * gi_marshalling_tests_array_zero_terminated_return_unichar: * @@ -2341,6 +2463,20 @@ gi_marshalling_tests_array_zero_terminated_return_unichar (void) return retval; } +/** + * gi_marshalling_tests_array_zero_terminated_return_unaligned: + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + * + * Returns: (array zero-terminated) (transfer none): + */ +const guint8 * +gi_marshalling_tests_array_zero_terminated_return_unaligned (void) +{ + return init_unaligned_buffer (); +} + /** * gi_marshalling_tests_array_zero_terminated_in: * @utf8s: (array zero-terminated) (transfer none): @@ -2375,6 +2511,19 @@ gi_marshalling_tests_array_zero_terminated_out_uninitialized (const gchar ***v G return FALSE; } +/** + * gi_marshalling_tests_array_zero_terminated_out_unaligned: + * @v: (out) (array zero-terminated) (transfer none): + * + * Note that the buffer will leak unless you call + * gi_marshalling_tests_cleanup_unaligned_buffer(). + */ +void +gi_marshalling_tests_array_zero_terminated_out_unaligned (const guint8 **v) +{ + *v = init_unaligned_buffer (); +} + /** * gi_marshalling_tests_array_zero_terminated_inout: * @utf8s: (inout) (array zero-terminated) (transfer none): @@ -2477,4561 +2626,7184 @@ gi_marshalling_tests_array_gvariant_full_in (GVariant **variants) return container; } +/* The following tests expect the following arrays: + * in: 🅰, β, c, d (the first two characters are U+1F170 and U+03B2) + * out/return: a, b, ¢, 🔠 (the last two characters are U+00A2 and U+1F520) + * This is intended to test the full capabilities of C arrays of a basic type. + * UTF-8 strings and filenames are the only basic types that need to be released + * as individual elements, so we test UTF-8 strings. We test ASCII characters, + * basic multilingual plane characters, and astral plane characters in both the + * in and out arrays. + */ +#define SQUARED_A "\xf0\x9f\x85\xb0" +#define BETA "\xce\xb2" +#define CENT "\xc2\xa2" +#define ABCD "\xf0\x9f\x94\xa0" + /** - * gi_marshalling_tests_garray_int_none_return: + * gi_marshalling_tests_length_array_utf8_none_return: * - * Returns: (element-type gint) (transfer none): + * Returns: (array length=out_length) (transfer none): */ -GArray * -gi_marshalling_tests_garray_int_none_return (void) +const gchar *const * +gi_marshalling_tests_length_array_utf8_none_return (size_t *out_length) { - static GArray *v = NULL; - gint i; - - if (v == NULL) - { - v = g_array_new (TRUE, TRUE, sizeof (gint)); - for (i = -1; i < 3; i++) - g_array_append_val (v, i); - } + static const gchar *array[] = { "a", "b", CENT, ABCD }; - return v; + *out_length = 4; + return array; } /** - * gi_marshalling_tests_garray_uint64_none_return: + * gi_marshalling_tests_length_array_utf8_container_return: * - * Returns: (element-type guint64) (transfer none): + * Returns: (array length=out_length) (transfer container): */ -GArray * -gi_marshalling_tests_garray_uint64_none_return (void) +const gchar ** +gi_marshalling_tests_length_array_utf8_container_return (size_t *out_length) { - static GArray *array = NULL; - guint64 i; + const gchar **array = g_new0 (const gchar *, 4); - if (array == NULL) - { - array = g_array_new (TRUE, TRUE, sizeof (guint64)); - i = 0; - g_array_append_val (array, i); - i = G_MAXUINT64; - g_array_append_val (array, i); - } + array[0] = "a"; + array[1] = "b"; + array[2] = CENT; + array[3] = ABCD; + *out_length = 4; return array; } /** - * gi_marshalling_tests_garray_utf8_none_return: + * gi_marshalling_tests_length_array_utf8_full_return: * - * Returns: (element-type utf8) (transfer none): + * Returns: (array length=out_length) (transfer full): */ -GArray * -gi_marshalling_tests_garray_utf8_none_return (void) +gchar ** +gi_marshalling_tests_length_array_utf8_full_return (size_t *out_length) { - static GArray *array = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; - - if (array == NULL) - { - array = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - g_array_append_val (array, values[i]); - } + gchar **array = g_new0 (gchar *, 4); + array[0] = g_strdup ("a"); + array[1] = g_strdup ("b"); + array[2] = g_strdup (CENT); + array[3] = g_strdup (ABCD); + *out_length = 4; return array; } /** - * gi_marshalling_tests_garray_utf8_container_return: - * - * Returns: (element-type utf8) (transfer container): + * gi_marshalling_tests_length_array_utf8_none_in: + * @array: (array length=length) (transfer none): */ -GArray * -gi_marshalling_tests_garray_utf8_container_return (void) +void +gi_marshalling_tests_length_array_utf8_none_in (const gchar *const *array, size_t length) { - GArray *array = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; - - array = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - g_array_append_val (array, values[i]); + g_assert_cmpint (length, ==, 4); - return array; + g_assert_cmpstr (array[0], ==, SQUARED_A); + g_assert_cmpstr (array[1], ==, BETA); + g_assert_cmpstr (array[2], ==, "c"); + g_assert_cmpstr (array[3], ==, "d"); } /** - * gi_marshalling_tests_garray_utf8_full_return: - * - * Returns: (element-type utf8) (transfer full): + * gi_marshalling_tests_length_array_utf8_container_in: + * @array: (array length=length) (transfer container): */ -GArray * -gi_marshalling_tests_garray_utf8_full_return (void) +void +gi_marshalling_tests_length_array_utf8_container_in (const gchar **array, size_t length) { - GArray *array = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; - - array = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - { - gchar *str = g_strdup (values[i]); - g_array_append_val (array, str); - } + gi_marshalling_tests_length_array_utf8_none_in (array, length); - return array; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_garray_boxed_struct_full_return: - * - * Returns: (element-type GIMarshallingTestsBoxedStruct) (transfer full): + * gi_marshalling_tests_length_array_utf8_full_in: + * @array: (array length=length) (transfer full): */ -GArray * -gi_marshalling_tests_garray_boxed_struct_full_return (void) +void +gi_marshalling_tests_length_array_utf8_full_in (gchar **array, size_t length) { - GArray *array = NULL; - static const glong long_values[] = { 42, 43, 44 }; - gint i; + gi_marshalling_tests_length_array_utf8_none_in ((const gchar *const *) array, length); - array = g_array_new (TRUE, TRUE, sizeof (GIMarshallingTestsBoxedStruct)); - g_array_set_size (array, 3); - for (i = 0; i < 3; i++) - { - GIMarshallingTestsBoxedStruct *new_struct; - new_struct = &g_array_index (array, GIMarshallingTestsBoxedStruct, i); - memset (new_struct, 0, sizeof (GIMarshallingTestsSimpleStruct)); - new_struct->long_ = long_values[i]; - } + g_clear_pointer (&array[0], g_free); + g_clear_pointer (&array[1], g_free); + g_clear_pointer (&array[2], g_free); + g_clear_pointer (&array[3], g_free); - return array; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_garray_int_none_in: - * @array_: (element-type gint) (transfer none): + * gi_marshalling_tests_length_array_utf8_none_out: + * @array_out: (array length=out_length) (out) (transfer none): + * @out_length: (out): */ void -gi_marshalling_tests_garray_int_none_in (GArray *array_) +gi_marshalling_tests_length_array_utf8_none_out (const gchar *const **array_out, size_t *out_length) { - g_assert_cmpint (array_->len, ==, 4); - g_assert_cmpint (g_array_index (array_, gint, 0), ==, -1); - g_assert_cmpint (g_array_index (array_, gint, 1), ==, 0); - g_assert_cmpint (g_array_index (array_, gint, 2), ==, 1); - g_assert_cmpint (g_array_index (array_, gint, 3), ==, 2); + *array_out = gi_marshalling_tests_length_array_utf8_none_return (out_length); } /** - * gi_marshalling_tests_garray_uint64_none_in: - * @array_: (element-type guint64) (transfer none): + * gi_marshalling_tests_length_array_utf8_container_out: + * @array_out: (array length=out_length) (out) (transfer container): */ void -gi_marshalling_tests_garray_uint64_none_in (GArray *array_) +gi_marshalling_tests_length_array_utf8_container_out (const gchar ***array_out, size_t *out_length) { - g_assert_cmpint (array_->len, ==, 2); - g_assert_cmpint (g_array_index (array_, guint64, 0), ==, 0); - g_assert_cmpint (g_array_index (array_, guint64, 1), ==, G_MAXUINT64); + *array_out = gi_marshalling_tests_length_array_utf8_container_return (out_length); } /** - * gi_marshalling_tests_garray_utf8_none_in: - * @array_: (element-type utf8) (transfer none): + * gi_marshalling_tests_length_array_utf8_full_out: + * @array_out: (array length=out_length) (out) (transfer full): */ void -gi_marshalling_tests_garray_utf8_none_in (GArray *array_) +gi_marshalling_tests_length_array_utf8_full_out (gchar ***array_out, size_t *out_length) { - g_assert_cmpint (array_->len, ==, 3); - g_assert_cmpstr (g_array_index (array_, gchar *, 0), ==, "0"); - g_assert_cmpstr (g_array_index (array_, gchar *, 1), ==, "1"); - g_assert_cmpstr (g_array_index (array_, gchar *, 2), ==, "2"); + *array_out = gi_marshalling_tests_length_array_utf8_full_return (out_length); } /** - * gi_marshalling_tests_garray_utf8_none_out: - * @array_: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_length_array_utf8_none_inout: + * @array_inout: (array length=inout_length) (inout) (transfer none): + * @inout_length: (inout): */ void -gi_marshalling_tests_garray_utf8_none_out (GArray **array_) +gi_marshalling_tests_length_array_utf8_none_inout (const gchar *const **array_inout, size_t *inout_length) { - static GArray *internal = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + static const gchar *array_out[] = { "a", "b", CENT, ABCD }; - if (internal == NULL) - { - internal = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - g_array_append_val (internal, values[i]); - } + g_assert_nonnull (inout_length); + gi_marshalling_tests_length_array_utf8_none_in (*array_inout, *inout_length); - *array_ = internal; + *array_inout = array_out; + *inout_length = 4; } /** - * gi_marshalling_tests_garray_utf8_none_out_uninitialized: - * @v: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_length_array_utf8_container_inout: + * @array_inout: (array length=inout_length) (inout) (transfer container): + * @inout_length: (inout): */ -gboolean -gi_marshalling_tests_garray_utf8_none_out_uninitialized (GArray **v G_GNUC_UNUSED) +void +gi_marshalling_tests_length_array_utf8_container_inout (const gchar ***array_inout, size_t *inout_length) { - return FALSE; + const gchar **array_out = g_new0 (const gchar *, 4); + + g_assert_nonnull (inout_length); + g_assert_nonnull (array_inout); + gi_marshalling_tests_length_array_utf8_container_in (*array_inout, *inout_length); + + array_out[0] = "a"; + array_out[1] = "b"; + array_out[2] = CENT; + array_out[3] = ABCD; + + *array_inout = array_out; + *inout_length = 4; } /** - * gi_marshalling_tests_garray_utf8_container_out: - * @array_: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_length_array_utf8_full_inout: + * @array_inout: (array length=inout_length) (inout) (transfer full): + * @inout_length: (inout): */ void -gi_marshalling_tests_garray_utf8_container_out (GArray **array_) +gi_marshalling_tests_length_array_utf8_full_inout (gchar ***array_inout, size_t *inout_length) { - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + gchar **array_out = g_new0 (gchar *, 4); - *array_ = NULL; + g_assert_nonnull (inout_length); + g_assert_nonnull (array_inout); + gi_marshalling_tests_length_array_utf8_full_in ( + g_steal_pointer (array_inout), *inout_length); - *array_ = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - g_array_append_val (*array_, values[i]); + array_out[0] = g_strdup ("a"); + array_out[1] = g_strdup ("b"); + array_out[2] = g_strdup (CENT); + array_out[3] = g_strdup (ABCD); + + *array_inout = array_out; + *inout_length = 4; } /** - * gi_marshalling_tests_garray_utf8_container_out_uninitialized: - * @v: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_zero_terminated_array_utf8_none_return: + * + * Returns: (array zero-terminated) (transfer none): */ -gboolean -gi_marshalling_tests_garray_utf8_container_out_uninitialized (GArray **v G_GNUC_UNUSED) +const gchar *const * +gi_marshalling_tests_zero_terminated_array_utf8_none_return (void) { - return FALSE; + static const gchar *array[] = { "a", "b", CENT, ABCD, NULL }; + return array; } /** - * gi_marshalling_tests_garray_utf8_full_out: - * @array_: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_zero_terminated_array_utf8_container_return: + * + * Returns: (array zero-terminated) (transfer container): */ -void -gi_marshalling_tests_garray_utf8_full_out (GArray **array_) +const gchar ** +gi_marshalling_tests_zero_terminated_array_utf8_container_return (void) { - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + const gchar **array = g_new0 (const gchar *, 5); - *array_ = NULL; + array[0] = "a"; + array[1] = "b"; + array[2] = CENT; + array[3] = ABCD; - *array_ = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - { - gchar *str = g_strdup (values[i]); - g_array_append_val (*array_, str); - } + return array; } /** - * gi_marshalling_tests_garray_utf8_full_out_uninitialized: - * @v: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_zero_terminated_array_utf8_full_return: + * + * Returns: (array zero-terminated) (transfer full): */ -gboolean -gi_marshalling_tests_garray_utf8_full_out_uninitialized (GArray **v G_GNUC_UNUSED) +gchar ** +gi_marshalling_tests_zero_terminated_array_utf8_full_return (void) { - return FALSE; + gchar **array = g_new0 (gchar *, 5); + + array[0] = g_strdup ("a"); + array[1] = g_strdup ("b"); + array[2] = g_strdup (CENT); + array[3] = g_strdup (ABCD); + + return array; } /** - * gi_marshalling_tests_garray_utf8_full_out_caller_allocated: - * @array_: (out caller-allocates) (array) (element-type utf8) (transfer full): + * gi_marshalling_tests_zero_terminated_array_utf8_none_in: + * @array: (array zero-terminated) (transfer none): */ void -gi_marshalling_tests_garray_utf8_full_out_caller_allocated (GArray *array_) +gi_marshalling_tests_zero_terminated_array_utf8_none_in (const gchar *const *array) { - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + g_assert_cmpstr (array[0], ==, SQUARED_A); + g_assert_cmpstr (array[1], ==, BETA); + g_assert_cmpstr (array[2], ==, "c"); + g_assert_cmpstr (array[3], ==, "d"); - g_array_set_size (array_, 0); - for (i = 0; values[i]; i++) - { - gchar *str = g_strdup (values[i]); - g_array_append_val (array_, str); - } + g_assert_null (array[4]); } /** - * gi_marshalling_tests_garray_utf8_none_inout: - * @array_: (inout) (element-type utf8) (transfer none): + * gi_marshalling_tests_zero_terminated_array_utf8_container_in: + * @array: (array zero-terminated) (transfer container): */ void -gi_marshalling_tests_garray_utf8_none_inout (GArray **array_) +gi_marshalling_tests_zero_terminated_array_utf8_container_in (const gchar **array) { - static GArray *internal = NULL; - static const gchar *values[] = { "-2", "-1", "0", "1", NULL }; - gint i; - - g_assert_cmpint ((*array_)->len, ==, 3); - g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); - - if (internal == NULL) - { - internal = g_array_new (TRUE, TRUE, sizeof (gchar *)); - for (i = 0; values[i]; i++) - g_array_append_val (internal, values[i]); - } + gi_marshalling_tests_zero_terminated_array_utf8_none_in (array); - *array_ = internal; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_garray_utf8_container_inout: - * @array_: (inout) (element-type utf8) (transfer container): + * gi_marshalling_tests_zero_terminated_array_utf8_full_in: + * @array: (array zero-terminated) (transfer full): */ void -gi_marshalling_tests_garray_utf8_container_inout (GArray **array_) +gi_marshalling_tests_zero_terminated_array_utf8_full_in (gchar **array) { - static const gchar *val1 = "-2"; - static const gchar *val2 = "-1"; - static const gchar *val3 = "0"; - static const gchar *val4 = "1"; - GArray *result; - - g_assert_cmpint ((*array_)->len, ==, 3); - g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); + gi_marshalling_tests_zero_terminated_array_utf8_none_in ((const gchar *const *) array); - result = g_array_new (TRUE, TRUE, sizeof (gchar *)); - g_array_append_val (result, val1); - g_array_append_val (result, val2); - g_array_append_val (result, val3); - g_array_append_val (result, val4); + for (size_t i = 0; array && array[i] != NULL; i++) + g_clear_pointer (&array[i], g_free); - g_array_unref (*array_); - *array_ = result; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_garray_utf8_full_inout: - * @array_: (inout) (element-type utf8) (transfer full): + * gi_marshalling_tests_zero_terminated_array_utf8_none_out: + * @array_out: (array zero-terminated) (out) (transfer none): */ void -gi_marshalling_tests_garray_utf8_full_inout (GArray **array_) +gi_marshalling_tests_zero_terminated_array_utf8_none_out (const gchar *const **array_out) { - static const gchar *val1 = "-1"; - static const gchar *val2 = "-2"; - gchar *val; - GArray *result; - - g_assert_cmpint ((*array_)->len, ==, 3); - g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); - g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); - - result = g_array_new (TRUE, TRUE, sizeof (gchar *)); - val = g_strdup (val2); - g_array_append_val (result, val); - val = g_strdup (val1); - g_array_append_val (result, val); - val = g_strdup ("0"); - g_array_append_val (result, val); - val = g_strdup ("1"); - g_array_append_val (result, val); + *array_out = gi_marshalling_tests_zero_terminated_array_utf8_none_return (); +} - g_array_unref (*array_); - *array_ = result; +/** + * gi_marshalling_tests_zero_terminated_array_utf8_container_out: + * @array_out: (array zero-terminated) (out) (transfer container): + */ +void +gi_marshalling_tests_zero_terminated_array_utf8_container_out (const gchar ***array_out) +{ + *array_out = gi_marshalling_tests_zero_terminated_array_utf8_container_return (); } /** - * gi_marshalling_tests_garray_bool_none_in: - * @array_: (element-type gboolean) (transfer none): + * gi_marshalling_tests_zero_terminated_array_utf8_full_out: + * @array_out: (array zero-terminated) (out) (transfer full): */ void -gi_marshalling_tests_garray_bool_none_in (GArray *array_) +gi_marshalling_tests_zero_terminated_array_utf8_full_out (gchar ***array_out) { - g_assert_cmpint (array_->len, ==, 4); - g_assert_cmpint (g_array_index (array_, gboolean, 0), ==, TRUE); - g_assert_cmpint (g_array_index (array_, gboolean, 1), ==, FALSE); - g_assert_cmpint (g_array_index (array_, gboolean, 2), ==, TRUE); - g_assert_cmpint (g_array_index (array_, gboolean, 3), ==, TRUE); + *array_out = gi_marshalling_tests_zero_terminated_array_utf8_full_return (); } /** - * gi_marshalling_tests_garray_unichar_none_in: - * @array_: (element-type gunichar) (transfer none): + * gi_marshalling_tests_zero_terminated_array_utf8_none_inout: + * @array_inout: (array zero-terminated) (inout) (transfer none): */ void -gi_marshalling_tests_garray_unichar_none_in (GArray *array_) +gi_marshalling_tests_zero_terminated_array_utf8_none_inout (const gchar *const **array_inout) { - unsigned ix; - static const gunichar expected[] = GI_MARSHALLING_TESTS_CONSTANT_UCS4; - g_assert_cmpint (array_->len, ==, 12); - for (ix = 0; ix < array_->len; ix++) - g_assert_cmpuint (g_array_index (array_, gunichar, ix), ==, expected[ix]); + static const gchar *array_out[] = { "a", "b", CENT, ABCD, NULL }; + + gi_marshalling_tests_zero_terminated_array_utf8_none_in (*array_inout); + + *array_inout = array_out; } /** - * gi_marshalling_tests_gptrarray_utf8_none_return: - * - * Returns: (element-type utf8) (transfer none): + * gi_marshalling_tests_zero_terminated_array_utf8_container_inout: + * @array_inout: (array zero-terminated) (inout) (transfer container): */ -GPtrArray * -gi_marshalling_tests_gptrarray_utf8_none_return (void) +void +gi_marshalling_tests_zero_terminated_array_utf8_container_inout (const gchar ***array_inout) { - static GPtrArray *parray = NULL; - static const gchar *values[] = { "0", "1", "2" }; - gint i; + const gchar **array_out = g_new0 (const gchar *, 5); - if (parray == NULL) - { - parray = g_ptr_array_new (); - for (i = 0; i < 3; i++) - g_ptr_array_add (parray, (gpointer) values[i]); - } + gi_marshalling_tests_zero_terminated_array_utf8_container_in (*array_inout); - return parray; + array_out[0] = "a"; + array_out[1] = "b"; + array_out[2] = CENT; + array_out[3] = ABCD; + + *array_inout = array_out; } /** - * gi_marshalling_tests_gptrarray_utf8_container_return: - * - * Returns: (element-type utf8) (transfer container): + * gi_marshalling_tests_zero_terminated_array_utf8_full_inout: + * @array_inout: (array zero-terminated) (inout) (transfer full): */ -GPtrArray * -gi_marshalling_tests_gptrarray_utf8_container_return (void) +void +gi_marshalling_tests_zero_terminated_array_utf8_full_inout (gchar ***array_inout) { - GPtrArray *parray = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + gchar **array_out = g_new0 (gchar *, 5); - parray = g_ptr_array_new (); - for (i = 0; values[i]; i++) - g_ptr_array_add (parray, (gpointer) values[i]); + gi_marshalling_tests_zero_terminated_array_utf8_full_in ( + g_steal_pointer (array_inout)); - return parray; + array_out[0] = g_strdup ("a"); + array_out[1] = g_strdup ("b"); + array_out[2] = g_strdup (CENT); + array_out[3] = g_strdup (ABCD); + + *array_inout = array_out; } /** - * gi_marshalling_tests_gptrarray_utf8_full_return: + * gi_marshalling_tests_fixed_array_utf8_none_return: * - * Returns: (element-type utf8) (transfer full): + * Returns: (array fixed-size=4) (transfer none): */ -GPtrArray * -gi_marshalling_tests_gptrarray_utf8_full_return (void) +const gchar *const * +gi_marshalling_tests_fixed_array_utf8_none_return (void) { - GPtrArray *parray = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + static const gchar *array[] = { "a", "b", CENT, ABCD }; + return array; +} - parray = g_ptr_array_new (); - for (i = 0; values[i]; i++) - { - gchar *str = g_strdup (values[i]); - g_ptr_array_add (parray, (gpointer) str); - } +/** + * gi_marshalling_tests_fixed_array_utf8_container_return: + * + * Returns: (array fixed-size=4) (transfer container): + */ +const gchar ** +gi_marshalling_tests_fixed_array_utf8_container_return (void) +{ + const gchar **array = g_new0 (const gchar *, 4); - return parray; + array[0] = "a"; + array[1] = "b"; + array[2] = CENT; + array[3] = ABCD; + + return array; } /** - * gi_marshalling_tests_gptrarray_boxed_struct_full_return: + * gi_marshalling_tests_fixed_array_utf8_full_return: * - * Returns: (element-type GIMarshallingTestsBoxedStruct) (transfer full): + * Returns: (array fixed-size=4) (transfer full): */ -GPtrArray * -gi_marshalling_tests_gptrarray_boxed_struct_full_return (void) +gchar ** +gi_marshalling_tests_fixed_array_utf8_full_return (void) { - GPtrArray *parray = NULL; - static const glong long_values[] = { 42, 43, 44 }; - gint i; + gchar **array = g_new0 (gchar *, 4); - parray = g_ptr_array_new (); - for (i = 0; i < 3; i++) - { - GIMarshallingTestsBoxedStruct *new_struct = gi_marshalling_tests_boxed_struct_new (); - new_struct->long_ = long_values[i]; - g_ptr_array_add (parray, (gpointer) new_struct); - } + array[0] = g_strdup ("a"); + array[1] = g_strdup ("b"); + array[2] = g_strdup (CENT); + array[3] = g_strdup (ABCD); - return parray; + return array; } /** - * gi_marshalling_tests_gptrarray_utf8_none_in: - * @parray_: (element-type utf8) (transfer none): + * gi_marshalling_tests_fixed_array_utf8_none_in: + * @array: (array fixed-size=4) (transfer none): */ void -gi_marshalling_tests_gptrarray_utf8_none_in (GPtrArray *parray_) +gi_marshalling_tests_fixed_array_utf8_none_in (const gchar *const *array) { - g_assert_cmpint (parray_->len, ==, 3); - g_assert_cmpstr (g_ptr_array_index (parray_, 0), ==, "0"); - g_assert_cmpstr (g_ptr_array_index (parray_, 1), ==, "1"); - g_assert_cmpstr (g_ptr_array_index (parray_, 2), ==, "2"); + g_assert_cmpstr (array[0], ==, SQUARED_A); + g_assert_cmpstr (array[1], ==, BETA); + g_assert_cmpstr (array[2], ==, "c"); + g_assert_cmpstr (array[3], ==, "d"); } /** - * gi_marshalling_tests_gptrarray_utf8_none_out: - * @parray_: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_fixed_array_utf8_container_in: + * @array: (array fixed-size=4) (transfer container): */ void -gi_marshalling_tests_gptrarray_utf8_none_out (GPtrArray **parray_) +gi_marshalling_tests_fixed_array_utf8_container_in (const gchar **array) { - static GPtrArray *internal = NULL; - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; - - if (internal == NULL) - { - internal = g_ptr_array_new (); - for (i = 0; values[i]; i++) - g_ptr_array_add (internal, (gpointer) values[i]); - } + gi_marshalling_tests_fixed_array_utf8_none_in (array); - *parray_ = internal; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_gptrarray_utf8_none_out_uninitialized: - * @v: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_fixed_array_utf8_full_in: + * @array: (array fixed-size=4) (transfer full): */ -gboolean -gi_marshalling_tests_gptrarray_utf8_none_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) +void +gi_marshalling_tests_fixed_array_utf8_full_in (gchar **array) { - return FALSE; + gi_marshalling_tests_fixed_array_utf8_none_in ((const gchar *const *) array); + + g_clear_pointer (&array[0], g_free); + g_clear_pointer (&array[1], g_free); + g_clear_pointer (&array[2], g_free); + g_clear_pointer (&array[3], g_free); + + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_gptrarray_utf8_container_out: - * @parray_: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_fixed_array_utf8_none_out: + * @array_out: (array fixed-size=4) (out) (transfer none): */ void -gi_marshalling_tests_gptrarray_utf8_container_out (GPtrArray **parray_) +gi_marshalling_tests_fixed_array_utf8_none_out (const gchar *const **array_out) { - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; - - *parray_ = NULL; + *array_out = gi_marshalling_tests_fixed_array_utf8_none_return (); +} - *parray_ = g_ptr_array_new (); - for (i = 0; values[i]; i++) - g_ptr_array_add (*parray_, (gpointer) values[i]); +/** + * gi_marshalling_tests_fixed_array_utf8_container_out: + * @array_out: (array fixed-size=4) (out) (transfer container): + */ +void +gi_marshalling_tests_fixed_array_utf8_container_out (const gchar ***array_out) +{ + *array_out = gi_marshalling_tests_fixed_array_utf8_container_return (); } /** - * gi_marshalling_tests_gptrarray_utf8_container_out_uninitialized: - * @v: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_fixed_array_utf8_full_out: + * @array_out: (array fixed-size=4) (out) (transfer full): */ -gboolean -gi_marshalling_tests_gptrarray_utf8_container_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) +void +gi_marshalling_tests_fixed_array_utf8_full_out (gchar ***array_out) { - return FALSE; + *array_out = gi_marshalling_tests_fixed_array_utf8_full_return (); } /** - * gi_marshalling_tests_gptrarray_utf8_full_out: - * @parray_: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_fixed_array_utf8_none_inout: + * @array_inout: (array fixed-size=4) (inout) (transfer none): */ void -gi_marshalling_tests_gptrarray_utf8_full_out (GPtrArray **parray_) +gi_marshalling_tests_fixed_array_utf8_none_inout (const gchar *const **array_inout) { - static const gchar *values[] = { "0", "1", "2", NULL }; - gint i; + static const gchar *array_out[] = { "a", "b", CENT, ABCD }; - *parray_ = NULL; + gi_marshalling_tests_fixed_array_utf8_none_in (*array_inout); - *parray_ = g_ptr_array_new (); - for (i = 0; values[i]; i++) - { - gchar *str = g_strdup (values[i]); - g_ptr_array_add (*parray_, (gpointer) str); - } + *array_inout = array_out; } /** - * gi_marshalling_tests_gptrarray_utf8_full_out_uninitialized: - * @v: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_fixed_array_utf8_container_inout: + * @array_inout: (array fixed-size=4) (inout) (transfer container): */ -gboolean -gi_marshalling_tests_gptrarray_utf8_full_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) +void +gi_marshalling_tests_fixed_array_utf8_container_inout (const gchar ***array_inout) { - return FALSE; + const gchar **array_out = g_new0 (const gchar *, 4); + + gi_marshalling_tests_fixed_array_utf8_container_in (*array_inout); + + array_out[0] = "a"; + array_out[1] = "b"; + array_out[2] = CENT; + array_out[3] = ABCD; + + *array_inout = array_out; } /** - * gi_marshalling_tests_gptrarray_utf8_none_inout: - * @parray_: (inout) (element-type utf8) (transfer none): + * gi_marshalling_tests_fixed_array_utf8_full_inout: + * @array_inout: (array fixed-size=4) (inout) (transfer full): */ void -gi_marshalling_tests_gptrarray_utf8_none_inout (GPtrArray **parray_) +gi_marshalling_tests_fixed_array_utf8_full_inout (gchar ***array_inout) { - static GPtrArray *internal = NULL; - static const gchar *values[] = { "-2", "-1", "0", "1", NULL }; - gint i; + gchar **array_out = g_new0 (gchar *, 4); - g_assert_cmpint ((*parray_)->len, ==, 3); - g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); + gi_marshalling_tests_fixed_array_utf8_full_in ( + g_steal_pointer (array_inout)); - if (internal == NULL) + array_out[0] = g_strdup ("a"); + array_out[1] = g_strdup ("b"); + array_out[2] = g_strdup (CENT); + array_out[3] = g_strdup (ABCD); + + *array_inout = array_out; +} + +/** + * gi_marshalling_tests_garray_int_none_return: + * + * Returns: (element-type gint) (transfer none): + */ +GArray * +gi_marshalling_tests_garray_int_none_return (void) +{ + static GArray *v = NULL; + gint i; + + if (v == NULL) { - internal = g_ptr_array_new (); - for (i = 0; values[i]; i++) - g_ptr_array_add (internal, (gpointer) values[i]); + v = g_array_new (TRUE, TRUE, sizeof (gint)); + for (i = -1; i < 3; i++) + g_array_append_val (v, i); } - *parray_ = internal; + return v; } /** - * gi_marshalling_tests_gptrarray_utf8_container_inout: - * @parray_: (inout) (element-type utf8) (transfer container): + * gi_marshalling_tests_garray_uint64_none_return: + * + * Returns: (element-type guint64) (transfer none): */ -void -gi_marshalling_tests_gptrarray_utf8_container_inout (GPtrArray **parray_) +GArray * +gi_marshalling_tests_garray_uint64_none_return (void) { - static const gchar *val1 = "-2"; - static const gchar *val2 = "-1"; - static const gchar *val3 = "0"; - static const gchar *val4 = "1"; - GPtrArray *result; - - g_assert_cmpint ((*parray_)->len, ==, 3); - g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); + static GArray *array = NULL; + guint64 i; - result = g_ptr_array_new (); - g_ptr_array_add (result, (gpointer) val1); - g_ptr_array_add (result, (gpointer) val2); - g_ptr_array_add (result, (gpointer) val3); - g_ptr_array_add (result, (gpointer) val4); + if (array == NULL) + { + array = g_array_new (TRUE, TRUE, sizeof (guint64)); + i = 0; + g_array_append_val (array, i); + i = G_MAXUINT64; + g_array_append_val (array, i); + } - g_ptr_array_unref (*parray_); - *parray_ = result; + return array; } /** - * gi_marshalling_tests_gptrarray_utf8_full_inout: - * @parray_: (inout) (element-type utf8) (transfer full): + * gi_marshalling_tests_garray_utf8_none_return: + * + * Returns: (element-type utf8) (transfer none): */ -void -gi_marshalling_tests_gptrarray_utf8_full_inout (GPtrArray **parray_) +GArray * +gi_marshalling_tests_garray_utf8_none_return (void) { - static const gchar *val1 = "-1"; - static const gchar *val2 = "-2"; - gchar *val; - GPtrArray *result; - - g_assert_cmpint ((*parray_)->len, ==, 3); - g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); - g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); + static GArray *array = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - result = g_ptr_array_new (); - val = g_strdup (val2); - g_ptr_array_add (result, (gpointer) val); - val = g_strdup (val1); - g_ptr_array_add (result, (gpointer) val); - val = g_strdup ("0"); - g_ptr_array_add (result, (gpointer) val); - val = g_strdup ("1"); - g_ptr_array_add (result, (gpointer) val); + if (array == NULL) + { + array = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + g_array_append_val (array, values[i]); + } - g_ptr_array_unref (*parray_); - *parray_ = result; + return array; } /** - * gi_marshalling_tests_bytearray_full_return: + * gi_marshalling_tests_garray_utf8_container_return: * - * Returns: (transfer full): + * Returns: (element-type utf8) (transfer container): */ -GByteArray * -gi_marshalling_tests_bytearray_full_return (void) +GArray * +gi_marshalling_tests_garray_utf8_container_return (void) { - GByteArray *array = NULL; - guint8 data[] = { '\0', '1', '\xFF', '3' }; + GArray *array = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - array = g_byte_array_new (); - g_byte_array_append (array, (const guint8 *) data, G_N_ELEMENTS (data)); + array = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + g_array_append_val (array, values[i]); return array; } /** - * gi_marshalling_tests_bytearray_none_in: - * @v: (element-type gint8) (transfer none): + * gi_marshalling_tests_garray_utf8_full_return: + * + * Returns: (element-type utf8) (transfer full): */ -void -gi_marshalling_tests_bytearray_none_in (GByteArray *v) +GArray * +gi_marshalling_tests_garray_utf8_full_return (void) { - g_assert_cmpuint (v->len, ==, 4); - g_assert_cmpuint (g_array_index (v, unsigned char, 0), ==, 0); - g_assert_cmpuint (g_array_index (v, unsigned char, 1), ==, 49); - g_assert_cmpuint (g_array_index (v, unsigned char, 2), ==, 0xFF); - g_assert_cmpuint (g_array_index (v, unsigned char, 3), ==, 51); + GArray *array = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; + + array = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + { + gchar *str = g_strdup (values[i]); + g_array_append_val (array, str); + } + + return array; } /** - * gi_marshalling_tests_gbytes_full_return: + * gi_marshalling_tests_garray_boxed_struct_full_return: * - * Returns: (transfer full): + * Returns: (element-type GIMarshallingTestsBoxedStruct) (transfer full): */ -GBytes * -gi_marshalling_tests_gbytes_full_return (void) +GArray * +gi_marshalling_tests_garray_boxed_struct_full_return (void) { - static guint8 data[] = { 0, 49, 0xFF, 51 }; + GArray *array = NULL; + static const glong long_values[] = { 42, 43, 44 }; + gint i; - return g_bytes_new_static (data, G_N_ELEMENTS (data)); + array = g_array_new (TRUE, TRUE, sizeof (GIMarshallingTestsBoxedStruct)); + g_array_set_size (array, 3); + for (i = 0; i < 3; i++) + { + GIMarshallingTestsBoxedStruct *new_struct; + new_struct = &g_array_index (array, GIMarshallingTestsBoxedStruct, i); + memset (new_struct, 0, sizeof (GIMarshallingTestsSimpleStruct)); + new_struct->long_ = long_values[i]; + } + + return array; } /** - * gi_marshalling_tests_gbytes_none_in: + * gi_marshalling_tests_garray_int_none_in: + * @array_: (element-type gint) (transfer none): */ void -gi_marshalling_tests_gbytes_none_in (GBytes *v) +gi_marshalling_tests_garray_int_none_in (GArray *array_) { - const guint8 *data; - gsize len; - data = g_bytes_get_data (v, &len); - - g_assert_cmpuint (len, ==, 4); - g_assert_cmpuint (data[0], ==, 0); - g_assert_cmpuint (data[1], ==, 49); - g_assert_cmpuint (data[2], ==, 0xFF); - g_assert_cmpuint (data[3], ==, 51); + g_assert_cmpint (array_->len, ==, 4); + g_assert_cmpint (g_array_index (array_, gint, 0), ==, -1); + g_assert_cmpint (g_array_index (array_, gint, 1), ==, 0); + g_assert_cmpint (g_array_index (array_, gint, 2), ==, 1); + g_assert_cmpint (g_array_index (array_, gint, 3), ==, 2); } /** - * gi_marshalling_tests_gstrv_return: - * - * Returns: (transfer full): an array of strings + * gi_marshalling_tests_garray_uint64_none_in: + * @array_: (element-type guint64) (transfer none): */ -GStrv -gi_marshalling_tests_gstrv_return (void) +void +gi_marshalling_tests_garray_uint64_none_in (GArray *array_) { - GStrv values = g_new0 (gchar *, 4); - values[0] = g_strdup ("0"); - values[1] = g_strdup ("1"); - values[2] = g_strdup ("2"); - values[3] = NULL; - return values; + g_assert_cmpint (array_->len, ==, 2); + g_assert_cmpint (g_array_index (array_, guint64, 0), ==, 0); + g_assert_cmpint (g_array_index (array_, guint64, 1), ==, G_MAXUINT64); } /** - * gi_marshalling_tests_gstrv_in: - * @g_strv: + * gi_marshalling_tests_garray_utf8_none_in: + * @array_: (element-type utf8) (transfer none): */ void -gi_marshalling_tests_gstrv_in (GStrv g_strv) +gi_marshalling_tests_garray_utf8_none_in (GArray *array_) { - g_assert_cmpint (g_strv_length (g_strv), ==, 3); - g_assert_cmpstr (g_strv[0], ==, "0"); - g_assert_cmpstr (g_strv[1], ==, "1"); - g_assert_cmpstr (g_strv[2], ==, "2"); + g_assert_cmpint (array_->len, ==, 3); + g_assert_cmpstr (g_array_index (array_, gchar *, 0), ==, "0"); + g_assert_cmpstr (g_array_index (array_, gchar *, 1), ==, "1"); + g_assert_cmpstr (g_array_index (array_, gchar *, 2), ==, "2"); } /** - * gi_marshalling_tests_gstrv_out: - * @g_strv: (out) (transfer none): + * gi_marshalling_tests_garray_utf8_container_in: + * @array_: (element-type utf8) (transfer container): */ void -gi_marshalling_tests_gstrv_out (GStrv *g_strv) +gi_marshalling_tests_garray_utf8_container_in (GArray *array_) { - static const gchar *values[] = { "0", "1", "2", NULL }; - *g_strv = (gchar **) values; + gi_marshalling_tests_garray_utf8_none_in (array_); + g_array_unref (array_); } /** - * gi_marshalling_tests_gstrv_out_uninitialized: - * @v: (out) (transfer none): + * gi_marshalling_tests_garray_utf8_full_in: + * @array_: (element-type utf8) (transfer full): */ -gboolean -gi_marshalling_tests_gstrv_out_uninitialized (GStrv *v G_GNUC_UNUSED) +void +gi_marshalling_tests_garray_utf8_full_in (GArray *array_) { - return FALSE; + gi_marshalling_tests_garray_utf8_none_in (array_); + for (size_t ix = 0; ix < 3; ix++) + g_clear_pointer (&g_array_index (array_, gchar *, ix), g_free); + g_array_unref (array_); } /** - * gi_marshalling_tests_gstrv_inout: - * @g_strv: (inout) (transfer none): + * gi_marshalling_tests_garray_utf8_none_out: + * @array_: (out) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_gstrv_inout (GStrv *g_strv) +gi_marshalling_tests_garray_utf8_none_out (GArray **array_) { - static const gchar *values[] = { "-1", "0", "1", "2", NULL }; + static GArray *internal = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - g_assert (g_strv_length (*g_strv) == 3); - g_assert (strcmp ((*g_strv)[0], "0") == 0); - g_assert (strcmp ((*g_strv)[1], "1") == 0); - g_assert (strcmp ((*g_strv)[2], "2") == 0); + if (internal == NULL) + { + internal = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + g_array_append_val (internal, values[i]); + } - *g_strv = (gchar **) values; + *array_ = internal; } /** - * gi_marshalling_tests_glist_int_none_return: - * - * Returns: (element-type gint) (transfer none): + * gi_marshalling_tests_garray_utf8_none_out_uninitialized: + * @v: (out) (element-type utf8) (transfer none): */ -GList * -gi_marshalling_tests_glist_int_none_return (void) +gboolean +gi_marshalling_tests_garray_utf8_none_out_uninitialized (GArray **v G_GNUC_UNUSED) { - static GList *list = NULL; - - if (list == NULL) - { - list = g_list_append (list, GINT_TO_POINTER (-1)); - list = g_list_append (list, GINT_TO_POINTER (0)); - list = g_list_append (list, GINT_TO_POINTER (1)); - list = g_list_append (list, GINT_TO_POINTER (2)); - } - - return list; + return FALSE; } /** - * gi_marshalling_tests_glist_uint32_none_return: - * - * Returns: (element-type guint32) (transfer none): + * gi_marshalling_tests_garray_utf8_container_out: + * @array_: (out) (element-type utf8) (transfer container): */ -GList * -gi_marshalling_tests_glist_uint32_none_return (void) +void +gi_marshalling_tests_garray_utf8_container_out (GArray **array_) { - static GList *list = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - if (list == NULL) - { - list = g_list_append (list, GUINT_TO_POINTER (0)); - list = g_list_append (list, GUINT_TO_POINTER (G_MAXUINT32)); - } + *array_ = NULL; - return list; + *array_ = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + g_array_append_val (*array_, values[i]); } /** - * gi_marshalling_tests_glist_utf8_none_return: - * - * Returns: (element-type utf8) (transfer none): + * gi_marshalling_tests_garray_utf8_container_out_uninitialized: + * @v: (out) (element-type utf8) (transfer container): */ -GList * -gi_marshalling_tests_glist_utf8_none_return (void) +gboolean +gi_marshalling_tests_garray_utf8_container_out_uninitialized (GArray **v G_GNUC_UNUSED) { - static GList *list = NULL; - - if (list == NULL) - { - list = g_list_append (list, (gpointer) "0"); - list = g_list_append (list, (gpointer) "1"); - list = g_list_append (list, (gpointer) "2"); - } - - return list; + return FALSE; } /** - * gi_marshalling_tests_glist_utf8_container_return: - * - * Returns: (element-type utf8) (transfer container): + * gi_marshalling_tests_garray_utf8_full_out: + * @array_: (out) (element-type utf8) (transfer full): */ -GList * -gi_marshalling_tests_glist_utf8_container_return (void) +void +gi_marshalling_tests_garray_utf8_full_out (GArray **array_) { - GList *list = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - list = g_list_append (list, (gpointer) "0"); - list = g_list_append (list, (gpointer) "1"); - list = g_list_append (list, (gpointer) "2"); + *array_ = NULL; - return list; + *array_ = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + { + gchar *str = g_strdup (values[i]); + g_array_append_val (*array_, str); + } } /** - * gi_marshalling_tests_glist_utf8_full_return: - * - * Returns: (element-type utf8) (transfer full): + * gi_marshalling_tests_garray_utf8_full_out_uninitialized: + * @v: (out) (element-type utf8) (transfer full): */ -GList * -gi_marshalling_tests_glist_utf8_full_return (void) +gboolean +gi_marshalling_tests_garray_utf8_full_out_uninitialized (GArray **v G_GNUC_UNUSED) { - GList *list = NULL; - - list = g_list_append (list, g_strdup ("0")); - list = g_list_append (list, g_strdup ("1")); - list = g_list_append (list, g_strdup ("2")); - - return list; + return FALSE; } /** - * gi_marshalling_tests_glist_int_none_in: - * @list: (element-type gint) (transfer none): + * gi_marshalling_tests_garray_utf8_full_out_caller_allocated: + * @array_: (out caller-allocates) (array) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_glist_int_none_in (GList *list) +gi_marshalling_tests_garray_utf8_full_out_caller_allocated (GArray *array_) { - g_assert_cmpint (g_list_length (list), ==, 4); - g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 0)), ==, -1); - g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 1)), ==, 0); - g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 2)), ==, 1); - g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 3)), ==, 2); -} + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; -/** - * gi_marshalling_tests_glist_uint32_none_in: - * @list: (element-type guint32) (transfer none): - */ -void -gi_marshalling_tests_glist_uint32_none_in (GList *list) -{ - g_assert_cmpint (g_list_length (list), ==, 2); - g_assert_cmpint (GPOINTER_TO_UINT (g_list_nth_data (list, 0)), ==, 0); - g_assert_cmpint (GPOINTER_TO_UINT (g_list_nth_data (list, 1)), ==, G_MAXUINT32); + g_array_set_size (array_, 0); + for (i = 0; values[i]; i++) + { + gchar *str = g_strdup (values[i]); + g_array_append_val (array_, str); + } } /** - * gi_marshalling_tests_glist_utf8_none_in: - * @list: (element-type utf8) (transfer none): + * gi_marshalling_tests_garray_utf8_none_inout: + * @array_: (inout) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_glist_utf8_none_in (GList *list) +gi_marshalling_tests_garray_utf8_none_inout (GArray **array_) { - g_assert_cmpint (g_list_length (list), ==, 3); - g_assert_cmpint (strcmp (g_list_nth_data (list, 0), "0"), ==, 0); - g_assert_cmpint (strcmp (g_list_nth_data (list, 1), "1"), ==, 0); - g_assert_cmpint (strcmp (g_list_nth_data (list, 2), "2"), ==, 0); -} + static GArray *internal = NULL; + static const gchar *values[] = { "-2", "-1", "0", "1", NULL }; + gint i; -/** - * gi_marshalling_tests_glist_utf8_none_out: - * @list: (out) (element-type utf8) (transfer none): - */ -void -gi_marshalling_tests_glist_utf8_none_out (GList **list) -{ - static GList *values = NULL; + g_assert_cmpint ((*array_)->len, ==, 3); + g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); - if (values == NULL) + if (internal == NULL) { - values = g_list_append (values, (gpointer) "0"); - values = g_list_append (values, (gpointer) "1"); - values = g_list_append (values, (gpointer) "2"); + internal = g_array_new (TRUE, TRUE, sizeof (gchar *)); + for (i = 0; values[i]; i++) + g_array_append_val (internal, values[i]); } - *list = values; + *array_ = internal; } /** - * gi_marshalling_tests_glist_utf8_none_out_uninitialized: - * @v: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_garray_utf8_container_inout: + * @array_: (inout) (element-type utf8) (transfer container): */ -gboolean -gi_marshalling_tests_glist_utf8_none_out_uninitialized (GList **v G_GNUC_UNUSED) +void +gi_marshalling_tests_garray_utf8_container_inout (GArray **array_) { - return FALSE; + static const gchar *val1 = "-2"; + static const gchar *val2 = "-1"; + static const gchar *val3 = "0"; + static const gchar *val4 = "1"; + GArray *result; + + g_assert_cmpint ((*array_)->len, ==, 3); + g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); + + result = g_array_new (TRUE, TRUE, sizeof (gchar *)); + g_array_append_val (result, val1); + g_array_append_val (result, val2); + g_array_append_val (result, val3); + g_array_append_val (result, val4); + + g_array_unref (*array_); + *array_ = result; } /** - * gi_marshalling_tests_glist_utf8_container_out: - * @list: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_garray_utf8_full_inout: + * @array_: (inout) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_glist_utf8_container_out (GList **list) +gi_marshalling_tests_garray_utf8_full_inout (GArray **array_) { - *list = NULL; + static const gchar *val1 = "-1"; + static const gchar *val2 = "-2"; + gchar *val; + GArray *result; - *list = g_list_append (*list, (gpointer) "0"); - *list = g_list_append (*list, (gpointer) "1"); - *list = g_list_append (*list, (gpointer) "2"); + g_assert_cmpint ((*array_)->len, ==, 3); + g_assert_cmpstr (g_array_index (*array_, gchar *, 0), ==, "0"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 1), ==, "1"); + g_assert_cmpstr (g_array_index (*array_, gchar *, 2), ==, "2"); + + result = g_array_new (TRUE, TRUE, sizeof (gchar *)); + val = g_strdup (val2); + g_array_append_val (result, val); + val = g_strdup (val1); + g_array_append_val (result, val); + val = g_strdup ("0"); + g_array_append_val (result, val); + val = g_strdup ("1"); + g_array_append_val (result, val); + + g_array_unref (*array_); + *array_ = result; } /** - * gi_marshalling_tests_glist_utf8_container_out_uninitialized: - * @v: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_garray_bool_none_in: + * @array_: (element-type gboolean) (transfer none): */ -gboolean -gi_marshalling_tests_glist_utf8_container_out_uninitialized (GList **v G_GNUC_UNUSED) +void +gi_marshalling_tests_garray_bool_none_in (GArray *array_) { - return FALSE; + g_assert_cmpint (array_->len, ==, 4); + g_assert_cmpint (g_array_index (array_, gboolean, 0), ==, TRUE); + g_assert_cmpint (g_array_index (array_, gboolean, 1), ==, FALSE); + g_assert_cmpint (g_array_index (array_, gboolean, 2), ==, TRUE); + g_assert_cmpint (g_array_index (array_, gboolean, 3), ==, TRUE); } /** - * gi_marshalling_tests_glist_utf8_full_out: - * @list: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_garray_unichar_none_in: + * @array_: (element-type gunichar) (transfer none): */ void -gi_marshalling_tests_glist_utf8_full_out (GList **list) +gi_marshalling_tests_garray_unichar_none_in (GArray *array_) { - *list = NULL; - - *list = g_list_append (*list, g_strdup ("0")); - *list = g_list_append (*list, g_strdup ("1")); - *list = g_list_append (*list, g_strdup ("2")); + unsigned ix; + static const gunichar expected[] = GI_MARSHALLING_TESTS_CONSTANT_UCS4; + g_assert_cmpint (array_->len, ==, 12); + for (ix = 0; ix < array_->len; ix++) + g_assert_cmpuint (g_array_index (array_, gunichar, ix), ==, expected[ix]); } /** - * gi_marshalling_tests_glist_utf8_full_out_uninitialized: - * @v: (out) (element-type utf8) (transfer full): - */ -gboolean -gi_marshalling_tests_glist_utf8_full_out_uninitialized (GList **v G_GNUC_UNUSED) -{ - return FALSE; -} - -/** - * gi_marshalling_tests_glist_utf8_none_inout: - * @list: (inout) (element-type utf8) (transfer none): + * gi_marshalling_tests_gptrarray_utf8_none_return: + * + * Returns: (element-type utf8) (transfer none): */ -void -gi_marshalling_tests_glist_utf8_none_inout (GList **list) +GPtrArray * +gi_marshalling_tests_gptrarray_utf8_none_return (void) { - static GList *values = NULL; - - g_assert_cmpint (g_list_length (*list), ==, 3); - g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); + static GPtrArray *parray = NULL; + static const gchar *values[] = { "0", "1", "2" }; + gint i; - if (values == NULL) + if (parray == NULL) { - values = g_list_append (values, (gpointer) "-2"); - values = g_list_append (values, (gpointer) "-1"); - values = g_list_append (values, (gpointer) "0"); - values = g_list_append (values, (gpointer) "1"); + parray = g_ptr_array_new (); + for (i = 0; i < 3; i++) + g_ptr_array_add (parray, (gpointer) values[i]); } - *list = values; -} - -/** - * gi_marshalling_tests_glist_utf8_container_inout: - * @list: (inout) (element-type utf8) (transfer container): - */ -void -gi_marshalling_tests_glist_utf8_container_inout (GList **list) -{ - GList *result = NULL; - - g_assert_cmpint (g_list_length (*list), ==, 3); - g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); - - result = g_list_prepend (result, (gpointer) "1"); - result = g_list_prepend (result, (gpointer) "0"); - result = g_list_prepend (result, (gpointer) "-1"); - result = g_list_prepend (result, (gpointer) "-2"); - - g_list_free (*list); - *list = result; + return parray; } /** - * gi_marshalling_tests_glist_utf8_full_inout: - * @list: (inout) (element-type utf8) (transfer full): + * gi_marshalling_tests_gptrarray_utf8_container_return: + * + * Returns: (element-type utf8) (transfer container): */ -void -gi_marshalling_tests_glist_utf8_full_inout (GList **list) +GPtrArray * +gi_marshalling_tests_gptrarray_utf8_container_return (void) { - GList *result = NULL; - - g_assert_cmpint (g_list_length (*list), ==, 3); - g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); + GPtrArray *parray = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - result = g_list_prepend (result, g_strdup ("1")); - result = g_list_prepend (result, g_strdup ("0")); - result = g_list_prepend (result, g_strdup ("-1")); - result = g_list_prepend (result, g_strdup ("-2")); + parray = g_ptr_array_new (); + for (i = 0; values[i]; i++) + g_ptr_array_add (parray, (gpointer) values[i]); - g_list_free_full (*list, g_free); - *list = result; + return parray; } /** - * gi_marshalling_tests_gslist_int_none_return: + * gi_marshalling_tests_gptrarray_utf8_full_return: * - * Returns: (element-type gint) (transfer none): + * Returns: (element-type utf8) (transfer full): */ -GSList * -gi_marshalling_tests_gslist_int_none_return (void) +GPtrArray * +gi_marshalling_tests_gptrarray_utf8_full_return (void) { - static GSList *list = NULL; + GPtrArray *parray = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - if (list == NULL) + parray = g_ptr_array_new (); + for (i = 0; values[i]; i++) { - list = g_slist_prepend (list, GINT_TO_POINTER (-1)); - list = g_slist_prepend (list, GINT_TO_POINTER (0)); - list = g_slist_prepend (list, GINT_TO_POINTER (1)); - list = g_slist_prepend (list, GINT_TO_POINTER (2)); - list = g_slist_reverse (list); + gchar *str = g_strdup (values[i]); + g_ptr_array_add (parray, (gpointer) str); } - return list; + return parray; } /** - * gi_marshalling_tests_gslist_utf8_none_return: + * gi_marshalling_tests_gptrarray_boxed_struct_full_return: * - * Returns: (element-type utf8) (transfer none): + * Returns: (element-type GIMarshallingTestsBoxedStruct) (transfer full): */ -GSList * -gi_marshalling_tests_gslist_utf8_none_return (void) +GPtrArray * +gi_marshalling_tests_gptrarray_boxed_struct_full_return (void) { - static GSList *list = NULL; + GPtrArray *parray = NULL; + static const glong long_values[] = { 42, 43, 44 }; + gint i; - if (list == NULL) + parray = g_ptr_array_new (); + for (i = 0; i < 3; i++) { - list = g_slist_prepend (list, (gpointer) "0"); - list = g_slist_prepend (list, (gpointer) "1"); - list = g_slist_prepend (list, (gpointer) "2"); - list = g_slist_reverse (list); + GIMarshallingTestsBoxedStruct *new_struct = gi_marshalling_tests_boxed_struct_new (); + new_struct->long_ = long_values[i]; + g_ptr_array_add (parray, (gpointer) new_struct); } - return list; -} - -/** - * gi_marshalling_tests_gslist_utf8_container_return: - * - * Returns: (element-type utf8) (transfer container): - */ -GSList * -gi_marshalling_tests_gslist_utf8_container_return (void) -{ - GSList *list = NULL; - - list = g_slist_prepend (list, (gpointer) "0"); - list = g_slist_prepend (list, (gpointer) "1"); - list = g_slist_prepend (list, (gpointer) "2"); - list = g_slist_reverse (list); - - return list; + return parray; } /** - * gi_marshalling_tests_gslist_utf8_full_return: - * - * Returns: (element-type utf8) (transfer full): + * gi_marshalling_tests_gptrarray_utf8_none_in: + * @parray_: (element-type utf8) (transfer none): */ -GSList * -gi_marshalling_tests_gslist_utf8_full_return (void) +void +gi_marshalling_tests_gptrarray_utf8_none_in (GPtrArray *parray_) { - GSList *list = NULL; - - list = g_slist_prepend (list, g_strdup ("0")); - list = g_slist_prepend (list, g_strdup ("1")); - list = g_slist_prepend (list, g_strdup ("2")); - list = g_slist_reverse (list); - - return list; + g_assert_cmpint (parray_->len, ==, 3); + g_assert_cmpstr (g_ptr_array_index (parray_, 0), ==, "0"); + g_assert_cmpstr (g_ptr_array_index (parray_, 1), ==, "1"); + g_assert_cmpstr (g_ptr_array_index (parray_, 2), ==, "2"); } /** - * gi_marshalling_tests_gslist_int_none_in: - * @list: (element-type gint) (transfer none): + * gi_marshalling_tests_gptrarray_utf8_container_in: + * @parray_: (element-type utf8) (transfer container): */ void -gi_marshalling_tests_gslist_int_none_in (GSList *list) +gi_marshalling_tests_gptrarray_utf8_container_in (GPtrArray *parray_) { - g_assert_cmpint (g_slist_length (list), ==, 4); - g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 0)), ==, -1); - g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 1)), ==, 0); - g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 2)), ==, 1); - g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 3)), ==, 2); + gi_marshalling_tests_gptrarray_utf8_none_in (parray_); + g_ptr_array_unref (parray_); } /** - * gi_marshalling_tests_gslist_utf8_none_in: - * @list: (element-type utf8) (transfer none): + * gi_marshalling_tests_gptrarray_utf8_full_in: + * @parray_: (element-type utf8) (transfer full): */ void -gi_marshalling_tests_gslist_utf8_none_in (GSList *list) +gi_marshalling_tests_gptrarray_utf8_full_in (GPtrArray *parray_) { - g_assert_cmpint (g_slist_length (list), ==, 3); - g_assert_cmpstr (g_slist_nth_data (list, 0), ==, "0"); - g_assert_cmpstr (g_slist_nth_data (list, 1), ==, "1"); - g_assert_cmpstr (g_slist_nth_data (list, 2), ==, "2"); + gi_marshalling_tests_gptrarray_utf8_none_in (parray_); + for (size_t ix = 0; ix < 3; ix++) + g_clear_pointer (&g_ptr_array_index (parray_, ix), g_free); + g_ptr_array_unref (parray_); } /** - * gi_marshalling_tests_gslist_utf8_none_out: - * @list: (out) (element-type utf8) (transfer none): + * gi_marshalling_tests_gptrarray_utf8_none_out: + * @parray_: (out) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_gslist_utf8_none_out (GSList **list) +gi_marshalling_tests_gptrarray_utf8_none_out (GPtrArray **parray_) { - static GSList *values = NULL; + static GPtrArray *internal = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - if (values == NULL) + if (internal == NULL) { - values = g_slist_prepend (values, (gpointer) "0"); - values = g_slist_prepend (values, (gpointer) "1"); - values = g_slist_prepend (values, (gpointer) "2"); - values = g_slist_reverse (values); + internal = g_ptr_array_new (); + for (i = 0; values[i]; i++) + g_ptr_array_add (internal, (gpointer) values[i]); } - *list = values; + *parray_ = internal; } /** - * gi_marshalling_tests_gslist_utf8_none_out_uninitialized: + * gi_marshalling_tests_gptrarray_utf8_none_out_uninitialized: * @v: (out) (element-type utf8) (transfer none): */ gboolean -gi_marshalling_tests_gslist_utf8_none_out_uninitialized (GSList **v G_GNUC_UNUSED) +gi_marshalling_tests_gptrarray_utf8_none_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) { return FALSE; } /** - * gi_marshalling_tests_gslist_utf8_container_out: - * @list: (out) (element-type utf8) (transfer container): + * gi_marshalling_tests_gptrarray_utf8_container_out: + * @parray_: (out) (element-type utf8) (transfer container): */ void -gi_marshalling_tests_gslist_utf8_container_out (GSList **list) +gi_marshalling_tests_gptrarray_utf8_container_out (GPtrArray **parray_) { - *list = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - *list = g_slist_prepend (*list, (gpointer) "0"); - *list = g_slist_prepend (*list, (gpointer) "1"); - *list = g_slist_prepend (*list, (gpointer) "2"); - *list = g_slist_reverse (*list); + *parray_ = NULL; + + *parray_ = g_ptr_array_new (); + for (i = 0; values[i]; i++) + g_ptr_array_add (*parray_, (gpointer) values[i]); } /** - * gi_marshalling_tests_gslist_utf8_container_out_uninitialized: + * gi_marshalling_tests_gptrarray_utf8_container_out_uninitialized: * @v: (out) (element-type utf8) (transfer container): */ gboolean -gi_marshalling_tests_gslist_utf8_container_out_uninitialized (GSList **v G_GNUC_UNUSED) +gi_marshalling_tests_gptrarray_utf8_container_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) { return FALSE; } /** - * gi_marshalling_tests_gslist_utf8_full_out: - * @list: (out) (element-type utf8) (transfer full): + * gi_marshalling_tests_gptrarray_utf8_full_out: + * @parray_: (out) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_gslist_utf8_full_out (GSList **list) +gi_marshalling_tests_gptrarray_utf8_full_out (GPtrArray **parray_) { - *list = NULL; + static const gchar *values[] = { "0", "1", "2", NULL }; + gint i; - *list = g_slist_prepend (*list, g_strdup ("0")); - *list = g_slist_prepend (*list, g_strdup ("1")); - *list = g_slist_prepend (*list, g_strdup ("2")); - *list = g_slist_reverse (*list); -} + *parray_ = NULL; -/** - * gi_marshalling_tests_gslist_utf8_full_out_uninitialized: - * @v: (out) (element-type utf8) (transfer full): - */ -gboolean -gi_marshalling_tests_gslist_utf8_full_out_uninitialized (GSList **v G_GNUC_UNUSED) + *parray_ = g_ptr_array_new (); + for (i = 0; values[i]; i++) + { + gchar *str = g_strdup (values[i]); + g_ptr_array_add (*parray_, (gpointer) str); + } +} + +/** + * gi_marshalling_tests_gptrarray_utf8_full_out_uninitialized: + * @v: (out) (element-type utf8) (transfer full): + */ +gboolean +gi_marshalling_tests_gptrarray_utf8_full_out_uninitialized (GPtrArray **v G_GNUC_UNUSED) { return FALSE; } /** - * gi_marshalling_tests_gslist_utf8_none_inout: - * @list: (inout) (element-type utf8) (transfer none): + * gi_marshalling_tests_gptrarray_utf8_none_inout: + * @parray_: (inout) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_gslist_utf8_none_inout (GSList **list) +gi_marshalling_tests_gptrarray_utf8_none_inout (GPtrArray **parray_) { - static GSList *values = NULL; + static GPtrArray *internal = NULL; + static const gchar *values[] = { "-2", "-1", "0", "1", NULL }; + gint i; - g_assert_cmpint (g_slist_length (*list), ==, 3); - g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); + g_assert_cmpint ((*parray_)->len, ==, 3); + g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); - if (values == NULL) + if (internal == NULL) { - values = g_slist_prepend (values, (gpointer) "-2"); - values = g_slist_prepend (values, (gpointer) "-1"); - values = g_slist_prepend (values, (gpointer) "0"); - values = g_slist_prepend (values, (gpointer) "1"); - values = g_slist_reverse (values); + internal = g_ptr_array_new (); + for (i = 0; values[i]; i++) + g_ptr_array_add (internal, (gpointer) values[i]); } - *list = values; + *parray_ = internal; } /** - * gi_marshalling_tests_gslist_utf8_container_inout: - * @list: (inout) (element-type utf8) (transfer container): + * gi_marshalling_tests_gptrarray_utf8_container_inout: + * @parray_: (inout) (element-type utf8) (transfer container): */ void -gi_marshalling_tests_gslist_utf8_container_inout (GSList **list) +gi_marshalling_tests_gptrarray_utf8_container_inout (GPtrArray **parray_) { - GSList *result = NULL; + static const gchar *val1 = "-2"; + static const gchar *val2 = "-1"; + static const gchar *val3 = "0"; + static const gchar *val4 = "1"; + GPtrArray *result; - g_assert_cmpint (g_slist_length (*list), ==, 3); - g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); + g_assert_cmpint ((*parray_)->len, ==, 3); + g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); - result = g_slist_prepend (result, (gpointer) "1"); - result = g_slist_prepend (result, (gpointer) "0"); - result = g_slist_prepend (result, (gpointer) "-1"); - result = g_slist_prepend (result, (gpointer) "-2"); + result = g_ptr_array_new (); + g_ptr_array_add (result, (gpointer) val1); + g_ptr_array_add (result, (gpointer) val2); + g_ptr_array_add (result, (gpointer) val3); + g_ptr_array_add (result, (gpointer) val4); - g_slist_free (*list); - *list = result; + g_ptr_array_unref (*parray_); + *parray_ = result; } /** - * gi_marshalling_tests_gslist_utf8_full_inout: - * @list: (inout) (element-type utf8) (transfer full): + * gi_marshalling_tests_gptrarray_utf8_full_inout: + * @parray_: (inout) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_gslist_utf8_full_inout (GSList **list) +gi_marshalling_tests_gptrarray_utf8_full_inout (GPtrArray **parray_) { - GSList *result = NULL; + static const gchar *val1 = "-1"; + static const gchar *val2 = "-2"; + gchar *val; + GPtrArray *result; - g_assert_cmpint (g_slist_length (*list), ==, 3); - g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); - g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); - g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); + g_assert_cmpint ((*parray_)->len, ==, 3); + g_assert_cmpstr (g_ptr_array_index (*parray_, 0), ==, "0"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 1), ==, "1"); + g_assert_cmpstr (g_ptr_array_index (*parray_, 2), ==, "2"); - result = g_slist_prepend (result, g_strdup ("1")); - result = g_slist_prepend (result, g_strdup ("0")); - result = g_slist_prepend (result, g_strdup ("-1")); - result = g_slist_prepend (result, g_strdup ("-2")); + result = g_ptr_array_new (); + val = g_strdup (val2); + g_ptr_array_add (result, (gpointer) val); + val = g_strdup (val1); + g_ptr_array_add (result, (gpointer) val); + val = g_strdup ("0"); + g_ptr_array_add (result, (gpointer) val); + val = g_strdup ("1"); + g_ptr_array_add (result, (gpointer) val); - g_slist_free_full (*list, g_free); - *list = result; + g_ptr_array_unref (*parray_); + *parray_ = result; } /** - * gi_marshalling_tests_ghashtable_int_none_return: + * gi_marshalling_tests_bytearray_full_return: * - * Returns: (element-type gint gint) (transfer none): + * Returns: (transfer full): */ -GHashTable * -gi_marshalling_tests_ghashtable_int_none_return (void) +GByteArray * +gi_marshalling_tests_bytearray_full_return (void) { - static GHashTable *hash_table = NULL; + GByteArray *array = NULL; + guint8 data[] = { '\0', '1', '\xFF', '3' }; - if (hash_table == NULL) - { - hash_table = g_hash_table_new (NULL, NULL); - g_hash_table_insert (hash_table, GINT_TO_POINTER (-1), GINT_TO_POINTER (1)); - g_hash_table_insert (hash_table, GINT_TO_POINTER (0), GINT_TO_POINTER (0)); - g_hash_table_insert (hash_table, GINT_TO_POINTER (1), GINT_TO_POINTER (-1)); - g_hash_table_insert (hash_table, GINT_TO_POINTER (2), GINT_TO_POINTER (-2)); - } + array = g_byte_array_new (); + g_byte_array_append (array, (const guint8 *) data, G_N_ELEMENTS (data)); - return hash_table; + return array; } /** - * gi_marshalling_tests_ghashtable_utf8_none_return: - * - * Returns: (element-type utf8 utf8) (transfer none): + * gi_marshalling_tests_bytearray_none_in: + * @v: (element-type gint8) (transfer none): */ -GHashTable * -gi_marshalling_tests_ghashtable_utf8_none_return (void) +void +gi_marshalling_tests_bytearray_none_in (GByteArray *v) { - static GHashTable *hash_table = NULL; - - if (hash_table == NULL) - { - hash_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (hash_table, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (hash_table, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (hash_table, (gpointer) "1", (gpointer) "-1"); - g_hash_table_insert (hash_table, (gpointer) "2", (gpointer) "-2"); - } + g_assert_cmpuint (v->len, ==, 4); + g_assert_cmpuint (g_array_index (v, unsigned char, 0), ==, 0); + g_assert_cmpuint (g_array_index (v, unsigned char, 1), ==, 49); + g_assert_cmpuint (g_array_index (v, unsigned char, 2), ==, 0xFF); + g_assert_cmpuint (g_array_index (v, unsigned char, 3), ==, 51); +} - return hash_table; +/** + * gi_marshalling_tests_bytearray_full_out: + * @v: (out) (transfer full): + */ +void +gi_marshalling_tests_bytearray_full_out (GByteArray **v) +{ + *v = gi_marshalling_tests_bytearray_full_return (); } /** - * gi_marshalling_tests_ghashtable_utf8_container_return: - * - * Returns: (element-type utf8 utf8) (transfer container): + * gi_marshalling_tests_bytearray_full_inout: + * @v: (inout) (transfer full): */ -GHashTable * -gi_marshalling_tests_ghashtable_utf8_container_return (void) +void +gi_marshalling_tests_bytearray_full_inout (GByteArray **v) { - GHashTable *hash_table = NULL; + gi_marshalling_tests_bytearray_none_in (*v); + g_byte_array_unref (*v); - hash_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (hash_table, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (hash_table, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (hash_table, (gpointer) "1", (gpointer) "-1"); - g_hash_table_insert (hash_table, (gpointer) "2", (gpointer) "-2"); + guint8 data[] = { 'h', 'e', 'l', '\0', '\xFF' }; - return hash_table; + GByteArray *array = g_byte_array_new (); + g_byte_array_append (array, (const guint8 *) data, G_N_ELEMENTS (data)); + + *v = array; } /** - * gi_marshalling_tests_ghashtable_utf8_full_return: + * gi_marshalling_tests_gbytes_full_return: * - * Returns: (element-type utf8 utf8) (transfer full): + * Returns: (transfer full): */ -GHashTable * -gi_marshalling_tests_ghashtable_utf8_full_return (void) +GBytes * +gi_marshalling_tests_gbytes_full_return (void) { - GHashTable *hash_table = NULL; - - hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - g_hash_table_insert (hash_table, g_strdup ("-1"), g_strdup ("1")); - g_hash_table_insert (hash_table, g_strdup ("0"), g_strdup ("0")); - g_hash_table_insert (hash_table, g_strdup ("1"), g_strdup ("-1")); - g_hash_table_insert (hash_table, g_strdup ("2"), g_strdup ("-2")); + static guint8 data[] = { 0, 49, 0xFF, 51 }; - return hash_table; + return g_bytes_new_static (data, G_N_ELEMENTS (data)); } /** - * gi_marshalling_tests_ghashtable_int_none_in: - * @hash_table: (element-type gint gint) (transfer none): + * gi_marshalling_tests_gbytes_none_in: */ void -gi_marshalling_tests_ghashtable_int_none_in (GHashTable *hash_table) +gi_marshalling_tests_gbytes_none_in (GBytes *v) { - g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (-1))), ==, 1); - g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (0))), ==, 0); - g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (1))), ==, -1); - g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (2))), ==, -2); + const guint8 *data; + gsize len; + data = g_bytes_get_data (v, &len); + + g_assert_cmpuint (len, ==, 4); + g_assert_cmpuint (data[0], ==, 0); + g_assert_cmpuint (data[1], ==, 49); + g_assert_cmpuint (data[2], ==, 0xFF); + g_assert_cmpuint (data[3], ==, 51); } /** - * gi_marshalling_tests_ghashtable_utf8_none_in: - * @hash_table: (element-type utf8 utf8) (transfer none): + * gi_marshalling_tests_gstrv_return: + * + * Returns: (transfer full): an array of strings */ -void -gi_marshalling_tests_ghashtable_utf8_none_in (GHashTable *hash_table) +GStrv +gi_marshalling_tests_gstrv_return (void) { - g_assert_cmpstr (g_hash_table_lookup (hash_table, "-1"), ==, "1"); - g_assert_cmpstr (g_hash_table_lookup (hash_table, "0"), ==, "0"); - g_assert_cmpstr (g_hash_table_lookup (hash_table, "1"), ==, "-1"); - g_assert_cmpstr (g_hash_table_lookup (hash_table, "2"), ==, "-2"); + GStrv values = g_new0 (gchar *, 4); + values[0] = g_strdup ("0"); + values[1] = g_strdup ("1"); + values[2] = g_strdup ("2"); + values[3] = NULL; + return values; } /** - * gi_marshalling_tests_ghashtable_double_in: - * @hash_table: (element-type utf8 double) (transfer none): - * - * Meant to test a value type that doesn't fit inside a pointer. + * gi_marshalling_tests_gstrv_in: + * @g_strv: */ void -gi_marshalling_tests_ghashtable_double_in (GHashTable *hash_table) +gi_marshalling_tests_gstrv_in (GStrv g_strv) { - double *value; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "0"); + g_assert_cmpstr (g_strv[1], ==, "1"); + g_assert_cmpstr (g_strv[2], ==, "2"); +} - value = g_hash_table_lookup (hash_table, "-1"); - g_assert_cmpfloat_with_epsilon (*value, -0.1, 0.01); - value = g_hash_table_lookup (hash_table, "0"); - g_assert_cmpfloat (*value, ==, 0.0); - value = g_hash_table_lookup (hash_table, "1"); - g_assert_cmpfloat_with_epsilon (*value, 0.1, 0.01); - value = g_hash_table_lookup (hash_table, "2"); - g_assert_cmpfloat_with_epsilon (*value, 0.2, 0.01); +/** + * gi_marshalling_tests_gstrv_out: + * @g_strv: (out) (transfer none): + */ +void +gi_marshalling_tests_gstrv_out (GStrv *g_strv) +{ + static const gchar *values[] = { "0", "1", "2", NULL }; + *g_strv = (gchar **) values; } /** - * gi_marshalling_tests_ghashtable_float_in: - * @hash_table: (element-type utf8 float) (transfer none): - * - * Meant to test a value type that doesn't fit inside a pointer. + * gi_marshalling_tests_gstrv_out_uninitialized: + * @v: (out) (transfer none): + */ +gboolean +gi_marshalling_tests_gstrv_out_uninitialized (GStrv *v G_GNUC_UNUSED) +{ + return FALSE; +} + +/** + * gi_marshalling_tests_gstrv_inout: + * @g_strv: (inout) (transfer none): */ void -gi_marshalling_tests_ghashtable_float_in (GHashTable *hash_table) +gi_marshalling_tests_gstrv_inout (GStrv *g_strv) { - float *value; + static const gchar *values[] = { "-1", "0", "1", "2", NULL }; - value = g_hash_table_lookup (hash_table, "-1"); - g_assert_cmpfloat_with_epsilon (*value, -0.1f, 0.01f); - value = g_hash_table_lookup (hash_table, "0"); - g_assert_cmpfloat (*value, ==, 0.0f); - value = g_hash_table_lookup (hash_table, "1"); - g_assert_cmpfloat_with_epsilon (*value, 0.1f, 0.01f); - value = g_hash_table_lookup (hash_table, "2"); - g_assert_cmpfloat_with_epsilon (*value, 0.2f, 0.01f); + g_assert (g_strv_length (*g_strv) == 3); + g_assert (strcmp ((*g_strv)[0], "0") == 0); + g_assert (strcmp ((*g_strv)[1], "1") == 0); + g_assert (strcmp ((*g_strv)[2], "2") == 0); + + *g_strv = (gchar **) values; } /** - * gi_marshalling_tests_ghashtable_int64_in: - * @hash_table: (element-type utf8 gint64) (transfer none): + * gi_marshalling_tests_length_array_of_gstrv_transfer_full_return: * - * Meant to test a value type that doesn't fit inside a pointer. + * Returns: (array length=out_length) (element-type GStrv) (transfer full): */ -void -gi_marshalling_tests_ghashtable_int64_in (GHashTable *hash_table) +GStrv * +gi_marshalling_tests_length_array_of_gstrv_transfer_full_return (size_t *out_length) { - gint64 *value; + GStrv *array = g_new0 (GStrv, 3); + GStrv values; - value = g_hash_table_lookup (hash_table, "-1"); - g_assert_cmpint (*value, ==, -1); - value = g_hash_table_lookup (hash_table, "0"); - g_assert_cmpint (*value, ==, 0); - value = g_hash_table_lookup (hash_table, "1"); - g_assert_cmpint (*value, ==, 1); - value = g_hash_table_lookup (hash_table, "2"); - g_assert_cmpint (*value, ==, (gint64) G_MAXUINT32 + 1); + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("0"); + values[1] = g_strdup ("1"); + values[2] = g_strdup ("2"); + values[3] = NULL; + array[0] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("3"); + values[1] = g_strdup ("4"); + values[2] = g_strdup ("5"); + values[3] = NULL; + array[1] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("6"); + values[1] = g_strdup ("7"); + values[2] = g_strdup ("8"); + values[3] = NULL; + array[2] = g_steal_pointer (&values); + + *out_length = 3; + + return array; } /** - * gi_marshalling_tests_ghashtable_uint64_in: - * @hash_table: (element-type utf8 guint64) (transfer none): + * gi_marshalling_tests_length_array_of_gstrv_transfer_container_return: * - * Meant to test a value type that doesn't fit inside a pointer. + * Returns: (array length=out_length) (element-type GStrv) (transfer container): */ -void -gi_marshalling_tests_ghashtable_uint64_in (GHashTable *hash_table) +GStrv * +gi_marshalling_tests_length_array_of_gstrv_transfer_container_return (size_t *out_length) { - guint64 *value; + GStrv *array = g_new0 (GStrv, 3); + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; - value = g_hash_table_lookup (hash_table, "-1"); - g_assert_cmpuint (*value, ==, (guint64) G_MAXUINT32 + 1); - value = g_hash_table_lookup (hash_table, "0"); - g_assert_cmpuint (*value, ==, 0); - value = g_hash_table_lookup (hash_table, "1"); - g_assert_cmpuint (*value, ==, 1); - value = g_hash_table_lookup (hash_table, "2"); - g_assert_cmpuint (*value, ==, 2); + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; + + *out_length = 3; + + return array; } /** - * gi_marshalling_tests_ghashtable_utf8_none_out: - * @hash_table: (out) (element-type utf8 utf8) (transfer none): + * gi_marshalling_tests_length_array_of_gstrv_transfer_none_return: + * + * Returns: (array length=out_length) (element-type GStrv) (transfer none): + */ +GStrv * +gi_marshalling_tests_length_array_of_gstrv_transfer_none_return (size_t *out_length) +{ + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; + static const gchar **array[] = { values0, values1, values2 }; + + *out_length = 3; + + return (GStrv *) array; +} + +/** + * gi_marshalling_tests_length_array_of_gstrv_transfer_none_in: + * @array: (array length=length) (element-type GStrv) (transfer none): */ void -gi_marshalling_tests_ghashtable_utf8_none_out (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_none_in (GStrv *array, size_t length) { - static GHashTable *new_hash_table = NULL; + GStrv g_strv; - if (new_hash_table == NULL) - { - new_hash_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (new_hash_table, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (new_hash_table, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (new_hash_table, (gpointer) "1", (gpointer) "-1"); - g_hash_table_insert (new_hash_table, (gpointer) "2", (gpointer) "-2"); - } + g_assert_cmpint (length, ==, 3); - *hash_table = new_hash_table; + g_strv = array[0]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "0"); + g_assert_cmpstr (g_strv[1], ==, "1"); + g_assert_cmpstr (g_strv[2], ==, "2"); + + g_strv = array[1]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "3"); + g_assert_cmpstr (g_strv[1], ==, "4"); + g_assert_cmpstr (g_strv[2], ==, "5"); + + g_strv = array[2]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "6"); + g_assert_cmpstr (g_strv[1], ==, "7"); + g_assert_cmpstr (g_strv[2], ==, "8"); } /** - * gi_marshalling_tests_ghashtable_utf8_none_out_uninitialized: - * @v: (out) (element-type utf8 utf8) (transfer none): + * gi_marshalling_tests_length_array_of_gstrv_transfer_container_in: + * @array: (array length=length) (element-type GStrv) (transfer container): */ -gboolean -gi_marshalling_tests_ghashtable_utf8_none_out_uninitialized (GHashTable **v G_GNUC_UNUSED) +void +gi_marshalling_tests_length_array_of_gstrv_transfer_container_in (GStrv *array, size_t length) { - return FALSE; + gi_marshalling_tests_length_array_of_gstrv_transfer_none_in (array, length); + + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_ghashtable_utf8_container_out: - * @hash_table: (out) (element-type utf8 utf8) (transfer container): + * gi_marshalling_tests_length_array_of_gstrv_transfer_full_in: + * @array: (array length=length) (element-type GStrv) (transfer full): */ void -gi_marshalling_tests_ghashtable_utf8_container_out (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_full_in (GStrv *array, size_t length) { - *hash_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (*hash_table, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (*hash_table, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (*hash_table, (gpointer) "1", (gpointer) "-1"); - g_hash_table_insert (*hash_table, (gpointer) "2", (gpointer) "-2"); + gi_marshalling_tests_length_array_of_gstrv_transfer_none_in (array, length); + + g_clear_pointer (&array[0], g_strfreev); + g_clear_pointer (&array[1], g_strfreev); + g_clear_pointer (&array[2], g_strfreev); + + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_ghashtable_utf8_container_out_uninitialized: - * @v: (out) (element-type utf8 utf8) (transfer container): + * gi_marshalling_tests_length_array_of_gstrv_transfer_none_out: + * @array_out: (array length=out_length) (out) (element-type GStrv) (transfer none): + * @out_length: (out): */ -gboolean -gi_marshalling_tests_ghashtable_utf8_container_out_uninitialized (GHashTable **v G_GNUC_UNUSED) +void +gi_marshalling_tests_length_array_of_gstrv_transfer_none_out (GStrv **array_out, size_t *out_length) { - return FALSE; + *array_out = gi_marshalling_tests_length_array_of_gstrv_transfer_none_return (out_length); } /** - * gi_marshalling_tests_ghashtable_utf8_full_out: - * @hash_table: (out) (element-type utf8 utf8) (transfer full): + * gi_marshalling_tests_length_array_of_gstrv_transfer_container_out: + * @array_out: (array length=out_length) (out) (element-type GStrv) (transfer container): */ void -gi_marshalling_tests_ghashtable_utf8_full_out (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_container_out (GStrv **array_out, size_t *out_length) { - *hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - g_hash_table_insert (*hash_table, g_strdup ("-1"), g_strdup ("1")); - g_hash_table_insert (*hash_table, g_strdup ("0"), g_strdup ("0")); - g_hash_table_insert (*hash_table, g_strdup ("1"), g_strdup ("-1")); - g_hash_table_insert (*hash_table, g_strdup ("2"), g_strdup ("-2")); + *array_out = gi_marshalling_tests_length_array_of_gstrv_transfer_container_return (out_length); } /** - * gi_marshalling_tests_ghashtable_utf8_full_out_uninitialized: - * @v: (out) (element-type utf8 utf8) (transfer full): + * gi_marshalling_tests_length_array_of_gstrv_transfer_full_out: + * @array_out: (array length=out_length) (out) (element-type GStrv) (transfer full): */ -gboolean -gi_marshalling_tests_ghashtable_utf8_full_out_uninitialized (GHashTable **v G_GNUC_UNUSED) +void +gi_marshalling_tests_length_array_of_gstrv_transfer_full_out (GStrv **array_out, size_t *out_length) { - return FALSE; + *array_out = gi_marshalling_tests_length_array_of_gstrv_transfer_full_return (out_length); } /** - * gi_marshalling_tests_ghashtable_utf8_none_inout: - * @hash_table: (inout) (element-type utf8 utf8) (transfer none): + * gi_marshalling_tests_length_array_of_gstrv_transfer_full_inout: + * @array_inout: (array length=inout_length) (inout) (element-type GStrv) (transfer full): + * @inout_length: (inout): */ void -gi_marshalling_tests_ghashtable_utf8_none_inout (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_full_inout (GStrv **array_inout, size_t *inout_length) { - static GHashTable *new_hash_table = NULL; + GStrv *array = g_new0 (GStrv, 4); + GStrv values; - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); + g_assert_nonnull (inout_length); + g_assert_nonnull (array_inout); + gi_marshalling_tests_length_array_of_gstrv_transfer_full_in ( + g_steal_pointer (array_inout), *inout_length); - if (new_hash_table == NULL) - { - new_hash_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (new_hash_table, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (new_hash_table, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (new_hash_table, (gpointer) "1", (gpointer) "1"); - } + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("0"); + values[2] = g_strdup ("1"); + values[3] = g_strdup ("2"); + values[4] = NULL; + array[0] = g_steal_pointer (&values); - *hash_table = new_hash_table; + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("3"); + values[2] = g_strdup ("4"); + values[3] = g_strdup ("5"); + values[4] = NULL; + array[1] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("6"); + values[2] = g_strdup ("7"); + values[3] = g_strdup ("8"); + values[4] = NULL; + array[2] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("9"); + values[2] = g_strdup ("10"); + values[3] = g_strdup ("11"); + values[4] = NULL; + array[3] = g_steal_pointer (&values); + + *array_inout = (GStrv *) array; + *inout_length = 4; } /** - * gi_marshalling_tests_ghashtable_utf8_container_inout: - * @hash_table: (inout) (element-type utf8 utf8) (transfer container): + * gi_marshalling_tests_length_array_of_gstrv_transfer_none_inout: + * @array_inout: (array length=inout_length) (inout) (element-type GStrv) (transfer none): + * @inout_length: (inout): */ void -gi_marshalling_tests_ghashtable_utf8_container_inout (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_none_inout (GStrv **array_inout, size_t *inout_length) { - GHashTable *result = g_hash_table_new (g_str_hash, g_str_equal); - - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; + static const gchar *values3[] = { "-1", "9", "10", "11", NULL }; + static const gchar **array[] = { values0, values1, values2, values3 }; - g_hash_table_insert (result, (gpointer) "-1", (gpointer) "1"); - g_hash_table_insert (result, (gpointer) "0", (gpointer) "0"); - g_hash_table_insert (result, (gpointer) "1", (gpointer) "1"); + g_assert_nonnull (inout_length); + gi_marshalling_tests_length_array_of_gstrv_transfer_none_in (*array_inout, *inout_length); - g_hash_table_unref (*hash_table); - *hash_table = result; + *array_inout = (GStrv *) array; + *inout_length = 4; } /** - * gi_marshalling_tests_ghashtable_utf8_full_inout: - * @hash_table: (inout) (element-type utf8 utf8) (transfer full): + * gi_marshalling_tests_length_array_of_gstrv_transfer_container_inout: + * @array_inout: (array length=inout_length) (inout) (element-type GStrv) (transfer container): + * @inout_length: (inout): */ void -gi_marshalling_tests_ghashtable_utf8_full_inout (GHashTable **hash_table) +gi_marshalling_tests_length_array_of_gstrv_transfer_container_inout (GStrv **array_inout, size_t *inout_length) { - GHashTable *result = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, g_free); + GStrv *array = g_new0 (GStrv, 4); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; + static const gchar *values3[] = { "-1", "9", "10", "11", NULL }; - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); - g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); + g_assert_nonnull (inout_length); + g_assert_nonnull (array_inout); + gi_marshalling_tests_length_array_of_gstrv_transfer_container_in (*array_inout, *inout_length); - g_hash_table_insert (result, g_strdup ("-1"), g_strdup ("1")); - g_hash_table_insert (result, g_strdup ("0"), g_strdup ("0")); - g_hash_table_insert (result, g_strdup ("1"), g_strdup ("1")); + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; + array[3] = (GStrv) values3; - g_hash_table_unref (*hash_table); - *hash_table = result; + *array_inout = (GStrv *) array; + *inout_length = 4; } /** - * gi_marshalling_tests_gvalue_return: + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_return: * - * Returns: (transfer none): + * Returns: (array zero-terminated) (element-type GStrv) (transfer full): */ -GValue * -gi_marshalling_tests_gvalue_return (void) +GStrv * +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_return (void) { - static GValue *value = NULL; + GStrv *array = g_new0 (GStrv, 4); + GStrv values; - if (value == NULL) - { - value = g_new0 (GValue, 1); - g_value_init (value, G_TYPE_INT); - g_value_set_int (value, 42); - } + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("0"); + values[1] = g_strdup ("1"); + values[2] = g_strdup ("2"); + values[3] = NULL; + array[0] = g_steal_pointer (&values); - return value; + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("3"); + values[1] = g_strdup ("4"); + values[2] = g_strdup ("5"); + values[3] = NULL; + array[1] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("6"); + values[1] = g_strdup ("7"); + values[2] = g_strdup ("8"); + values[3] = NULL; + array[2] = g_steal_pointer (&values); + + return array; } /** - * gi_marshalling_tests_gvalue_noncanonical_nan_float: + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_return: * - * Returns: (transfer none): + * Returns: (array zero-terminated) (element-type GStrv) (transfer container): */ -GValue * -gi_marshalling_tests_gvalue_noncanonical_nan_float (void) +GStrv * +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_return (void) { - static GValue *value = NULL; + GStrv *array = g_new0 (GStrv, 4); + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; - if (value == NULL) - { - value = g_new0 (GValue, 1); - g_value_init (value, G_TYPE_FLOAT); - g_value_set_float (value, noncanonical_nan_float ()); - } + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; + array[3] = NULL; - return value; + return array; } /** - * gi_marshalling_tests_gvalue_noncanonical_nan_double: + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_return: * - * Returns: (transfer none): + * Returns: (array zero-terminated) (element-type GStrv) (transfer none): */ -GValue * -gi_marshalling_tests_gvalue_noncanonical_nan_double (void) +GStrv * +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_return (void) { - static GValue *value = NULL; - - if (value == NULL) - { - value = g_new0 (GValue, 1); - g_value_init (value, G_TYPE_DOUBLE); - g_value_set_double (value, noncanonical_nan_double ()); - } + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; + static const gchar **array[] = { values0, values1, values2, NULL }; - return value; + return (GStrv *) array; } /** - * gi_marshalling_tests_gvalue_in: - * @value: (transfer none): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_return: + * + * Returns: (array fixed-size=3) (element-type GStrv) (transfer full): */ -void -gi_marshalling_tests_gvalue_in (GValue *value) +GStrv * +gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_return (void) { - g_assert_cmpint (g_value_get_int (value), ==, 42); -} + GStrv *array = g_new0 (GStrv, 3); + GStrv values; -/** - * gi_marshalling_tests_gvalue_int64_in: - * @value: (transfer none): - */ -void -gi_marshalling_tests_gvalue_int64_in (GValue *value) -{ - g_assert_cmpint (g_value_get_int64 (value), ==, G_MAXINT64); -} + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("0"); + values[1] = g_strdup ("1"); + values[2] = g_strdup ("2"); + values[3] = NULL; + array[0] = g_steal_pointer (&values); -/** - * gi_marshalling_tests_gvalue_in_with_type: - * @value: (transfer none): - * @type: - */ -void -gi_marshalling_tests_gvalue_in_with_type (GValue *value, GType type) -{ - g_assert (g_type_is_a (G_VALUE_TYPE (value), type)); -} + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("3"); + values[1] = g_strdup ("4"); + values[2] = g_strdup ("5"); + values[3] = NULL; + array[1] = g_steal_pointer (&values); -/** - * gi_marshalling_tests_gvalue_in_with_modification: - * @value: (transfer none): - * - * Expects a GValue passed by reference which is then modified by - * this function. - */ -void -gi_marshalling_tests_gvalue_in_with_modification (GValue *value) -{ - g_assert_cmpint (g_value_get_int (value), ==, 42); - g_value_set_int (value, 24); + values = g_new0 (gchar *, 4); + values[0] = g_strdup ("6"); + values[1] = g_strdup ("7"); + values[2] = g_strdup ("8"); + values[3] = NULL; + array[2] = g_steal_pointer (&values); + + return array; } /** - * gi_marshalling_tests_gvalue_in_enum: - * @value: (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in: + * @array: (array zero-terminated) (element-type GStrv) (transfer none): */ void -gi_marshalling_tests_gvalue_in_enum (GValue *value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in (GStrv *array) { - if (!G_VALUE_HOLDS_ENUM (value)) - g_critical ("Expected enum, got %s", G_VALUE_TYPE_NAME (value)); - g_assert (g_value_get_enum (value) == GI_MARSHALLING_TESTS_ENUM_VALUE3); + GStrv g_strv; + + g_strv = array[0]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "0"); + g_assert_cmpstr (g_strv[1], ==, "1"); + g_assert_cmpstr (g_strv[2], ==, "2"); + + g_strv = array[1]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "3"); + g_assert_cmpstr (g_strv[1], ==, "4"); + g_assert_cmpstr (g_strv[2], ==, "5"); + + g_strv = array[2]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "6"); + g_assert_cmpstr (g_strv[1], ==, "7"); + g_assert_cmpstr (g_strv[2], ==, "8"); + + g_assert_null (array[3]); } /** - * gi_marshalling_tests_gvalue_in_flags: - * @value: (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_in: + * @array: (array zero-terminated) (element-type GStrv) (transfer container): */ void -gi_marshalling_tests_gvalue_in_flags (GValue *value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_in (GStrv *array) { - if (!G_VALUE_HOLDS_FLAGS (value)) - g_critical ("Expected flags, got %s", G_VALUE_TYPE_NAME (value)); - g_assert_cmpint (g_value_get_flags (value), ==, GI_MARSHALLING_TESTS_FLAGS_VALUE3); + gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in (array); + + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_gvalue_out: - * @value: (out) (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_in: + * @array: (array zero-terminated) (element-type GStrv) (transfer full): */ void -gi_marshalling_tests_gvalue_out (GValue **value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_in (GStrv *array) { - static GValue *new_value = NULL; + gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in (array); - if (new_value == NULL) - { - new_value = g_new0 (GValue, 1); - g_value_init (new_value, G_TYPE_INT); - g_value_set_int (new_value, 42); - } + for (int i = 0; array && array[i] != NULL; i++) + g_clear_pointer (&array[i], g_strfreev); - *value = new_value; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_gvalue_out_uninitialized: - * @v: (out) (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_out: + * @array_out: (array zero-terminated) (out) (element-type GStrv) (transfer none): */ -gboolean -gi_marshalling_tests_gvalue_out_uninitialized (GValue **v G_GNUC_UNUSED) +void +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_out (GStrv **array_out) { - return FALSE; + *array_out = gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_return (); } /** - * gi_marshalling_tests_gvalue_int64_out: - * @value: (out) (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_out: + * @array_out: (array zero-terminated) (out) (element-type GStrv) (transfer container): */ void -gi_marshalling_tests_gvalue_int64_out (GValue **value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_out (GStrv **array_out) { - static GValue *new_value = NULL; - - if (new_value == NULL) - { - new_value = g_new0 (GValue, 1); - g_value_init (new_value, G_TYPE_INT64); - g_value_set_int64 (new_value, G_MAXINT64); - } - - *value = new_value; + *array_out = gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_return (); } /** - * gi_marshalling_tests_gvalue_out_caller_allocates: - * @value: (out) (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_out: + * @array_out: (array zero-terminated) (out) (element-type GStrv) (transfer full): */ void -gi_marshalling_tests_gvalue_out_caller_allocates (GValue *value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_out (GStrv **array_out) { - g_value_init (value, G_TYPE_INT); - g_value_set_int (value, 42); + *array_out = gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_return (); } /** - * gi_marshalling_tests_gvalue_inout: - * @value: (inout) (transfer none): + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_inout: + * @array_inout: (array zero-terminated) (inout) (element-type GStrv) (transfer full): */ void -gi_marshalling_tests_gvalue_inout (GValue **value) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_inout (GStrv **array_inout) { - g_assert_cmpint (g_value_get_int (*value), ==, 42); - g_value_unset (*value); - g_value_init (*value, G_TYPE_STRING); - g_value_set_string (*value, "42"); + GStrv *array = g_new0 (GStrv, 5); + GStrv values; + + gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_in ( + g_steal_pointer (array_inout)); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("0"); + values[2] = g_strdup ("1"); + values[3] = g_strdup ("2"); + values[4] = NULL; + array[0] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("3"); + values[2] = g_strdup ("4"); + values[3] = g_strdup ("5"); + values[4] = NULL; + array[1] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("6"); + values[2] = g_strdup ("7"); + values[3] = g_strdup ("8"); + values[4] = NULL; + array[2] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("9"); + values[2] = g_strdup ("10"); + values[3] = g_strdup ("11"); + values[4] = NULL; + array[3] = g_steal_pointer (&values); + + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_gvalue_flat_array: - * @n_values: number of values - * @values: (array length=n_values): an array containing values + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_inout: + * @array_inout: (array zero-terminated) (inout) (element-type GStrv) (transfer none): */ void -gi_marshalling_tests_gvalue_flat_array (guint n_values, const GValue *values) +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_inout (GStrv **array_inout) { - g_assert (n_values == 3); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; + static const gchar *values3[] = { "-1", "9", "10", "11", NULL }; + static const gchar **array[] = { values0, values1, values2, values3, NULL }; - g_assert_cmpint (g_value_get_int (&values[0]), ==, 42); - g_assert_cmpstr (g_value_get_string (&values[1]), ==, "42"); - g_assert_cmpint (g_value_get_boolean (&values[2]), ==, TRUE); + gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in (*array_inout); + + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_return_gvalue_flat_array: - * - * Returns: (array fixed-size=3) (transfer full): a flat GValue array + * gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_inout: + * @array_inout: (array zero-terminated) (inout) (element-type GStrv) (transfer container): */ -GValue * -gi_marshalling_tests_return_gvalue_flat_array (void) +void +gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_inout (GStrv **array_inout) { - GValue *array = g_new0 (GValue, 3); + GStrv *array = g_new0 (GStrv, 4); - g_value_init (&array[0], G_TYPE_INT); - g_value_set_int (&array[0], 42); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; + static const gchar *values3[] = { "-1", "9", "10", "11", NULL }; - g_value_init (&array[1], G_TYPE_STRING); - g_value_set_static_string (&array[1], "42"); + gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_in (*array_inout); - g_value_init (&array[2], G_TYPE_BOOLEAN); - g_value_set_boolean (&array[2], TRUE); + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; + array[3] = (GStrv) values3; - return array; + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_return_gvalue_zero_terminated_array: + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_return: * - * Returns: (array zero-terminated) (transfer full): a flat GValue array + * Returns: (array fixed-size=3) (element-type GStrv) (transfer container): */ -GValue * -gi_marshalling_tests_return_gvalue_zero_terminated_array (void) +GStrv * +gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_return (void) { - GValue *array = g_new0 (GValue, 4); - - g_value_init (&array[0], G_TYPE_INT); - g_value_set_int (&array[0], 42); - - g_value_init (&array[1], G_TYPE_STRING); - g_value_set_static_string (&array[1], "42"); + GStrv *array = g_new0 (GStrv, 3); + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; - g_value_init (&array[2], G_TYPE_BOOLEAN); - g_value_set_boolean (&array[2], TRUE); + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; return array; } /** - * gi_marshalling_tests_gvalue_round_trip: - * @value: The first GValue + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_return: * - * Returns: (transfer none): + * Returns: (array fixed-size=3) (element-type GStrv) (transfer none): */ -GValue * -gi_marshalling_tests_gvalue_round_trip (GValue *value) +GStrv * +gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_return (void) { - return value; + static const gchar *values0[] = { "0", "1", "2", NULL }; + static const gchar *values1[] = { "3", "4", "5", NULL }; + static const gchar *values2[] = { "6", "7", "8", NULL }; + static const gchar **array[] = { values0, values1, values2 }; + + return (GStrv *) array; } /** - * gi_marshalling_tests_gvalue_copy: - * @value: The first GValue - * - * Returns: (transfer full): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in: + * @array: (array fixed-size=3) (element-type GStrv) (transfer none): */ -GValue * -gi_marshalling_tests_gvalue_copy (GValue *value) +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in (GStrv *array) { - GValue *return_value = g_new0 (GValue, 1); + GStrv g_strv; - g_value_init (return_value, G_VALUE_TYPE (value)); - g_value_copy (value, return_value); + g_strv = array[0]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "0"); + g_assert_cmpstr (g_strv[1], ==, "1"); + g_assert_cmpstr (g_strv[2], ==, "2"); - return return_value; + g_strv = array[1]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "3"); + g_assert_cmpstr (g_strv[1], ==, "4"); + g_assert_cmpstr (g_strv[2], ==, "5"); + + g_strv = array[2]; + g_assert_cmpint (g_strv_length (g_strv), ==, 3); + g_assert_cmpstr (g_strv[0], ==, "6"); + g_assert_cmpstr (g_strv[1], ==, "7"); + g_assert_cmpstr (g_strv[2], ==, "8"); } /** - * gi_marshalling_tests_gvalue_flat_array_round_trip: - * @one: The first GValue - * @two: The second GValue - * @three: The third GValue - * - * Returns: (array fixed-size=3) (transfer full): a flat array of [@one, @two, @three] + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_in: + * @array: (array fixed-size=3) (element-type GStrv) (transfer container): */ -GValue * -gi_marshalling_tests_gvalue_flat_array_round_trip (const GValue one, const GValue two, const GValue three) +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_in (GStrv *array) { - GValue *array = g_new (GValue, 3); - array[0] = one; - array[1] = two; - array[2] = three; + gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in (array); - return array; + g_clear_pointer (&array, g_free); } /** - * gi_marshalling_tests_gclosure_in: - * @closure: (transfer none): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_in: + * @array: (array fixed-size=3) (element-type GStrv) (transfer full): */ void -gi_marshalling_tests_gclosure_in (GClosure *closure) +gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_in (GStrv *array) { - GValue return_value = { - 0, - }; + gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in (array); - g_value_init (&return_value, G_TYPE_INT); - - g_closure_invoke (closure, &return_value, 0, NULL, NULL); - - g_assert_cmpint (g_value_get_int (&return_value), ==, 42); + g_clear_pointer (&array[0], g_strfreev); + g_clear_pointer (&array[1], g_strfreev); + g_clear_pointer (&array[2], g_strfreev); - g_value_unset (&return_value); + g_clear_pointer (&array, g_free); } -static gint -_closure_return_42 (void) +/** + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_out: + * @array_out: (array fixed-size=3) (out) (element-type GStrv) (transfer none): + */ +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_out (GStrv **array_out) { - return 42; + *array_out = gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_return (); } -static void -_marshal_INT__VOID (GClosure *closure, - GValue *return_value, - guint n_param_values G_GNUC_UNUSED, - const GValue *param_values G_GNUC_UNUSED, - gpointer invocation_hint G_GNUC_UNUSED, - gpointer marshal_data G_GNUC_UNUSED) +/** + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_out: + * @array_out: (array fixed-size=3) (out) (element-type GStrv) (transfer container): + */ +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_out (GStrv **array_out) { - typedef gint (*GMarshalFunc_INT__VOID) (void); - register GMarshalFunc_INT__VOID callback; - register GCClosure *cc = (GCClosure *) closure; - - callback = (GMarshalFunc_INT__VOID) cc->callback; - g_value_set_int (return_value, callback ()); + *array_out = gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_return (); } /** - * gi_marshalling_tests_gclosure_return: - * - * Return: a #GClosure + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_out: + * @array_out: (array fixed-size=3) (out) (element-type GStrv) (transfer full): */ -GClosure * -gi_marshalling_tests_gclosure_return (void) +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_out (GStrv **array_out) { - GClosure *closure = g_cclosure_new ((GCallback) _closure_return_42, - NULL, NULL); - g_closure_set_marshal (closure, _marshal_INT__VOID); - - return closure; + *array_out = gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_return (); } /** - * gi_marshalling_tests_callback_return_value_only: - * @callback: (scope call): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_inout: + * @array_inout: (array fixed-size=3) (inout) (element-type GStrv) (transfer full): */ -glong -gi_marshalling_tests_callback_return_value_only (GIMarshallingTestsCallbackReturnValueOnly callback) +void +gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_inout (GStrv **array_inout) { - return callback (); + GStrv *array = g_new0 (GStrv, 3); + GStrv values; + + gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_in ( + g_steal_pointer (array_inout)); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("0"); + values[2] = g_strdup ("1"); + values[3] = g_strdup ("2"); + values[4] = NULL; + array[0] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("3"); + values[2] = g_strdup ("4"); + values[3] = g_strdup ("5"); + values[4] = NULL; + array[1] = g_steal_pointer (&values); + + values = g_new0 (gchar *, 5); + values[0] = g_strdup ("-1"); + values[1] = g_strdup ("6"); + values[2] = g_strdup ("7"); + values[3] = g_strdup ("8"); + values[4] = NULL; + array[2] = g_steal_pointer (&values); + + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_callback_one_out_parameter: - * @callback: (scope call): - * @a: (out): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_inout: + * @array_inout: (array fixed-size=3) (inout) (element-type GStrv) (transfer none): */ void -gi_marshalling_tests_callback_one_out_parameter (GIMarshallingTestsCallbackOneOutParameter callback, gfloat *a) +gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_inout (GStrv **array_inout) { - callback (a); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; + static const gchar **array[] = { values0, values1, values2 }; + + gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in (*array_inout); + + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_callback_multiple_out_parameters: - * @callback: (scope call): - * @a: (out): - * @b: (out): + * gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_inout: + * @array_inout: (array fixed-size=3) (inout) (element-type GStrv) (transfer container): */ void -gi_marshalling_tests_callback_multiple_out_parameters (GIMarshallingTestsCallbackMultipleOutParameters callback, gfloat *a, gfloat *b) +gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_inout (GStrv **array_inout) { - callback (a, b); -} + GStrv *array = g_new0 (GStrv, 3); + static const gchar *values0[] = { "-1", "0", "1", "2", NULL }; + static const gchar *values1[] = { "-1", "3", "4", "5", NULL }; + static const gchar *values2[] = { "-1", "6", "7", "8", NULL }; -/** - * gi_marshalling_tests_callback_return_value_and_one_out_parameter: - * @callback: (scope call): - * @a: (out): - */ -glong -gi_marshalling_tests_callback_return_value_and_one_out_parameter (GIMarshallingTestsCallbackReturnValueAndOneOutParameter callback, glong *a) -{ - return callback (a); + gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_in (*array_inout); + + array[0] = (GStrv) values0; + array[1] = (GStrv) values1; + array[2] = (GStrv) values2; + + *array_inout = (GStrv *) array; } /** - * gi_marshalling_tests_callback_return_value_and_multiple_out_parameters: - * @callback: (scope call): - * @a: (out): - * @b: (out): + * gi_marshalling_tests_glist_int_none_return: + * + * Returns: (element-type gint) (transfer none): */ -glong -gi_marshalling_tests_callback_return_value_and_multiple_out_parameters (GIMarshallingTestsCallbackReturnValueAndMultipleOutParameters callback, glong *a, glong *b) +GList * +gi_marshalling_tests_glist_int_none_return (void) { - return callback (a, b); + static GList *list = NULL; + + if (list == NULL) + { + list = g_list_append (list, GINT_TO_POINTER (-1)); + list = g_list_append (list, GINT_TO_POINTER (0)); + list = g_list_append (list, GINT_TO_POINTER (1)); + list = g_list_append (list, GINT_TO_POINTER (2)); + } + + return list; } /** - * gi_marshalling_tests_pointer_in_return: + * gi_marshalling_tests_glist_uint32_none_return: * - * Returns: The same pointer + * Returns: (element-type guint32) (transfer none): */ -gpointer -gi_marshalling_tests_pointer_in_return (gpointer pointer) +GList * +gi_marshalling_tests_glist_uint32_none_return (void) { - return pointer; -} + static GList *list = NULL; -GType -gi_marshalling_tests_genum_get_type (void) -{ - static GType type = 0; - if (G_UNLIKELY (type == 0)) + if (list == NULL) { - static const GEnumValue values[] = { - { GI_MARSHALLING_TESTS_GENUM_VALUE1, - "GI_MARSHALLING_TESTS_GENUM_VALUE1", "value1" }, - { GI_MARSHALLING_TESTS_GENUM_VALUE2, - "GI_MARSHALLING_TESTS_GENUM_VALUE2", "value2" }, - { GI_MARSHALLING_TESTS_GENUM_VALUE3, - "GI_MARSHALLING_TESTS_GENUM_VALUE3", "value3" }, - { 0, NULL, NULL } - }; - type = g_enum_register_static (g_intern_static_string ("GIMarshallingTestsGEnum"), values); + list = g_list_append (list, GUINT_TO_POINTER (0)); + list = g_list_append (list, GUINT_TO_POINTER (G_MAXUINT32)); } - return type; + return list; } -GIMarshallingTestsGEnum -gi_marshalling_tests_genum_returnv (void) +/** + * gi_marshalling_tests_glist_utf8_none_return: + * + * Returns: (element-type utf8) (transfer none): + */ +GList * +gi_marshalling_tests_glist_utf8_none_return (void) { - return GI_MARSHALLING_TESTS_GENUM_VALUE3; -} + static GList *list = NULL; -void -gi_marshalling_tests_genum_in (GIMarshallingTestsGEnum v) -{ - g_assert_cmpint (v, ==, GI_MARSHALLING_TESTS_GENUM_VALUE3); + if (list == NULL) + { + list = g_list_append (list, (gpointer) "0"); + list = g_list_append (list, (gpointer) "1"); + list = g_list_append (list, (gpointer) "2"); + } + + return list; } /** - * gi_marshalling_tests_genum_out: - * @v: (out): + * gi_marshalling_tests_glist_utf8_container_return: + * + * Returns: (element-type utf8) (transfer container): */ -void -gi_marshalling_tests_genum_out (GIMarshallingTestsGEnum *v) +GList * +gi_marshalling_tests_glist_utf8_container_return (void) { - *v = GI_MARSHALLING_TESTS_GENUM_VALUE3; + GList *list = NULL; + + list = g_list_append (list, (gpointer) "0"); + list = g_list_append (list, (gpointer) "1"); + list = g_list_append (list, (gpointer) "2"); + + return list; } /** - * gi_marshalling_tests_genum_out_uninitialized: - * @v: (out): + * gi_marshalling_tests_glist_utf8_full_return: + * + * Returns: (element-type utf8) (transfer full): */ -gboolean -gi_marshalling_tests_genum_out_uninitialized (GIMarshallingTestsGEnum *v G_GNUC_UNUSED) +GList * +gi_marshalling_tests_glist_utf8_full_return (void) { - return FALSE; + GList *list = NULL; + + list = g_list_append (list, g_strdup ("0")); + list = g_list_append (list, g_strdup ("1")); + list = g_list_append (list, g_strdup ("2")); + + return list; } /** - * gi_marshalling_tests_genum_inout: - * @v: (inout): + * gi_marshalling_tests_glist_int_none_in: + * @list: (element-type gint) (transfer none): */ void -gi_marshalling_tests_genum_inout (GIMarshallingTestsGEnum *v) +gi_marshalling_tests_glist_int_none_in (GList *list) { - g_assert_cmpint (*v, ==, GI_MARSHALLING_TESTS_GENUM_VALUE3); - *v = GI_MARSHALLING_TESTS_GENUM_VALUE1; + g_assert_cmpint (g_list_length (list), ==, 4); + g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 0)), ==, -1); + g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 1)), ==, 0); + g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 2)), ==, 1); + g_assert_cmpint (GPOINTER_TO_INT (g_list_nth_data (list, 3)), ==, 2); } -GIMarshallingTestsEnum -gi_marshalling_tests_enum_returnv (void) +/** + * gi_marshalling_tests_glist_uint32_none_in: + * @list: (element-type guint32) (transfer none): + */ +void +gi_marshalling_tests_glist_uint32_none_in (GList *list) { - return GI_MARSHALLING_TESTS_ENUM_VALUE3; + g_assert_cmpint (g_list_length (list), ==, 2); + g_assert_cmpint (GPOINTER_TO_UINT (g_list_nth_data (list, 0)), ==, 0); + g_assert_cmpint (GPOINTER_TO_UINT (g_list_nth_data (list, 1)), ==, G_MAXUINT32); } +/** + * gi_marshalling_tests_glist_utf8_none_in: + * @list: (element-type utf8) (transfer none): + */ void -gi_marshalling_tests_enum_in (GIMarshallingTestsEnum v) +gi_marshalling_tests_glist_utf8_none_in (GList *list) { - g_assert_cmpint (v, ==, GI_MARSHALLING_TESTS_ENUM_VALUE3); + g_assert_cmpint (g_list_length (list), ==, 3); + g_assert_cmpint (strcmp (g_list_nth_data (list, 0), "0"), ==, 0); + g_assert_cmpint (strcmp (g_list_nth_data (list, 1), "1"), ==, 0); + g_assert_cmpint (strcmp (g_list_nth_data (list, 2), "2"), ==, 0); } /** - * gi_marshalling_tests_enum_out: - * @v: (out): + * gi_marshalling_tests_glist_utf8_container_in: + * @list: (element-type utf8) (transfer container): */ void -gi_marshalling_tests_enum_out (GIMarshallingTestsEnum *v) +gi_marshalling_tests_glist_utf8_container_in (GList *list) { - *v = GI_MARSHALLING_TESTS_ENUM_VALUE3; + gi_marshalling_tests_glist_utf8_none_in (list); + g_list_free (list); } /** - * gi_marshalling_tests_enum_out_uninitialized: - * @v: (out): + * gi_marshalling_tests_glist_utf8_full_in: + * @list: (element-type utf8) (transfer full): */ -gboolean -gi_marshalling_tests_enum_out_uninitialized (GIMarshallingTestsEnum **v G_GNUC_UNUSED) +void +gi_marshalling_tests_glist_utf8_full_in (GList *list) { - return FALSE; + gi_marshalling_tests_glist_utf8_none_in (list); + g_list_free_full (list, g_free); } /** - * gi_marshalling_tests_enum_inout: - * @v: (inout): + * gi_marshalling_tests_glist_utf8_none_out: + * @list: (out) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_enum_inout (GIMarshallingTestsEnum *v) +gi_marshalling_tests_glist_utf8_none_out (GList **list) { - g_assert_cmpint (*v, ==, GI_MARSHALLING_TESTS_ENUM_VALUE3); - *v = GI_MARSHALLING_TESTS_ENUM_VALUE1; -} + static GList *values = NULL; -GType -gi_marshalling_tests_flags_get_type (void) -{ - static GType type = 0; - if (G_UNLIKELY (type == 0)) + if (values == NULL) { - static const GFlagsValue values[] = { - { GI_MARSHALLING_TESTS_FLAGS_VALUE1, - "GI_MARSHALLING_TESTS_FLAGS_VALUE1", "value1" }, - { GI_MARSHALLING_TESTS_FLAGS_VALUE2, - "GI_MARSHALLING_TESTS_FLAGS_VALUE2", "value2" }, - { GI_MARSHALLING_TESTS_FLAGS_VALUE3, - "GI_MARSHALLING_TESTS_FLAGS_VALUE3", "value3" }, - { GI_MARSHALLING_TESTS_FLAGS_MASK, "GI_MARSHALLING_TESTS_FLAGS_MASK", - "mask" }, - { GI_MARSHALLING_TESTS_FLAGS_MASK2, "GI_MARSHALLING_TESTS_FLAGS_MASK2", - "mask2" }, - { 0, NULL, NULL } - }; - type = g_flags_register_static (g_intern_static_string ("GIMarshallingTestsFlags"), values); + values = g_list_append (values, (gpointer) "0"); + values = g_list_append (values, (gpointer) "1"); + values = g_list_append (values, (gpointer) "2"); } - return type; + *list = values; } -GIMarshallingTestsFlags -gi_marshalling_tests_flags_returnv (void) +/** + * gi_marshalling_tests_glist_utf8_none_out_uninitialized: + * @v: (out) (element-type utf8) (transfer none): + */ +gboolean +gi_marshalling_tests_glist_utf8_none_out_uninitialized (GList **v G_GNUC_UNUSED) { - return GI_MARSHALLING_TESTS_FLAGS_VALUE2; + return FALSE; } +/** + * gi_marshalling_tests_glist_utf8_container_out: + * @list: (out) (element-type utf8) (transfer container): + */ void -gi_marshalling_tests_flags_in (GIMarshallingTestsFlags v) +gi_marshalling_tests_glist_utf8_container_out (GList **list) { - g_assert (v == GI_MARSHALLING_TESTS_FLAGS_VALUE2); + *list = NULL; + + *list = g_list_append (*list, (gpointer) "0"); + *list = g_list_append (*list, (gpointer) "1"); + *list = g_list_append (*list, (gpointer) "2"); } -void -gi_marshalling_tests_flags_in_zero (GIMarshallingTestsFlags v) +/** + * gi_marshalling_tests_glist_utf8_container_out_uninitialized: + * @v: (out) (element-type utf8) (transfer container): + */ +gboolean +gi_marshalling_tests_glist_utf8_container_out_uninitialized (GList **v G_GNUC_UNUSED) { - g_assert (v == 0); + return FALSE; } /** - * gi_marshalling_tests_flags_out: - * @v: (out): + * gi_marshalling_tests_glist_utf8_full_out: + * @list: (out) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_flags_out (GIMarshallingTestsFlags *v) +gi_marshalling_tests_glist_utf8_full_out (GList **list) { - *v = GI_MARSHALLING_TESTS_FLAGS_VALUE2; + *list = NULL; + + *list = g_list_append (*list, g_strdup ("0")); + *list = g_list_append (*list, g_strdup ("1")); + *list = g_list_append (*list, g_strdup ("2")); } /** - * gi_marshalling_tests_flags_out_uninitialized: - * @v: (out): + * gi_marshalling_tests_glist_utf8_full_out_uninitialized: + * @v: (out) (element-type utf8) (transfer full): */ gboolean -gi_marshalling_tests_flags_out_uninitialized (GIMarshallingTestsFlags *v G_GNUC_UNUSED) +gi_marshalling_tests_glist_utf8_full_out_uninitialized (GList **v G_GNUC_UNUSED) { return FALSE; } /** - * gi_marshalling_tests_flags_inout: - * @v: (inout): + * gi_marshalling_tests_glist_utf8_none_inout: + * @list: (inout) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_flags_inout (GIMarshallingTestsFlags *v) +gi_marshalling_tests_glist_utf8_none_inout (GList **list) { - g_assert (*v == GI_MARSHALLING_TESTS_FLAGS_VALUE2); - *v = GI_MARSHALLING_TESTS_FLAGS_VALUE1; -} + static GList *values = NULL; -GIMarshallingTestsNoTypeFlags -gi_marshalling_tests_no_type_flags_returnv (void) -{ - return GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2; -} + g_assert_cmpint (g_list_length (*list), ==, 3); + g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); -void -gi_marshalling_tests_no_type_flags_in (GIMarshallingTestsNoTypeFlags v) -{ - g_assert (v == GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2); -} + if (values == NULL) + { + values = g_list_append (values, (gpointer) "-2"); + values = g_list_append (values, (gpointer) "-1"); + values = g_list_append (values, (gpointer) "0"); + values = g_list_append (values, (gpointer) "1"); + } -void -gi_marshalling_tests_no_type_flags_in_zero (GIMarshallingTestsNoTypeFlags v) -{ - g_assert (v == 0); + *list = values; } /** - * gi_marshalling_tests_no_type_flags_out: - * @v: (out): + * gi_marshalling_tests_glist_utf8_container_inout: + * @list: (inout) (element-type utf8) (transfer container): */ void -gi_marshalling_tests_no_type_flags_out (GIMarshallingTestsNoTypeFlags *v) +gi_marshalling_tests_glist_utf8_container_inout (GList **list) { - *v = GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2; -} + GList *result = NULL; -/** - * gi_marshalling_tests_no_type_flags_out_uninitialized: - * @v: (out): - */ -gboolean -gi_marshalling_tests_no_type_flags_out_uninitialized (GIMarshallingTestsNoTypeFlags **v G_GNUC_UNUSED) -{ - return FALSE; + g_assert_cmpint (g_list_length (*list), ==, 3); + g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); + + result = g_list_prepend (result, (gpointer) "1"); + result = g_list_prepend (result, (gpointer) "0"); + result = g_list_prepend (result, (gpointer) "-1"); + result = g_list_prepend (result, (gpointer) "-2"); + + g_list_free (*list); + *list = result; } /** - * gi_marshalling_tests_no_type_flags_inout: - * @v: (inout): + * gi_marshalling_tests_glist_utf8_full_inout: + * @list: (inout) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_no_type_flags_inout (GIMarshallingTestsNoTypeFlags *v) +gi_marshalling_tests_glist_utf8_full_inout (GList **list) { - g_assert (*v == GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2); - *v = GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE1; + GList *result = NULL; + + g_assert_cmpint (g_list_length (*list), ==, 3); + g_assert_cmpstr (g_list_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_list_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_list_nth_data (*list, 2), ==, "2"); + + result = g_list_prepend (result, g_strdup ("1")); + result = g_list_prepend (result, g_strdup ("0")); + result = g_list_prepend (result, g_strdup ("-1")); + result = g_list_prepend (result, g_strdup ("-2")); + + g_list_free_full (*list, g_free); + *list = result; } /** - * gi_marshalling_tests_simple_struct_returnv: + * gi_marshalling_tests_gslist_int_none_return: * - * Returns: (transfer none): + * Returns: (element-type gint) (transfer none): */ -GIMarshallingTestsSimpleStruct * -gi_marshalling_tests_simple_struct_returnv (void) +GSList * +gi_marshalling_tests_gslist_int_none_return (void) { - static GIMarshallingTestsSimpleStruct *struct_ = NULL; + static GSList *list = NULL; - if (struct_ == NULL) + if (list == NULL) { - struct_ = g_new (GIMarshallingTestsSimpleStruct, 1); - - struct_->long_ = 6; - struct_->int8 = 7; + list = g_slist_prepend (list, GINT_TO_POINTER (-1)); + list = g_slist_prepend (list, GINT_TO_POINTER (0)); + list = g_slist_prepend (list, GINT_TO_POINTER (1)); + list = g_slist_prepend (list, GINT_TO_POINTER (2)); + list = g_slist_reverse (list); } - return struct_; + return list; } /** - * gi_marshalling_tests_simple_struct_inv: - * @struct_: (transfer none): + * gi_marshalling_tests_gslist_utf8_none_return: + * + * Returns: (element-type utf8) (transfer none): */ -void -gi_marshalling_tests_simple_struct_inv (GIMarshallingTestsSimpleStruct *struct_) +GSList * +gi_marshalling_tests_gslist_utf8_none_return (void) { - g_assert_cmpint (struct_->long_, ==, 6); - g_assert_cmpint (struct_->int8, ==, 7); -} + static GSList *list = NULL; -void -gi_marshalling_tests_simple_struct_method (GIMarshallingTestsSimpleStruct *struct_) -{ - g_assert_cmpint (struct_->long_, ==, 6); - g_assert_cmpint (struct_->int8, ==, 7); + if (list == NULL) + { + list = g_slist_prepend (list, (gpointer) "0"); + list = g_slist_prepend (list, (gpointer) "1"); + list = g_slist_prepend (list, (gpointer) "2"); + list = g_slist_reverse (list); + } + + return list; } -GType -gi_marshalling_tests_pointer_struct_get_type (void) +/** + * gi_marshalling_tests_gslist_utf8_container_return: + * + * Returns: (element-type utf8) (transfer container): + */ +GSList * +gi_marshalling_tests_gslist_utf8_container_return (void) { - static GType type = 0; + GSList *list = NULL; - if (type == 0) - { - type = g_pointer_type_register_static ("GIMarshallingTestsPointerStruct"); - } + list = g_slist_prepend (list, (gpointer) "0"); + list = g_slist_prepend (list, (gpointer) "1"); + list = g_slist_prepend (list, (gpointer) "2"); + list = g_slist_reverse (list); - return type; + return list; } /** - * gi_marshalling_tests_pointer_struct_returnv: + * gi_marshalling_tests_gslist_utf8_full_return: * - * Returns: (transfer none): + * Returns: (element-type utf8) (transfer full): */ -GIMarshallingTestsPointerStruct * -gi_marshalling_tests_pointer_struct_returnv (void) +GSList * +gi_marshalling_tests_gslist_utf8_full_return (void) { - static GIMarshallingTestsPointerStruct *struct_ = NULL; - - if (struct_ == NULL) - { - struct_ = g_new (GIMarshallingTestsPointerStruct, 1); + GSList *list = NULL; - struct_->long_ = 42; - } + list = g_slist_prepend (list, g_strdup ("0")); + list = g_slist_prepend (list, g_strdup ("1")); + list = g_slist_prepend (list, g_strdup ("2")); + list = g_slist_reverse (list); - return struct_; + return list; } /** - * gi_marshalling_tests_pointer_struct_inv: - * @struct_: (transfer none): + * gi_marshalling_tests_gslist_int_none_in: + * @list: (element-type gint) (transfer none): */ void -gi_marshalling_tests_pointer_struct_inv (GIMarshallingTestsPointerStruct *struct_) +gi_marshalling_tests_gslist_int_none_in (GSList *list) { - g_assert_cmpint (struct_->long_, ==, 42); + g_assert_cmpint (g_slist_length (list), ==, 4); + g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 0)), ==, -1); + g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 1)), ==, 0); + g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 2)), ==, 1); + g_assert_cmpint (GPOINTER_TO_INT (g_slist_nth_data (list, 3)), ==, 2); } -static GIMarshallingTestsBoxedStruct * -gi_marshalling_tests_boxed_struct_copy (GIMarshallingTestsBoxedStruct *struct_) +/** + * gi_marshalling_tests_gslist_utf8_none_in: + * @list: (element-type utf8) (transfer none): + */ +void +gi_marshalling_tests_gslist_utf8_none_in (GSList *list) { - GIMarshallingTestsBoxedStruct *new_struct; - - if (struct_ == NULL) - return NULL; - - new_struct = g_slice_new (GIMarshallingTestsBoxedStruct); - - *new_struct = *struct_; - new_struct->string_ = g_strdup (struct_->string_); - new_struct->g_strv = g_strdupv (struct_->g_strv); + g_assert_cmpint (g_slist_length (list), ==, 3); + g_assert_cmpstr (g_slist_nth_data (list, 0), ==, "0"); + g_assert_cmpstr (g_slist_nth_data (list, 1), ==, "1"); + g_assert_cmpstr (g_slist_nth_data (list, 2), ==, "2"); +} - return new_struct; +/** + * gi_marshalling_tests_gslist_utf8_container_in: + * @list: (element-type utf8) (transfer container): + */ +void +gi_marshalling_tests_gslist_utf8_container_in (GSList *list) +{ + gi_marshalling_tests_gslist_utf8_none_in (list); + g_slist_free (list); } -static void -gi_marshalling_tests_boxed_struct_free (GIMarshallingTestsBoxedStruct *struct_) +/** + * gi_marshalling_tests_gslist_utf8_full_in: + * @list: (element-type utf8) (transfer full): + */ +void +gi_marshalling_tests_gslist_utf8_full_in (GSList *list) { - if (struct_ != NULL) - { - g_free (struct_->string_); - g_clear_pointer (&struct_->g_strv, g_strfreev); - g_slice_free (GIMarshallingTestsBoxedStruct, struct_); - } + gi_marshalling_tests_gslist_utf8_none_in (list); + g_slist_free_full (list, g_free); } -GType -gi_marshalling_tests_boxed_struct_get_type (void) +/** + * gi_marshalling_tests_gslist_utf8_none_out: + * @list: (out) (element-type utf8) (transfer none): + */ +void +gi_marshalling_tests_gslist_utf8_none_out (GSList **list) { - static GType type = 0; + static GSList *values = NULL; - if (type == 0) + if (values == NULL) { - type = g_boxed_type_register_static ("GIMarshallingTestsBoxedStruct", - (GBoxedCopyFunc) - gi_marshalling_tests_boxed_struct_copy, - (GBoxedFreeFunc) gi_marshalling_tests_boxed_struct_free); + values = g_slist_prepend (values, (gpointer) "0"); + values = g_slist_prepend (values, (gpointer) "1"); + values = g_slist_prepend (values, (gpointer) "2"); + values = g_slist_reverse (values); } - return type; + *list = values; } -static GType -gi_marshalling_tests_boxed_glist_get_type (void) -{ - static GType type = 0; - - if (type == 0) - { - type = g_boxed_type_register_static ("GIMarshallingTestsBoxedGList", - (GBoxedCopyFunc) g_list_copy, (GBoxedFreeFunc) g_list_free); - } - - return type; -} - -GIMarshallingTestsBoxedStruct * -gi_marshalling_tests_boxed_struct_new (void) +/** + * gi_marshalling_tests_gslist_utf8_none_out_uninitialized: + * @v: (out) (element-type utf8) (transfer none): + */ +gboolean +gi_marshalling_tests_gslist_utf8_none_out_uninitialized (GSList **v G_GNUC_UNUSED) { - return g_slice_new0 (GIMarshallingTestsBoxedStruct); + return FALSE; } /** - * gi_marshalling_tests_boxed_struct_returnv: - * - * Returns: (transfer none): + * gi_marshalling_tests_gslist_utf8_container_out: + * @list: (out) (element-type utf8) (transfer container): */ -GIMarshallingTestsBoxedStruct * -gi_marshalling_tests_boxed_struct_returnv (void) +void +gi_marshalling_tests_gslist_utf8_container_out (GSList **list) { - static GIMarshallingTestsBoxedStruct *struct_ = NULL; - - if (struct_ == NULL) - { - struct_ = g_new (GIMarshallingTestsBoxedStruct, 1); - - struct_->long_ = 42; - struct_->string_ = g_strdup ("hello"); - struct_->g_strv = g_new0 (gchar *, 4); - struct_->g_strv[0] = g_strdup ("0"); - struct_->g_strv[1] = g_strdup ("1"); - struct_->g_strv[2] = g_strdup ("2"); - struct_->g_strv[3] = NULL; - } + *list = NULL; - return struct_; + *list = g_slist_prepend (*list, (gpointer) "0"); + *list = g_slist_prepend (*list, (gpointer) "1"); + *list = g_slist_prepend (*list, (gpointer) "2"); + *list = g_slist_reverse (*list); } /** - * gi_marshalling_tests_boxed_struct_inv: - * @struct_: (transfer none): + * gi_marshalling_tests_gslist_utf8_container_out_uninitialized: + * @v: (out) (element-type utf8) (transfer container): */ -void -gi_marshalling_tests_boxed_struct_inv (GIMarshallingTestsBoxedStruct *struct_) +gboolean +gi_marshalling_tests_gslist_utf8_container_out_uninitialized (GSList **v G_GNUC_UNUSED) { - g_assert_cmpint (struct_->long_, ==, 42); + return FALSE; } /** - * gi_marshalling_tests_boxed_struct_out: - * @struct_: (out) (transfer none): + * gi_marshalling_tests_gslist_utf8_full_out: + * @list: (out) (element-type utf8) (transfer full): */ void -gi_marshalling_tests_boxed_struct_out (GIMarshallingTestsBoxedStruct **struct_) +gi_marshalling_tests_gslist_utf8_full_out (GSList **list) { - static GIMarshallingTestsBoxedStruct *new_struct = NULL; - - if (new_struct == NULL) - { - new_struct = g_new0 (GIMarshallingTestsBoxedStruct, 1); - - new_struct->long_ = 42; - } + *list = NULL; - *struct_ = new_struct; + *list = g_slist_prepend (*list, g_strdup ("0")); + *list = g_slist_prepend (*list, g_strdup ("1")); + *list = g_slist_prepend (*list, g_strdup ("2")); + *list = g_slist_reverse (*list); } /** - * gi_marshalling_tests_boxed_struct_out_uninitialized: - * @v: (out) (transfer none): + * gi_marshalling_tests_gslist_utf8_full_out_uninitialized: + * @v: (out) (element-type utf8) (transfer full): */ gboolean -gi_marshalling_tests_boxed_struct_out_uninitialized (GIMarshallingTestsBoxedStruct **v G_GNUC_UNUSED) +gi_marshalling_tests_gslist_utf8_full_out_uninitialized (GSList **v G_GNUC_UNUSED) { return FALSE; } /** - * gi_marshalling_tests_boxed_struct_inout: - * @struct_: (inout) (transfer full): + * gi_marshalling_tests_gslist_utf8_none_inout: + * @list: (inout) (element-type utf8) (transfer none): */ void -gi_marshalling_tests_boxed_struct_inout (GIMarshallingTestsBoxedStruct **struct_) +gi_marshalling_tests_gslist_utf8_none_inout (GSList **list) { - g_assert_cmpint ((*struct_)->long_, ==, 42); + static GSList *values = NULL; - g_boxed_free (gi_marshalling_tests_boxed_struct_get_type (), *struct_); - (*struct_) = g_slice_new0 (GIMarshallingTestsBoxedStruct); - (*struct_)->long_ = 0; + g_assert_cmpint (g_slist_length (*list), ==, 3); + g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); + + if (values == NULL) + { + values = g_slist_prepend (values, (gpointer) "-2"); + values = g_slist_prepend (values, (gpointer) "-1"); + values = g_slist_prepend (values, (gpointer) "0"); + values = g_slist_prepend (values, (gpointer) "1"); + values = g_slist_reverse (values); + } + + *list = values; } -static GIMarshallingTestsUnion * -gi_marshalling_tests_union_copy (GIMarshallingTestsUnion *union_) +/** + * gi_marshalling_tests_gslist_utf8_container_inout: + * @list: (inout) (element-type utf8) (transfer container): + */ +void +gi_marshalling_tests_gslist_utf8_container_inout (GSList **list) { - GIMarshallingTestsUnion *new_union; + GSList *result = NULL; - new_union = g_slice_new (GIMarshallingTestsUnion); + g_assert_cmpint (g_slist_length (*list), ==, 3); + g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); - *new_union = *union_; + result = g_slist_prepend (result, (gpointer) "1"); + result = g_slist_prepend (result, (gpointer) "0"); + result = g_slist_prepend (result, (gpointer) "-1"); + result = g_slist_prepend (result, (gpointer) "-2"); - return new_union; + g_slist_free (*list); + *list = result; } -static void -gi_marshalling_tests_union_free (GIMarshallingTestsUnion *union_) +/** + * gi_marshalling_tests_gslist_utf8_full_inout: + * @list: (inout) (element-type utf8) (transfer full): + */ +void +gi_marshalling_tests_gslist_utf8_full_inout (GSList **list) { - g_slice_free (GIMarshallingTestsUnion, union_); + GSList *result = NULL; + + g_assert_cmpint (g_slist_length (*list), ==, 3); + g_assert_cmpstr (g_slist_nth_data (*list, 0), ==, "0"); + g_assert_cmpstr (g_slist_nth_data (*list, 1), ==, "1"); + g_assert_cmpstr (g_slist_nth_data (*list, 2), ==, "2"); + + result = g_slist_prepend (result, g_strdup ("1")); + result = g_slist_prepend (result, g_strdup ("0")); + result = g_slist_prepend (result, g_strdup ("-1")); + result = g_slist_prepend (result, g_strdup ("-2")); + + g_slist_free_full (*list, g_free); + *list = result; } -GType -gi_marshalling_tests_union_get_type (void) +/** + * gi_marshalling_tests_ghashtable_int_none_return: + * + * Returns: (element-type gint gint) (transfer none): + */ +GHashTable * +gi_marshalling_tests_ghashtable_int_none_return (void) { - static GType type = 0; + static GHashTable *hash_table = NULL; - if (type == 0) + if (hash_table == NULL) { - type = g_boxed_type_register_static ("GIMarshallingTestsUnion", - (GBoxedCopyFunc) - gi_marshalling_tests_union_copy, - (GBoxedFreeFunc) gi_marshalling_tests_union_free); + hash_table = g_hash_table_new (NULL, NULL); + g_hash_table_insert (hash_table, GINT_TO_POINTER (-1), GINT_TO_POINTER (1)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (0), GINT_TO_POINTER (0)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (1), GINT_TO_POINTER (-1)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (2), GINT_TO_POINTER (-2)); } - return type; + return hash_table; } /** - * gi_marshalling_tests_union_returnv: + * gi_marshalling_tests_ghashtable_utf8_none_return: * - * Returns: (transfer none): + * Returns: (element-type utf8 utf8) (transfer none): */ -GIMarshallingTestsUnion * -gi_marshalling_tests_union_returnv (void) +GHashTable * +gi_marshalling_tests_ghashtable_utf8_none_return (void) { - static GIMarshallingTestsUnion *union_ = NULL; + static GHashTable *hash_table = NULL; - if (union_ == NULL) + if (hash_table == NULL) { - union_ = g_new (GIMarshallingTestsUnion, 1); - - union_->long_ = 42; + hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (hash_table, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (hash_table, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (hash_table, (gpointer) "1", (gpointer) "-1"); + g_hash_table_insert (hash_table, (gpointer) "2", (gpointer) "-2"); } - return union_; + return hash_table; } /** - * gi_marshalling_tests_union_inv: - * @union_: (transfer none): + * gi_marshalling_tests_ghashtable_utf8_container_return: + * + * Returns: (element-type utf8 utf8) (transfer container): */ -void -gi_marshalling_tests_union_inv (GIMarshallingTestsUnion *union_) +GHashTable * +gi_marshalling_tests_ghashtable_utf8_container_return (void) { - g_assert_cmpint (union_->long_, ==, 42); -} + GHashTable *hash_table = NULL; -void -gi_marshalling_tests_union_method (GIMarshallingTestsUnion *union_) -{ - g_assert_cmpint (union_->long_, ==, 42); + hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (hash_table, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (hash_table, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (hash_table, (gpointer) "1", (gpointer) "-1"); + g_hash_table_insert (hash_table, (gpointer) "2", (gpointer) "-2"); + + return hash_table; } /** - * gi_marshalling_tests_structured_union_new: - * @type: Type of #GIMarshallingTestsStructuredUnion to create + * gi_marshalling_tests_ghashtable_utf8_full_return: + * + * Returns: (element-type utf8 utf8) (transfer full): */ -GIMarshallingTestsStructuredUnion * -gi_marshalling_tests_structured_union_new (GIMarshallingTestsStructuredUnionType type) +GHashTable * +gi_marshalling_tests_ghashtable_utf8_full_return (void) { - GIMarshallingTestsStructuredUnion *new_union; - - new_union = g_new0 (GIMarshallingTestsStructuredUnion, 1); - new_union->type = type; + GHashTable *hash_table = NULL; - switch (type) - { - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: - break; + hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (hash_table, g_strdup ("-1"), g_strdup ("1")); + g_hash_table_insert (hash_table, g_strdup ("0"), g_strdup ("0")); + g_hash_table_insert (hash_table, g_strdup ("1"), g_strdup ("-1")); + g_hash_table_insert (hash_table, g_strdup ("2"), g_strdup ("-2")); - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: - new_union->simple_struct.parent.long_ = 6; - new_union->simple_struct.parent.int8 = 7; - break; + return hash_table; +} - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: - new_union->nested_struct.parent.simple_struct.long_ = 6; - new_union->nested_struct.parent.simple_struct.int8 = 7; - break; - - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: - new_union->boxed_struct.parent.long_ = 42; - new_union->boxed_struct.parent.string_ = g_strdup ("hello"); - new_union->boxed_struct.parent.g_strv = g_new0 (gchar *, 4); - new_union->boxed_struct.parent.g_strv[0] = g_strdup ("0"); - new_union->boxed_struct.parent.g_strv[1] = g_strdup ("1"); - new_union->boxed_struct.parent.g_strv[2] = g_strdup ("2"); - new_union->boxed_struct.parent.g_strv[3] = NULL; - break; - - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: - new_union->boxed_struct_ptr.parent = g_boxed_copy ( - gi_marshalling_tests_boxed_struct_get_type (), - gi_marshalling_tests_boxed_struct_returnv ()); - break; - - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: - new_union->pointer_struct.parent.long_ = 42; - break; - - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: - new_union->single_union.parent.union_.long_ = 42; - break; +/** + * gi_marshalling_tests_ghashtable_int_none_in: + * @hash_table: (element-type gint gint) (transfer none): + */ +void +gi_marshalling_tests_ghashtable_int_none_in (GHashTable *hash_table) +{ + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (-1))), ==, 1); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (0))), ==, 0); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (1))), ==, -1); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (2))), ==, -2); +} - default: - g_free (new_union); - g_return_val_if_reached (NULL); - } +/** + * gi_marshalling_tests_ghashtable_utf8_none_in: + * @hash_table: (element-type utf8 utf8) (transfer none): + */ +void +gi_marshalling_tests_ghashtable_utf8_none_in (GHashTable *hash_table) +{ + g_assert_cmpstr (g_hash_table_lookup (hash_table, "-1"), ==, "1"); + g_assert_cmpstr (g_hash_table_lookup (hash_table, "0"), ==, "0"); + g_assert_cmpstr (g_hash_table_lookup (hash_table, "1"), ==, "-1"); + g_assert_cmpstr (g_hash_table_lookup (hash_table, "2"), ==, "-2"); +} - return new_union; +/** + * gi_marshalling_tests_ghashtable_utf8_container_in: + * @hash_table: (element-type utf8 utf8) (transfer container): + */ +void +gi_marshalling_tests_ghashtable_utf8_container_in (GHashTable *hash_table) +{ + gi_marshalling_tests_ghashtable_utf8_none_in (hash_table); + g_hash_table_steal_all (hash_table); + g_hash_table_unref (hash_table); } -static GIMarshallingTestsStructuredUnion * -gi_marshalling_tests_structured_union_copy (GIMarshallingTestsStructuredUnion *union_) +static gboolean +hash_table_free_helper (gpointer key, gpointer value, gpointer data G_GNUC_UNUSED) { - GIMarshallingTestsStructuredUnion *new_union; + g_free (key); + g_free (value); + return TRUE; +} - new_union = g_new (GIMarshallingTestsStructuredUnion, 1); - *new_union = *union_; +/** + * gi_marshalling_tests_ghashtable_utf8_full_in: + * @hash_table: (element-type utf8 utf8) (transfer full): + */ +void +gi_marshalling_tests_ghashtable_utf8_full_in (GHashTable *hash_table) +{ + gi_marshalling_tests_ghashtable_utf8_none_in (hash_table); - switch (union_->type) - { - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: - new_union->boxed_struct.parent.string_ = g_strdup (union_->boxed_struct.parent.string_); - if (union_->boxed_struct.parent.g_strv) - { - guint length = g_strv_length (union_->boxed_struct.parent.g_strv); - guint i; + /* Free the keys and values manually. Do not rely on the binding passing in a + * GHashTable with the destroy functions set. */ + g_hash_table_foreach_steal (hash_table, hash_table_free_helper, NULL); - new_union->boxed_struct.parent.g_strv = g_new0 (gchar *, length + 1); - for (i = 0; i < length; i++) - new_union->boxed_struct.parent.g_strv[i] = g_strdup (union_->boxed_struct.parent.g_strv[i]); - new_union->boxed_struct.parent.g_strv[i] = NULL; - } - break; + g_hash_table_unref (hash_table); +} - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: - new_union->boxed_struct_ptr.parent = g_boxed_copy ( - gi_marshalling_tests_boxed_struct_get_type (), union_->boxed_struct_ptr.parent); - break; +/** + * gi_marshalling_tests_ghashtable_double_in: + * @hash_table: (element-type utf8 double) (transfer none): + * + * Meant to test a value type that doesn't fit inside a pointer. + */ +void +gi_marshalling_tests_ghashtable_double_in (GHashTable *hash_table) +{ + double *value; - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: - break; + value = g_hash_table_lookup (hash_table, "-1"); + g_assert_cmpfloat_with_epsilon (*value, -0.1, 0.01); + value = g_hash_table_lookup (hash_table, "0"); + g_assert_cmpfloat (*value, ==, 0.0); + value = g_hash_table_lookup (hash_table, "1"); + g_assert_cmpfloat_with_epsilon (*value, 0.1, 0.01); + value = g_hash_table_lookup (hash_table, "2"); + g_assert_cmpfloat_with_epsilon (*value, 0.2, 0.01); +} - default: - g_return_val_if_reached (new_union); - } +/** + * gi_marshalling_tests_ghashtable_float_in: + * @hash_table: (element-type utf8 float) (transfer none): + * + * Meant to test a value type that doesn't fit inside a pointer. + */ +void +gi_marshalling_tests_ghashtable_float_in (GHashTable *hash_table) +{ + float *value; - return new_union; + value = g_hash_table_lookup (hash_table, "-1"); + g_assert_cmpfloat_with_epsilon (*value, -0.1f, 0.01f); + value = g_hash_table_lookup (hash_table, "0"); + g_assert_cmpfloat (*value, ==, 0.0f); + value = g_hash_table_lookup (hash_table, "1"); + g_assert_cmpfloat_with_epsilon (*value, 0.1f, 0.01f); + value = g_hash_table_lookup (hash_table, "2"); + g_assert_cmpfloat_with_epsilon (*value, 0.2f, 0.01f); } -static void -gi_marshalling_tests_structured_union_free (GIMarshallingTestsStructuredUnion *union_) +/** + * gi_marshalling_tests_ghashtable_int64_in: + * @hash_table: (element-type utf8 gint64) (transfer none): + * + * Meant to test a value type that doesn't fit inside a pointer. + */ +void +gi_marshalling_tests_ghashtable_int64_in (GHashTable *hash_table) { - switch (union_->type) - { - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: - g_free (union_->boxed_struct.parent.string_); - g_strfreev (union_->boxed_struct.parent.g_strv); - break; - - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: - g_boxed_free (gi_marshalling_tests_boxed_struct_get_type (), union_->boxed_struct_ptr.parent); - break; + gint64 *value; - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: - case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: - break; + value = g_hash_table_lookup (hash_table, "-1"); + g_assert_cmpint (*value, ==, -1); + value = g_hash_table_lookup (hash_table, "0"); + g_assert_cmpint (*value, ==, 0); + value = g_hash_table_lookup (hash_table, "1"); + g_assert_cmpint (*value, ==, 1); + value = g_hash_table_lookup (hash_table, "2"); + g_assert_cmpint (*value, ==, (gint64) G_MAXUINT32 + 1); +} - default: - g_assert_not_reached (); - } +/** + * gi_marshalling_tests_ghashtable_uint64_in: + * @hash_table: (element-type utf8 guint64) (transfer none): + * + * Meant to test a value type that doesn't fit inside a pointer. + */ +void +gi_marshalling_tests_ghashtable_uint64_in (GHashTable *hash_table) +{ + guint64 *value; - g_free (union_); + value = g_hash_table_lookup (hash_table, "-1"); + g_assert_cmpuint (*value, ==, (guint64) G_MAXUINT32 + 1); + value = g_hash_table_lookup (hash_table, "0"); + g_assert_cmpuint (*value, ==, 0); + value = g_hash_table_lookup (hash_table, "1"); + g_assert_cmpuint (*value, ==, 1); + value = g_hash_table_lookup (hash_table, "2"); + g_assert_cmpuint (*value, ==, 2); } -GType -gi_marshalling_tests_structured_union_get_type (void) +/** + * gi_marshalling_tests_ghashtable_utf8_none_out: + * @hash_table: (out) (element-type utf8 utf8) (transfer none): + */ +void +gi_marshalling_tests_ghashtable_utf8_none_out (GHashTable **hash_table) { - static GType type = 0; + static GHashTable *new_hash_table = NULL; - if (type == 0) + if (new_hash_table == NULL) { - type = g_boxed_type_register_static ("GIMarshallingTestsStructuredUnion", - (GBoxedCopyFunc) - gi_marshalling_tests_structured_union_copy, - (GBoxedFreeFunc) gi_marshalling_tests_structured_union_free); + new_hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (new_hash_table, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (new_hash_table, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (new_hash_table, (gpointer) "1", (gpointer) "-1"); + g_hash_table_insert (new_hash_table, (gpointer) "2", (gpointer) "-2"); } - return type; + *hash_table = new_hash_table; } -GIMarshallingTestsStructuredUnionType -gi_marshalling_tests_structured_union_type (GIMarshallingTestsStructuredUnion *structured_union) +/** + * gi_marshalling_tests_ghashtable_utf8_none_out_uninitialized: + * @v: (out) (element-type utf8 utf8) (transfer none): + */ +gboolean +gi_marshalling_tests_ghashtable_utf8_none_out_uninitialized (GHashTable **v G_GNUC_UNUSED) { - return structured_union->type; + return FALSE; } -enum -{ - PROP_0, - PROP_INT_ -}; - -static void -gi_marshalling_tests_object_real_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in); - -G_DEFINE_TYPE (GIMarshallingTestsObject, gi_marshalling_tests_object, G_TYPE_OBJECT); - -static void -gi_marshalling_tests_object_init (GIMarshallingTestsObject *self G_GNUC_UNUSED) +/** + * gi_marshalling_tests_ghashtable_utf8_container_out: + * @hash_table: (out) (element-type utf8 utf8) (transfer container): + */ +void +gi_marshalling_tests_ghashtable_utf8_container_out (GHashTable **hash_table) { + *hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (*hash_table, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (*hash_table, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (*hash_table, (gpointer) "1", (gpointer) "-1"); + g_hash_table_insert (*hash_table, (gpointer) "2", (gpointer) "-2"); } -static void -gi_marshalling_tests_object_finalize (GObject *object) -{ - G_OBJECT_CLASS (gi_marshalling_tests_object_parent_class)->finalize (object); -} - -static void -gi_marshalling_tests_object_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +/** + * gi_marshalling_tests_ghashtable_utf8_container_out_uninitialized: + * @v: (out) (element-type utf8 utf8) (transfer container): + */ +gboolean +gi_marshalling_tests_ghashtable_utf8_container_out_uninitialized (GHashTable **v G_GNUC_UNUSED) { - g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); - - switch (prop_id) - { - case PROP_INT_: - GI_MARSHALLING_TESTS_OBJECT (object)->int_ = g_value_get_int (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } + return FALSE; } -static void -gi_marshalling_tests_object_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +/** + * gi_marshalling_tests_ghashtable_utf8_full_out: + * @hash_table: (out) (element-type utf8 utf8) (transfer full): + */ +void +gi_marshalling_tests_ghashtable_utf8_full_out (GHashTable **hash_table) { - g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); - - switch (prop_id) - { - case PROP_INT_: - g_value_set_int (value, GI_MARSHALLING_TESTS_OBJECT (object)->int_); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } + *hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (*hash_table, g_strdup ("-1"), g_strdup ("1")); + g_hash_table_insert (*hash_table, g_strdup ("0"), g_strdup ("0")); + g_hash_table_insert (*hash_table, g_strdup ("1"), g_strdup ("-1")); + g_hash_table_insert (*hash_table, g_strdup ("2"), g_strdup ("-2")); } -static void -gi_marshalling_tests_object_class_init (GIMarshallingTestsObjectClass *klass) +/** + * gi_marshalling_tests_ghashtable_utf8_full_out_uninitialized: + * @v: (out) (element-type utf8 utf8) (transfer full): + */ +gboolean +gi_marshalling_tests_ghashtable_utf8_full_out_uninitialized (GHashTable **v G_GNUC_UNUSED) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); -#if 0 - GObjectClass *parent_class = G_OBJECT_CLASS (klass); -#endif - - object_class->finalize = gi_marshalling_tests_object_finalize; - object_class->set_property = gi_marshalling_tests_object_set_property; - object_class->get_property = gi_marshalling_tests_object_get_property; - - g_object_class_install_property (object_class, PROP_INT_, - g_param_spec_int ("int", "Integer", - "An integer", G_MININT, - G_MAXINT, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - klass->method_with_default_implementation = gi_marshalling_tests_object_real_method_with_default_implementation; + return FALSE; } +/** + * gi_marshalling_tests_ghashtable_utf8_none_inout: + * @hash_table: (inout) (element-type utf8 utf8) (transfer none): + */ void -gi_marshalling_tests_object_static_method (void) +gi_marshalling_tests_ghashtable_utf8_none_inout (GHashTable **hash_table) { -} + static GHashTable *new_hash_table = NULL; -void -gi_marshalling_tests_object_method (GIMarshallingTestsObject *object) -{ - g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); - g_assert_cmpint (object->int_, ==, 42); -} + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); -void -gi_marshalling_tests_object_overridden_method (GIMarshallingTestsObject *object) -{ - g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); - g_assert_cmpint (object->int_, ==, 0); -} + if (new_hash_table == NULL) + { + new_hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (new_hash_table, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (new_hash_table, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (new_hash_table, (gpointer) "1", (gpointer) "1"); + } -GIMarshallingTestsObject * -gi_marshalling_tests_object_new (gint int_) -{ - return g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, "int", int_, NULL); + *hash_table = new_hash_table; } -GIMarshallingTestsObject * -gi_marshalling_tests_object_new_fail (gint int_ G_GNUC_UNUSED, - GError **error) +/** + * gi_marshalling_tests_ghashtable_utf8_container_inout: + * @hash_table: (inout) (element-type utf8 utf8) (transfer container): + */ +void +gi_marshalling_tests_ghashtable_utf8_container_inout (GHashTable **hash_table) { - g_return_val_if_fail (error == NULL || *error == NULL, NULL); + GHashTable *result = g_hash_table_new (g_str_hash, g_str_equal); - g_set_error_literal (error, - g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN), - GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, - GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); - return NULL; + g_hash_table_insert (result, (gpointer) "-1", (gpointer) "1"); + g_hash_table_insert (result, (gpointer) "0", (gpointer) "0"); + g_hash_table_insert (result, (gpointer) "1", (gpointer) "1"); + + g_hash_table_unref (*hash_table); + *hash_table = result; } /** - * gi_marshalling_tests_object_method_array_in: - * @ints: (array length=length): + * gi_marshalling_tests_ghashtable_utf8_full_inout: + * @hash_table: (inout) (element-type utf8 utf8) (transfer full): */ void -gi_marshalling_tests_object_method_array_in (GIMarshallingTestsObject *self G_GNUC_UNUSED, - const gint *ints, - gint length) +gi_marshalling_tests_ghashtable_utf8_full_inout (GHashTable **hash_table) { - g_assert_cmpint (length, ==, 4); - g_assert_cmpint (ints[0], ==, -1); - g_assert_cmpint (ints[1], ==, 0); - g_assert_cmpint (ints[2], ==, 1); - g_assert_cmpint (ints[3], ==, 2); + GHashTable *result = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "-1"), ==, "1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "0"), ==, "0"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "1"), ==, "-1"); + g_assert_cmpstr (g_hash_table_lookup (*hash_table, "2"), ==, "-2"); + + g_hash_table_insert (result, g_strdup ("-1"), g_strdup ("1")); + g_hash_table_insert (result, g_strdup ("0"), g_strdup ("0")); + g_hash_table_insert (result, g_strdup ("1"), g_strdup ("1")); + + g_hash_table_unref (*hash_table); + *hash_table = result; } /** - * gi_marshalling_tests_object_method_array_out: - * @ints: (out) (array length=length) (transfer none): + * gi_marshalling_tests_gvalue_return: + * + * Returns: (transfer none): */ -void -gi_marshalling_tests_object_method_array_out (GIMarshallingTestsObject *self G_GNUC_UNUSED, - gint **ints, - gint *length) +GValue * +gi_marshalling_tests_gvalue_return (void) { - static gint values[] = { -1, 0, 1, 2 }; + static GValue *value = NULL; - *length = 4; - *ints = values; + if (value == NULL) + { + value = g_new0 (GValue, 1); + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, 42); + } + + return value; } /** - * gi_marshalling_tests_object_method_array_inout: - * @ints: (inout) (array length=length) (transfer none): - * @length: (inout): + * gi_marshalling_tests_gvalue_noncanonical_nan_float: + * + * Returns: (transfer none): */ -void -gi_marshalling_tests_object_method_array_inout (GIMarshallingTestsObject *self G_GNUC_UNUSED, - gint **ints, - gint *length) +GValue * +gi_marshalling_tests_gvalue_noncanonical_nan_float (void) { - static gint values[] = { -2, -1, 0, 1, 2 }; + static GValue *value = NULL; - g_assert_cmpint (*length, ==, 4); - g_assert_cmpint ((*ints)[0], ==, -1); - g_assert_cmpint ((*ints)[1], ==, 0); - g_assert_cmpint ((*ints)[2], ==, 1); - g_assert_cmpint ((*ints)[3], ==, 2); + if (value == NULL) + { + value = g_new0 (GValue, 1); + g_value_init (value, G_TYPE_FLOAT); + g_value_set_float (value, noncanonical_nan_float ()); + } - *length = 5; - *ints = values; + return value; } /** - * gi_marshalling_tests_object_method_array_return: + * gi_marshalling_tests_gvalue_noncanonical_nan_double: * - * Returns: (array length=length): + * Returns: (transfer none): */ -const gint * -gi_marshalling_tests_object_method_array_return (GIMarshallingTestsObject *self G_GNUC_UNUSED, - gint *length) +GValue * +gi_marshalling_tests_gvalue_noncanonical_nan_double (void) { - static gint ints[] = { -1, 0, 1, 2 }; + static GValue *value = NULL; - *length = 4; - return ints; + if (value == NULL) + { + value = g_new0 (GValue, 1); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, noncanonical_nan_double ()); + } + + return value; } /** - * gi_marshalling_tests_object_method_int8_in: - * @in: (in): + * gi_marshalling_tests_gvalue_in: + * @value: (transfer none): */ void -gi_marshalling_tests_object_method_int8_in (GIMarshallingTestsObject *self, gint8 in) +gi_marshalling_tests_gvalue_in (GValue *value) { - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_in (self, in); + g_assert_cmpint (g_value_get_int (value), ==, 42); } /** - * gi_marshalling_tests_object_method_int8_out: - * @out: (out): + * gi_marshalling_tests_gvalue_int64_in: + * @value: (transfer none): */ void -gi_marshalling_tests_object_method_int8_out (GIMarshallingTestsObject *self, gint8 *out) +gi_marshalling_tests_gvalue_int64_in (GValue *value) { - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_out (self, out); + g_assert_cmpint (g_value_get_int64 (value), ==, G_MAXINT64); } /** - * gi_marshalling_tests_object_method_int8_arg_and_out_caller: - * @out: (out): + * gi_marshalling_tests_gvalue_in_with_type: + * @value: (transfer none): + * @type: */ void -gi_marshalling_tests_object_method_int8_arg_and_out_caller (GIMarshallingTestsObject *self, gint8 arg, gint8 *out) +gi_marshalling_tests_gvalue_in_with_type (GValue *value, GType type) { - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_arg_and_out_caller (self, arg, out); + g_assert (g_type_is_a (G_VALUE_TYPE (value), type)); } /** - * gi_marshalling_tests_object_method_int8_arg_and_out_callee: - * @out: (out): + * gi_marshalling_tests_gvalue_in_with_modification: + * @value: (transfer none): + * + * Expects a GValue passed by reference which is then modified by + * this function. */ void -gi_marshalling_tests_object_method_int8_arg_and_out_callee (GIMarshallingTestsObject *self, gint8 arg, gint8 **out) +gi_marshalling_tests_gvalue_in_with_modification (GValue *value) { - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_arg_and_out_callee (self, arg, out); + g_assert_cmpint (g_value_get_int (value), ==, 42); + g_value_set_int (value, 24); } /** - * gi_marshalling_tests_object_method_str_arg_out_ret: - * @out: (out): - * - * Returns: (transfer none) + * gi_marshalling_tests_gvalue_in_enum: + * @value: (transfer none): */ -const gchar * -gi_marshalling_tests_object_method_str_arg_out_ret (GIMarshallingTestsObject *self, const gchar *arg, guint *out) +void +gi_marshalling_tests_gvalue_in_enum (GValue *value) { - return GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_str_arg_out_ret (self, arg, out); + if (!G_VALUE_HOLDS_ENUM (value)) + g_critical ("Expected enum, got %s", G_VALUE_TYPE_NAME (value)); + g_assert (g_value_get_enum (value) == GI_MARSHALLING_TESTS_ENUM_VALUE3); } /** - * gi_marshalling_tests_object_method_with_default_implementation: - * @in: (in): + * gi_marshalling_tests_gvalue_in_flags: + * @value: (transfer none): */ void -gi_marshalling_tests_object_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in) -{ - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_with_default_implementation (self, in); -} - -static void -gi_marshalling_tests_object_real_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in) +gi_marshalling_tests_gvalue_in_flags (GValue *value) { - GValue val = { - 0, - }; - g_value_init (&val, G_TYPE_INT); - g_value_set_int (&val, in); - g_object_set_property (G_OBJECT (self), "int", &val); + if (!G_VALUE_HOLDS_FLAGS (value)) + g_critical ("Expected flags, got %s", G_VALUE_TYPE_NAME (value)); + g_assert_cmpint (g_value_get_flags (value), ==, GI_MARSHALLING_TESTS_FLAGS_VALUE3); } /** - * gi_marshalling_tests_object_vfunc_with_callback: (virtual vfunc_with_callback) - * @callback: (scope call) (closure callback_data): - * @callback_data: (allow-none): + * gi_marshalling_tests_gvalue_out: + * @value: (out) (transfer none): */ void -gi_marshalling_tests_object_vfunc_with_callback (GIMarshallingTestsObject *self G_GNUC_UNUSED, - GIMarshallingTestsCallbackIntInt callback G_GNUC_UNUSED, - void *callback_data G_GNUC_UNUSED) +gi_marshalling_tests_gvalue_out (GValue **value) { -} + static GValue *new_value = NULL; -static int -_callback (int val, void *user_data) -{ - g_assert (user_data == (gpointer) 0xdeadbeef); - return val; + if (new_value == NULL) + { + new_value = g_new0 (GValue, 1); + g_value_init (new_value, G_TYPE_INT); + g_value_set_int (new_value, 42); + } + + *value = new_value; } -void -gi_marshalling_tests_object_call_vfunc_with_callback (GIMarshallingTestsObject *object) +/** + * gi_marshalling_tests_gvalue_out_uninitialized: + * @v: (out) (transfer none): + */ +gboolean +gi_marshalling_tests_gvalue_out_uninitialized (GValue **v G_GNUC_UNUSED) { - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (object)->vfunc_with_callback (object, _callback, (void *) 0xdeadbeef); + return FALSE; } /** - * gi_marshalling_tests_object_none_return: - * - * Returns: (transfer none): + * gi_marshalling_tests_gvalue_int64_out: + * @value: (out) (transfer none): */ -GIMarshallingTestsObject * -gi_marshalling_tests_object_none_return (void) +void +gi_marshalling_tests_gvalue_int64_out (GValue **value) { - static GIMarshallingTestsObject *object = NULL; + static GValue *new_value = NULL; - if (object == NULL) + if (new_value == NULL) { - object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + new_value = g_new0 (GValue, 1); + g_value_init (new_value, G_TYPE_INT64); + g_value_set_int64 (new_value, G_MAXINT64); } - return object; + *value = new_value; } /** - * gi_marshalling_tests_object_full_return: - * - * Returns: (transfer full): + * gi_marshalling_tests_gvalue_out_caller_allocates: + * @value: (out) (transfer none): */ -GIMarshallingTestsObject * -gi_marshalling_tests_object_full_return (void) +void +gi_marshalling_tests_gvalue_out_caller_allocates (GValue *value) { - return g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, 42); } /** - * gi_marshalling_tests_object_none_in: - * @object: (transfer none): + * gi_marshalling_tests_gvalue_inout: + * @value: (inout) (transfer none): */ void -gi_marshalling_tests_object_none_in (GIMarshallingTestsObject *object) +gi_marshalling_tests_gvalue_inout (GValue **value) { - g_assert_cmpint (object->int_, ==, 42); + g_assert_cmpint (g_value_get_int (*value), ==, 42); + g_value_unset (*value); + g_value_init (*value, G_TYPE_STRING); + g_value_set_string (*value, "42"); } /** - * gi_marshalling_tests_object_none_out: - * @object: (out) (transfer none): + * gi_marshalling_tests_gvalue_flat_array: + * @n_values: number of values + * @values: (array length=n_values): an array containing values */ void -gi_marshalling_tests_object_none_out (GIMarshallingTestsObject **object) +gi_marshalling_tests_gvalue_flat_array (guint n_values, const GValue *values) { - static GIMarshallingTestsObject *new_object = NULL; - - if (new_object == NULL) - { - new_object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); - } + g_assert (n_values == 3); - *object = new_object; + g_assert_cmpint (g_value_get_int (&values[0]), ==, 42); + g_assert_cmpstr (g_value_get_string (&values[1]), ==, "42"); + g_assert_cmpint (g_value_get_boolean (&values[2]), ==, TRUE); } /** - * gi_marshalling_tests_object_none_out_uninitialized: - * @v: (out) (transfer none): + * gi_marshalling_tests_return_gvalue_flat_array: + * + * Returns: (array fixed-size=3) (transfer full): a flat GValue array */ -gboolean -gi_marshalling_tests_object_none_out_uninitialized (GIMarshallingTestsObject **v G_GNUC_UNUSED) +GValue * +gi_marshalling_tests_return_gvalue_flat_array (void) { - return FALSE; + GValue *array = g_new0 (GValue, 3); + + g_value_init (&array[0], G_TYPE_INT); + g_value_set_int (&array[0], 42); + + g_value_init (&array[1], G_TYPE_STRING); + g_value_set_static_string (&array[1], "42"); + + g_value_init (&array[2], G_TYPE_BOOLEAN); + g_value_set_boolean (&array[2], TRUE); + + return array; } /** - * gi_marshalling_tests_object_full_out: - * @object: (out) (transfer full): + * gi_marshalling_tests_return_gvalue_zero_terminated_array: + * + * Returns: (array zero-terminated) (transfer full): a flat GValue array */ -void -gi_marshalling_tests_object_full_out (GIMarshallingTestsObject **object) +GValue * +gi_marshalling_tests_return_gvalue_zero_terminated_array (void) { - *object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + GValue *array = g_new0 (GValue, 4); + + g_value_init (&array[0], G_TYPE_INT); + g_value_set_int (&array[0], 42); + + g_value_init (&array[1], G_TYPE_STRING); + g_value_set_static_string (&array[1], "42"); + + g_value_init (&array[2], G_TYPE_BOOLEAN); + g_value_set_boolean (&array[2], TRUE); + + return array; } /** - * gi_marshalling_tests_object_full_out_uninitialized: - * @v: (out) (transfer full): + * gi_marshalling_tests_gvalue_round_trip: + * @value: The first GValue + * + * Returns: (transfer none): */ -gboolean -gi_marshalling_tests_object_full_out_uninitialized (GIMarshallingTestsObject **v G_GNUC_UNUSED) +GValue * +gi_marshalling_tests_gvalue_round_trip (GValue *value) { - return FALSE; + return value; } /** - * gi_marshalling_tests_object_none_inout: - * @object: (inout) (transfer none): + * gi_marshalling_tests_gvalue_copy: + * @value: The first GValue + * + * Returns: (transfer full): */ -void -gi_marshalling_tests_object_none_inout (GIMarshallingTestsObject **object) +GValue * +gi_marshalling_tests_gvalue_copy (GValue *value) { - static GIMarshallingTestsObject *new_object = NULL; - - g_assert_cmpint ((*object)->int_, ==, 42); + GValue *return_value = g_new0 (GValue, 1); - if (new_object == NULL) - { - new_object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); - new_object->int_ = 0; - } + g_value_init (return_value, G_VALUE_TYPE (value)); + g_value_copy (value, return_value); - *object = new_object; + return return_value; } /** - * gi_marshalling_tests_object_full_inout: - * @object: (inout) (transfer full): + * gi_marshalling_tests_gvalue_flat_array_round_trip: + * @one: The first GValue + * @two: The second GValue + * @three: The third GValue + * + * Returns: (array fixed-size=3) (transfer full): a flat array of [@one, @two, @three] */ -void -gi_marshalling_tests_object_full_inout (GIMarshallingTestsObject **object) +GValue * +gi_marshalling_tests_gvalue_flat_array_round_trip (const GValue one, const GValue two, const GValue three) { - g_assert_cmpint ((*object)->int_, ==, 42); + GValue *array = g_new (GValue, 3); + array[0] = one; + array[1] = two; + array[2] = three; - g_object_unref (*object); - *object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + return array; } /** - * gi_marshalling_tests_object_int8_in: - * @in: (in): + * gi_marshalling_tests_gvalue_float: + * @float_value: A G_TYPE_FLOAT GValue + * @double_value: A G_TYPE_DOUBLE GValue */ void -gi_marshalling_tests_object_int8_in (GIMarshallingTestsObject *object, gint8 in) +gi_marshalling_tests_gvalue_float (const GValue *float_value, const GValue *double_value) { - gi_marshalling_tests_object_method_int8_in (object, in); + g_assert_cmpfloat_with_epsilon (g_value_get_float (float_value), 3.14, 0.001); + g_assert_cmpfloat_with_epsilon (g_value_get_double (double_value), 3.14, 0.001); } /** - * gi_marshalling_tests_object_int8_out: - * @out: (out): + * gi_marshalling_tests_gclosure_in: + * @closure: (transfer none): */ void -gi_marshalling_tests_object_int8_out (GIMarshallingTestsObject *object, gint8 *out) +gi_marshalling_tests_gclosure_in (GClosure *closure) { - gi_marshalling_tests_object_method_int8_out (object, out); -} - -/** - * gi_marshalling_tests_object_vfunc_return_value_only: - */ -glong -gi_marshalling_tests_object_vfunc_return_value_only (GIMarshallingTestsObject *self) -{ - /* make sure that local variables don't get smashed */ - glong return_value; - gulong local = 0x12345678; - return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_only (self); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + GValue return_value = { + 0, + }; + + g_value_init (&return_value, G_TYPE_INT); + + g_closure_invoke (closure, &return_value, 0, NULL, NULL); + + g_assert_cmpint (g_value_get_int (&return_value), ==, 42); + + g_value_unset (&return_value); } -/** - * gi_marshalling_tests_object_vfunc_one_out_parameter: - * @a: (out): - */ -void -gi_marshalling_tests_object_vfunc_one_out_parameter (GIMarshallingTestsObject *self, gfloat *a) +static gint +_closure_return_42 (void) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_one_out_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); + return 42; } -/** - * gi_marshalling_tests_object_vfunc_one_inout_parameter: - * @a: (inout): - */ -void -gi_marshalling_tests_object_vfunc_one_inout_parameter (GIMarshallingTestsObject *self, gfloat *a) +static void +_marshal_INT__VOID (GClosure *closure, + GValue *return_value, + guint n_param_values G_GNUC_UNUSED, + const GValue *param_values G_GNUC_UNUSED, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data G_GNUC_UNUSED) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_one_inout_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); + typedef gint (*GMarshalFunc_INT__VOID) (void); + register GMarshalFunc_INT__VOID callback; + register GCClosure *cc = (GCClosure *) closure; + + callback = (GMarshalFunc_INT__VOID) cc->callback; + g_value_set_int (return_value, callback ()); } /** - * gi_marshalling_tests_object_vfunc_multiple_inout_parameters: - * @a: (inout): - * @b: (inout): + * gi_marshalling_tests_gclosure_return: + * + * Return: a #GClosure */ -void -gi_marshalling_tests_object_vfunc_multiple_inout_parameters (GIMarshallingTestsObject *self, gfloat *a, gfloat *b) +GClosure * +gi_marshalling_tests_gclosure_return (void) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_multiple_inout_parameters (self, a, b); - g_assert_cmpint (local, ==, 0x12345678); + GClosure *closure = g_cclosure_new ((GCallback) _closure_return_42, + NULL, NULL); + g_closure_set_marshal (closure, _marshal_INT__VOID); + + return closure; } /** - * gi_marshalling_tests_object_vfunc_multiple_out_parameters: - * @a: (out): - * @b: (out): + * gi_marshalling_tests_callback_return_value_only: + * @callback: (scope call): */ -void -gi_marshalling_tests_object_vfunc_multiple_out_parameters (GIMarshallingTestsObject *self, gfloat *a, gfloat *b) +glong +gi_marshalling_tests_callback_return_value_only (GIMarshallingTestsCallbackReturnValueOnly callback) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_multiple_out_parameters (self, a, b); - g_assert_cmpint (local, ==, 0x12345678); + return callback (); } /** - * gi_marshalling_tests_object_vfunc_caller_allocated_out_parameter: + * gi_marshalling_tests_callback_one_out_parameter: + * @callback: (scope call): * @a: (out): */ void -gi_marshalling_tests_object_vfunc_caller_allocated_out_parameter (GIMarshallingTestsObject *self, GValue *a) +gi_marshalling_tests_callback_one_out_parameter (GIMarshallingTestsCallbackOneOutParameter callback, gfloat *a) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_caller_allocated_out_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); + callback (a); } /** - * gi_marshalling_tests_object_vfunc_array_out_parameter: - * @a: (out) (array zero-terminated): + * gi_marshalling_tests_callback_multiple_out_parameters: + * @callback: (scope call): + * @a: (out): + * @b: (out): */ void -gi_marshalling_tests_object_vfunc_array_out_parameter (GIMarshallingTestsObject *self, gfloat **a) +gi_marshalling_tests_callback_multiple_out_parameters (GIMarshallingTestsCallbackMultipleOutParameters callback, gfloat *a, gfloat *b) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_array_out_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); + callback (a, b); } /** - * gi_marshalling_tests_object_vfunc_return_value_and_one_out_parameter: + * gi_marshalling_tests_callback_return_value_and_one_out_parameter: + * @callback: (scope call): * @a: (out): */ glong -gi_marshalling_tests_object_vfunc_return_value_and_one_out_parameter (GIMarshallingTestsObject *self, glong *a) +gi_marshalling_tests_callback_return_value_and_one_out_parameter (GIMarshallingTestsCallbackReturnValueAndOneOutParameter callback, glong *a) { - /* make sure that local variables don't get smashed */ - gulong return_value; - gulong local = 0x12345678; - return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_one_out_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + return callback (a); } /** - * gi_marshalling_tests_object_vfunc_return_value_and_multiple_out_parameters: + * gi_marshalling_tests_callback_return_value_and_multiple_out_parameters: + * @callback: (scope call): * @a: (out): * @b: (out): */ glong -gi_marshalling_tests_object_vfunc_return_value_and_multiple_out_parameters (GIMarshallingTestsObject *self, glong *a, glong *b) -{ - gulong return_value; - gulong local = 0x12345678; - return_value = - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_multiple_out_parameters (self, a, b); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; -} - -/** - * gi_marshalling_tests_object_vfunc_return_value_and_one_inout_parameter: - * @a: (inout): - */ -glong -gi_marshalling_tests_object_vfunc_return_value_and_one_inout_parameter (GIMarshallingTestsObject *self, glong *a) +gi_marshalling_tests_callback_return_value_and_multiple_out_parameters (GIMarshallingTestsCallbackReturnValueAndMultipleOutParameters callback, glong *a, glong *b) { - /* make sure that local variables don't get smashed */ - gulong return_value; - gulong local = 0x12345678; - return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_one_inout_parameter (self, a); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + return callback (a, b); } /** - * gi_marshalling_tests_object_vfunc_return_value_and_multiple_inout_parameters: - * @a: (inout): - * @b: (inout): + * gi_marshalling_tests_pointer_in_return: + * + * Returns: The same pointer */ -glong -gi_marshalling_tests_object_vfunc_return_value_and_multiple_inout_parameters (GIMarshallingTestsObject *self, glong *a, glong *b) +gpointer +gi_marshalling_tests_pointer_in_return (gpointer pointer) { - gulong return_value; - gulong local = 0x12345678; - return_value = - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_multiple_inout_parameters (self, a, b); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + return pointer; } -/** - * gi_marshalling_tests_callback_owned_boxed: - * @callback: (scope call) (closure callback_data): - * @callback_data: (allow-none): - */ -glong -gi_marshalling_tests_callback_owned_boxed (GIMarshallingTestsCallbackOwnedBoxed callback, - void *callback_data) +GType +gi_marshalling_tests_genum_get_type (void) { - static GIMarshallingTestsBoxedStruct *box = NULL; - glong ret; + static GType type = 0; + if (G_UNLIKELY (type == 0)) + { + static const GEnumValue values[] = { + { GI_MARSHALLING_TESTS_GENUM_VALUE1, + "GI_MARSHALLING_TESTS_GENUM_VALUE1", "value1" }, + { GI_MARSHALLING_TESTS_GENUM_VALUE2, + "GI_MARSHALLING_TESTS_GENUM_VALUE2", "value2" }, + { GI_MARSHALLING_TESTS_GENUM_VALUE3, + "GI_MARSHALLING_TESTS_GENUM_VALUE3", "value3" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static (g_intern_static_string ("GIMarshallingTestsGEnum"), values); + } - if (!box) - box = gi_marshalling_tests_boxed_struct_new (); - box->long_++; - callback (box, callback_data); - ret = box->long_; - return ret; + return type; } -gboolean -gi_marshalling_tests_object_vfunc_meth_with_error (GIMarshallingTestsObject *self, gint x, GError **error) +GIMarshallingTestsGEnum +gi_marshalling_tests_genum_returnv (void) { - gulong local = 0x12345678; - gboolean ret = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_meth_with_err (self, - x, - error); - g_assert_cmpint (local, ==, 0x12345678); - return ret; + return GI_MARSHALLING_TESTS_GENUM_VALUE3; } -/** - * gi_marshalling_tests_object_vfunc_return_enum: - */ -GIMarshallingTestsEnum -gi_marshalling_tests_object_vfunc_return_enum (GIMarshallingTestsObject *self) +void +gi_marshalling_tests_genum_in (GIMarshallingTestsGEnum v) { - /* make sure that local variables don't get smashed */ - GIMarshallingTestsEnum return_value; - glong local = 0x12345678; - return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_enum (self); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + g_assert_cmpint (v, ==, GI_MARSHALLING_TESTS_GENUM_VALUE3); } /** - * gi_marshalling_tests_object_vfunc_out_enum: - * @_enum: (out): + * gi_marshalling_tests_genum_out: + * @v: (out): */ void -gi_marshalling_tests_object_vfunc_out_enum (GIMarshallingTestsObject *self, GIMarshallingTestsEnum *_enum) +gi_marshalling_tests_genum_out (GIMarshallingTestsGEnum *v) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_enum (self, _enum); - g_assert_cmpint (local, ==, 0x12345678); + *v = GI_MARSHALLING_TESTS_GENUM_VALUE3; } /** - * gi_marshalling_tests_object_vfunc_return_flags: + * gi_marshalling_tests_genum_out_uninitialized: + * @v: (out): */ -GIMarshallingTestsFlags -gi_marshalling_tests_object_vfunc_return_flags (GIMarshallingTestsObject *self) +gboolean +gi_marshalling_tests_genum_out_uninitialized (GIMarshallingTestsGEnum *v G_GNUC_UNUSED) { - /* make sure that local variables don't get smashed */ - GIMarshallingTestsFlags return_value; - glong local = 0x12345678; - return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_flags (self); - g_assert_cmpint (local, ==, 0x12345678); - return return_value; + return FALSE; } /** - * gi_marshalling_tests_object_vfunc_out_flags: - * @flags: (out): - */ + * gi_marshalling_tests_genum_inout: + * @v: (inout): + */ void -gi_marshalling_tests_object_vfunc_out_flags (GIMarshallingTestsObject *self, GIMarshallingTestsFlags *flags) +gi_marshalling_tests_genum_inout (GIMarshallingTestsGEnum *v) { - /* make sure that local variables don't get smashed */ - gulong local = 0x12345678; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_flags (self, flags); - g_assert_cmpuint (local, ==, 0x12345678); + g_assert_cmpint (*v, ==, GI_MARSHALLING_TESTS_GENUM_VALUE3); + *v = GI_MARSHALLING_TESTS_GENUM_VALUE1; } -/* NOTE: - * - * The following (get_ref_info_for_*) methods are designed to call vfuncs related - * to object argument marshaling. They do not pass the resulting objects through them - * as regular vfunc wrapper method do, but rather return reference count and floating - * information back to the callers. This is useful because callers can do testing of - * expected reference counts in isolation and from the perspective of C. This is important - * because if there are bugs in the reverse marshaling, they can obfuscate or compound - * bugs in marshaling from the vfuncs. - */ +GIMarshallingTestsEnum +gi_marshalling_tests_enum_returnv (void) +{ + return GI_MARSHALLING_TESTS_ENUM_VALUE3; +} -/** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_none: - * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. - * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. - */ void -gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_none (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +gi_marshalling_tests_enum_in (GIMarshallingTestsEnum v) { - GObject *object = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_object_transfer_none (self); - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - - /* Attempt to sink and unref the returned object and avoid any potential leaks */ - g_object_ref_sink (object); - g_object_unref (object); + g_assert_cmpint (v, ==, GI_MARSHALLING_TESTS_ENUM_VALUE3); } /** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_full: - * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. - * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + * gi_marshalling_tests_enum_out: + * @v: (out): */ void -gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_full (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +gi_marshalling_tests_enum_out (GIMarshallingTestsEnum *v) { - GObject *object = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_object_transfer_full (self); - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - g_object_unref (object); + *v = GI_MARSHALLING_TESTS_ENUM_VALUE3; } /** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_none: - * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. - * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + * gi_marshalling_tests_enum_out_uninitialized: + * @v: (out): */ -void -gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_none (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +gboolean +gi_marshalling_tests_enum_out_uninitialized (GIMarshallingTestsEnum **v G_GNUC_UNUSED) { - GObject *object = NULL; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_object_transfer_none (self, &object); - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - - /* Attempt to sink and unref the returned object and avoid any potential leaks */ - g_object_ref_sink (object); - g_object_unref (object); + return FALSE; } /** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_full: - * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. - * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + * gi_marshalling_tests_enum_inout: + * @v: (inout): */ void -gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_full (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +gi_marshalling_tests_enum_inout (GIMarshallingTestsEnum *v) { - GObject *object = NULL; - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_object_transfer_full (self, &object); - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - g_object_unref (object); + g_assert_cmpint (*v, ==, GI_MARSHALLING_TESTS_ENUM_VALUE3); + *v = GI_MARSHALLING_TESTS_ENUM_VALUE1; } -static void -_vfunc_in_object_destroy_callback (gboolean *destroy_called, - GObject *where_the_object_was G_GNUC_UNUSED) +GType +gi_marshalling_tests_flags_get_type (void) { - *destroy_called = TRUE; + static GType type = 0; + if (G_UNLIKELY (type == 0)) + { + static const GFlagsValue values[] = { + { GI_MARSHALLING_TESTS_FLAGS_VALUE1, + "GI_MARSHALLING_TESTS_FLAGS_VALUE1", "value1" }, + { GI_MARSHALLING_TESTS_FLAGS_VALUE2, + "GI_MARSHALLING_TESTS_FLAGS_VALUE2", "value2" }, + { GI_MARSHALLING_TESTS_FLAGS_VALUE3, + "GI_MARSHALLING_TESTS_FLAGS_VALUE3", "value3" }, + { GI_MARSHALLING_TESTS_FLAGS_MASK, "GI_MARSHALLING_TESTS_FLAGS_MASK", + "mask" }, + { GI_MARSHALLING_TESTS_FLAGS_MASK2, "GI_MARSHALLING_TESTS_FLAGS_MASK2", + "mask2" }, + { 0, NULL, NULL } + }; + type = g_flags_register_static (g_intern_static_string ("GIMarshallingTestsFlags"), values); + } + + return type; } -/** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_none: - * @type: GType of object to create and pass as in argument to the vfunc - * @ref_count: (out): Ref count of the in object directly after vfunc call. - * @is_floating: (out): Floating state of in object directly after vfunc call. - * - * Calls vfunc_in_object_transfer_none with a new object of the given type. - */ -void -gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_none (GIMarshallingTestsObject *self, GType type, guint *ref_count, gboolean *is_floating) +GIMarshallingTestsFlags +gi_marshalling_tests_flags_returnv (void) { - static gboolean destroy_called; - GObject *object; - destroy_called = FALSE; + return GI_MARSHALLING_TESTS_FLAGS_VALUE2; +} - object = g_object_new (type, NULL); - g_object_weak_ref (object, (GWeakNotify) _vfunc_in_object_destroy_callback, &destroy_called); +void +gi_marshalling_tests_flags_in (GIMarshallingTestsFlags v) +{ + g_assert (v == GI_MARSHALLING_TESTS_FLAGS_VALUE2); +} - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_in_object_transfer_none (self, object); - if (destroy_called) - { - *ref_count = 0; - *is_floating = FALSE; - } - else - { - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - g_object_unref (object); - } +void +gi_marshalling_tests_flags_in_zero (GIMarshallingTestsFlags v) +{ + g_assert (v == 0); } /** - * gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_full: - * @type: GType of object to create and pass as in argument to the vfunc - * @ref_count: (out): Ref count of the in object directly after vfunc call. - * @is_floating: (out): Floating state of in object directly after vfunc call. + * gi_marshalling_tests_flags_out: + * @v: (out): */ void -gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_full (GIMarshallingTestsObject *self, GType type, guint *ref_count, gboolean *is_floating) +gi_marshalling_tests_flags_out (GIMarshallingTestsFlags *v) { - static gboolean destroy_called; - GObject *object; - destroy_called = FALSE; - - object = g_object_new (type, NULL); - g_object_weak_ref (object, (GWeakNotify) _vfunc_in_object_destroy_callback, &destroy_called); - - /* Calling the vfunc takes ownership of the object, so we use a weak_ref to determine - * if the object gets destroyed after the call and appropriately return 0 as the ref count. - */ - GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_in_object_transfer_full (self, object); - if (destroy_called) - { - *ref_count = 0; - *is_floating = FALSE; - } - else - { - *ref_count = object->ref_count; - *is_floating = g_object_is_floating (object); - } + *v = GI_MARSHALLING_TESTS_FLAGS_VALUE2; } -G_DEFINE_TYPE (GIMarshallingTestsSubObject, gi_marshalling_tests_sub_object, GI_MARSHALLING_TESTS_TYPE_OBJECT); - -static void -gi_marshalling_tests_sub_object_init (GIMarshallingTestsSubObject *self G_GNUC_UNUSED) +/** + * gi_marshalling_tests_flags_out_uninitialized: + * @v: (out): + */ +gboolean +gi_marshalling_tests_flags_out_uninitialized (GIMarshallingTestsFlags *v G_GNUC_UNUSED) { + return FALSE; } -static void -gi_marshalling_tests_sub_object_finalize (GObject *object) +/** + * gi_marshalling_tests_flags_inout: + * @v: (inout): + */ +void +gi_marshalling_tests_flags_inout (GIMarshallingTestsFlags *v) { - G_OBJECT_CLASS (gi_marshalling_tests_sub_object_parent_class)->finalize (object); + g_assert (*v == GI_MARSHALLING_TESTS_FLAGS_VALUE2); + *v = GI_MARSHALLING_TESTS_FLAGS_VALUE1; } -static void -method_deep_hierarchy (GIMarshallingTestsObject *self, gint8 in) +GIMarshallingTestsNoTypeFlags +gi_marshalling_tests_no_type_flags_returnv (void) { - GValue val = { - 0, - }; - g_value_init (&val, G_TYPE_INT); - g_value_set_int (&val, in); - g_object_set_property (G_OBJECT (self), "int", &val); + return GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2; } -static void -gi_marshalling_tests_sub_object_class_init (GIMarshallingTestsSubObjectClass *klass) +void +gi_marshalling_tests_no_type_flags_in (GIMarshallingTestsNoTypeFlags v) { - G_OBJECT_CLASS (klass)->finalize = gi_marshalling_tests_sub_object_finalize; - GI_MARSHALLING_TESTS_OBJECT_CLASS (klass)->method_deep_hierarchy = method_deep_hierarchy; + g_assert (v == GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2); } void -gi_marshalling_tests_sub_object_sub_method (GIMarshallingTestsSubObject *object) +gi_marshalling_tests_no_type_flags_in_zero (GIMarshallingTestsNoTypeFlags v) { - g_assert_cmpint (GI_MARSHALLING_TESTS_OBJECT (object)->int_, ==, 0); + g_assert (v == 0); } +/** + * gi_marshalling_tests_no_type_flags_out: + * @v: (out): + */ void -gi_marshalling_tests_sub_object_overwritten_method (GIMarshallingTestsSubObject *object) +gi_marshalling_tests_no_type_flags_out (GIMarshallingTestsNoTypeFlags *v) { - g_assert_cmpint (GI_MARSHALLING_TESTS_OBJECT (object)->int_, ==, 0); + *v = GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2; } -G_DEFINE_TYPE (GIMarshallingTestsSubSubObject, - gi_marshalling_tests_sub_sub_object, - GI_MARSHALLING_TESTS_TYPE_SUB_OBJECT); - -static void -gi_marshalling_tests_sub_sub_object_init (GIMarshallingTestsSubSubObject *self G_GNUC_UNUSED) +/** + * gi_marshalling_tests_no_type_flags_out_uninitialized: + * @v: (out): + */ +gboolean +gi_marshalling_tests_no_type_flags_out_uninitialized (GIMarshallingTestsNoTypeFlags **v G_GNUC_UNUSED) { + return FALSE; } -static void -gi_marshalling_tests_sub_sub_object_class_init (GIMarshallingTestsSubSubObjectClass *klass G_GNUC_UNUSED) +/** + * gi_marshalling_tests_no_type_flags_inout: + * @v: (inout): + */ +void +gi_marshalling_tests_no_type_flags_inout (GIMarshallingTestsNoTypeFlags *v) { + g_assert (*v == GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2); + *v = GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE1; } -/* Interfaces */ +/** + * gi_marshalling_tests_simple_struct_returnv: + * + * Returns: (transfer none): + */ +GIMarshallingTestsSimpleStruct * +gi_marshalling_tests_simple_struct_returnv (void) +{ + static GIMarshallingTestsSimpleStruct *struct_ = NULL; -static void -gi_marshalling_tests_interface_class_init (void *g_iface G_GNUC_UNUSED, - void *data G_GNUC_UNUSED) + if (struct_ == NULL) + { + struct_ = g_new (GIMarshallingTestsSimpleStruct, 1); + + struct_->long_ = 6; + struct_->int8 = 7; + } + + return struct_; +} + +/** + * gi_marshalling_tests_simple_struct_inv: + * @struct_: (transfer none): + */ +void +gi_marshalling_tests_simple_struct_inv (GIMarshallingTestsSimpleStruct *struct_) +{ + g_assert_cmpint (struct_->long_, ==, 6); + g_assert_cmpint (struct_->int8, ==, 7); +} + +void +gi_marshalling_tests_simple_struct_method (GIMarshallingTestsSimpleStruct *struct_) { + g_assert_cmpint (struct_->long_, ==, 6); + g_assert_cmpint (struct_->int8, ==, 7); } GType -gi_marshalling_tests_interface_get_type (void) +gi_marshalling_tests_pointer_struct_get_type (void) { static GType type = 0; + if (type == 0) { - /* Not adding prerequisite here for test purposes */ - type = g_type_register_static_simple (G_TYPE_INTERFACE, - "GIMarshallingTestsInterface", - sizeof (GIMarshallingTestsInterfaceIface), - (GClassInitFunc) gi_marshalling_tests_interface_class_init, 0, NULL, 0); + type = g_pointer_type_register_static ("GIMarshallingTestsPointerStruct"); } return type; } /** - * gi_marshalling_tests_interface_test_int8_in: - * @in: (in): + * gi_marshalling_tests_pointer_struct_returnv: + * + * Returns: (transfer none): */ -void -gi_marshalling_tests_interface_test_int8_in (GIMarshallingTestsInterface *self, gint8 in) +GIMarshallingTestsPointerStruct * +gi_marshalling_tests_pointer_struct_returnv (void) { - GI_MARSHALLING_TESTS_INTERFACE_GET_IFACE (self)->test_int8_in (self, in); + static GIMarshallingTestsPointerStruct *struct_ = NULL; + + if (struct_ == NULL) + { + struct_ = g_new (GIMarshallingTestsPointerStruct, 1); + + struct_->long_ = 42; + } + + return struct_; } /** - * gi_marshalling_tests_test_interface_test_int8_in: - * @in: (in): + * gi_marshalling_tests_pointer_struct_inv: + * @struct_: (transfer none): */ void -gi_marshalling_tests_test_interface_test_int8_in (GIMarshallingTestsInterface *test_iface, gint8 in) +gi_marshalling_tests_pointer_struct_inv (GIMarshallingTestsPointerStruct *struct_) { - gi_marshalling_tests_interface_test_int8_in (test_iface, in); + g_assert_cmpint (struct_->long_, ==, 42); } -static void test_interface_init (GIMarshallingTestsInterfaceIface *iface); - -G_DEFINE_TYPE_WITH_CODE (GIMarshallingTestsInterfaceImpl, gi_marshalling_tests_interface_impl, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GI_MARSHALLING_TESTS_TYPE_INTERFACE, test_interface_init)) - -static void -gi_marshalling_tests_interface_impl_test_int8_in (GIMarshallingTestsInterface *self G_GNUC_UNUSED, - gint8 in G_GNUC_UNUSED) +static GIMarshallingTestsBoxedStruct * +gi_marshalling_tests_boxed_struct_copy (GIMarshallingTestsBoxedStruct *struct_) { -} + GIMarshallingTestsBoxedStruct *new_struct; -static void -test_interface_init (GIMarshallingTestsInterfaceIface *iface) -{ - iface->test_int8_in = gi_marshalling_tests_interface_impl_test_int8_in; -} + if (struct_ == NULL) + return NULL; -static void -gi_marshalling_tests_interface_impl_init (GIMarshallingTestsInterfaceImpl *self G_GNUC_UNUSED) -{ -} + new_struct = g_slice_new (GIMarshallingTestsBoxedStruct); -static void -gi_marshalling_tests_interface_impl_class_init (GIMarshallingTestsInterfaceImplClass *klass G_GNUC_UNUSED) -{ -} + *new_struct = *struct_; + new_struct->string_ = g_strdup (struct_->string_); + new_struct->g_strv = g_strdupv (struct_->g_strv); -/** - * gi_marshalling_tests_interface_impl_get_as_interface: - * - * Returns: (transfer none): - */ -GIMarshallingTestsInterface * -gi_marshalling_tests_interface_impl_get_as_interface (GIMarshallingTestsInterfaceImpl *self) -{ - return (GIMarshallingTestsInterface *) self; + return new_struct; } static void -gi_marshalling_tests_interface2_class_init (void *g_iface G_GNUC_UNUSED, - void *data G_GNUC_UNUSED) +gi_marshalling_tests_boxed_struct_free (GIMarshallingTestsBoxedStruct *struct_) { + if (struct_ != NULL) + { + g_free (struct_->string_); + g_clear_pointer (&struct_->g_strv, g_strfreev); + g_slice_free (GIMarshallingTestsBoxedStruct, struct_); + } } GType -gi_marshalling_tests_interface2_get_type (void) +gi_marshalling_tests_boxed_struct_get_type (void) { static GType type = 0; + if (type == 0) { - type = g_type_register_static_simple (G_TYPE_INTERFACE, - "GIMarshallingTestsInterface2", - sizeof (GIMarshallingTestsInterface2Iface), - (GClassInitFunc) gi_marshalling_tests_interface2_class_init, 0, NULL, 0); + type = g_boxed_type_register_static ("GIMarshallingTestsBoxedStruct", + (GBoxedCopyFunc) + gi_marshalling_tests_boxed_struct_copy, + (GBoxedFreeFunc) gi_marshalling_tests_boxed_struct_free); } return type; } -static void -gi_marshalling_tests_interface3_class_init (void *g_iface G_GNUC_UNUSED, - void *data G_GNUC_UNUSED) -{ -} - -GType -gi_marshalling_tests_interface3_get_type (void) +static GType +gi_marshalling_tests_boxed_glist_get_type (void) { static GType type = 0; + if (type == 0) { - type = g_type_register_static_simple (G_TYPE_INTERFACE, - "GIMarshallingTestsInterface3", - sizeof (GIMarshallingTestsInterface3Iface), - (GClassInitFunc) gi_marshalling_tests_interface3_class_init, 0, NULL, 0); + type = g_boxed_type_register_static ("GIMarshallingTestsBoxedGList", + (GBoxedCopyFunc) g_list_copy, (GBoxedFreeFunc) g_list_free); } return type; } -/** - * gi_marshalling_tests_interface3_test_variant_array_in: - * @in: (array length=n_in): - * @n_in: - */ -void -gi_marshalling_tests_interface3_test_variant_array_in (GIMarshallingTestsInterface3 *self, GVariant **in, gsize n_in) +GIMarshallingTestsBoxedStruct * +gi_marshalling_tests_boxed_struct_new (void) { - GI_MARSHALLING_TESTS_INTERFACE3_GET_IFACE (self)->test_variant_array_in (self, in, n_in); + return g_slice_new0 (GIMarshallingTestsBoxedStruct); } /** - * gi_marshalling_tests_int_out_out: - * @int0: (out): - * @int1: (out): + * gi_marshalling_tests_boxed_struct_returnv: + * + * Returns: (transfer none): */ -void -gi_marshalling_tests_int_out_out (gint *int0, gint *int1) +GIMarshallingTestsBoxedStruct * +gi_marshalling_tests_boxed_struct_returnv (void) { - *int0 = 6; - *int1 = 7; + static GIMarshallingTestsBoxedStruct *struct_ = NULL; + + if (struct_ == NULL) + { + struct_ = g_new (GIMarshallingTestsBoxedStruct, 1); + + struct_->long_ = 42; + struct_->string_ = g_strdup ("hello"); + struct_->g_strv = g_new0 (gchar *, 4); + struct_->g_strv[0] = g_strdup ("0"); + struct_->g_strv[1] = g_strdup ("1"); + struct_->g_strv[2] = g_strdup ("2"); + struct_->g_strv[3] = NULL; + } + + return struct_; } /** - * gi_marshalling_tests_int_three_in_three_out: - * @a: (in): - * @b: (in): - * @c: (in): - * @out0: (out): - * @out1: (out): - * @out2: (out): + * gi_marshalling_tests_boxed_struct_inv: + * @struct_: (transfer none): */ void -gi_marshalling_tests_int_three_in_three_out (gint a, gint b, gint c, gint *out0, gint *out1, gint *out2) +gi_marshalling_tests_boxed_struct_inv (GIMarshallingTestsBoxedStruct *struct_) { - *out0 = a; - *out1 = b; - *out2 = c; + g_assert_cmpint (struct_->long_, ==, 42); } /** - * gi_marshalling_tests_int_return_out: - * @int_: (out): + * gi_marshalling_tests_boxed_struct_out: + * @struct_: (out) (transfer none): */ -gint -gi_marshalling_tests_int_return_out (gint *int_) +void +gi_marshalling_tests_boxed_struct_out (GIMarshallingTestsBoxedStruct **struct_) { - *int_ = 7; - return 6; + static GIMarshallingTestsBoxedStruct *new_struct = NULL; + + if (new_struct == NULL) + { + new_struct = g_new0 (GIMarshallingTestsBoxedStruct, 1); + + new_struct->long_ = 42; + } + + *struct_ = new_struct; } /** - * gi_marshalling_tests_int_two_in_utf8_two_in_with_allow_none: - * @a: (in): Must be 1 - * @b: (in): Must be 2 - * @c: (in) (allow-none): Must be "3" or NULL - * @d: (in) (allow-none): Must be "4" or NULL + * gi_marshalling_tests_boxed_struct_out_uninitialized: + * @v: (out) (transfer none): */ -void -gi_marshalling_tests_int_two_in_utf8_two_in_with_allow_none (gint a, gint b, const gchar *c, const gchar *d) +gboolean +gi_marshalling_tests_boxed_struct_out_uninitialized (GIMarshallingTestsBoxedStruct **v G_GNUC_UNUSED) { - g_assert_cmpint (a, ==, 1); - g_assert_cmpint (b, ==, 2); - if (c != NULL) - g_assert_cmpstr (c, ==, "3"); - if (d != NULL) - g_assert_cmpstr (d, ==, "4"); + return FALSE; } /** - * gi_marshalling_tests_int_one_in_utf8_two_in_one_allows_none: - * @a: (in): Must be 1 - * @b: (in) (allow-none): Must be "2" or NULL - * @c: (in): Must be "3" + * gi_marshalling_tests_boxed_struct_inout: + * @struct_: (inout) (transfer full): */ void -gi_marshalling_tests_int_one_in_utf8_two_in_one_allows_none (gint a, const gchar *b, const gchar *c) +gi_marshalling_tests_boxed_struct_inout (GIMarshallingTestsBoxedStruct **struct_) { - g_assert_cmpint (a, ==, 1); - if (b != NULL) - g_assert_cmpstr (b, ==, "2"); - g_assert_cmpstr (c, ==, "3"); + g_assert_cmpint ((*struct_)->long_, ==, 42); + + g_boxed_free (gi_marshalling_tests_boxed_struct_get_type (), *struct_); + (*struct_) = g_slice_new0 (GIMarshallingTestsBoxedStruct); + (*struct_)->long_ = 0; } -/** - * gi_marshalling_tests_array_in_utf8_two_in: - * @ints: (array length=length): - * @length: - * @a: (in) (allow-none): Must be "1" or NULL - * @b: (in) (allow-none): Must be "2" or NULL - */ -void -gi_marshalling_tests_array_in_utf8_two_in (const gint *ints, gint length, const gchar *a, const gchar *b) +static GIMarshallingTestsUnion * +gi_marshalling_tests_union_copy (GIMarshallingTestsUnion *union_) { - g_assert_cmpint (length, ==, 4); - g_assert_cmpint (ints[0], ==, -1); - g_assert_cmpint (ints[1], ==, 0); - g_assert_cmpint (ints[2], ==, 1); - g_assert_cmpint (ints[3], ==, 2); + GIMarshallingTestsUnion *new_union; - if (a != NULL) - g_assert_cmpstr (a, ==, "1"); - if (b != NULL) - g_assert_cmpstr (b, ==, "2"); -} + new_union = g_slice_new (GIMarshallingTestsUnion); -/** - * gi_marshalling_tests_array_in_utf8_two_in_out_of_order: - * @length: - * @a: (in) (allow-none): Must be "1" or NULL - * @ints: (array length=length): - * @b: (in) (allow-none): Must be "2" or NULL - */ -void -gi_marshalling_tests_array_in_utf8_two_in_out_of_order (gint length, const gchar *a, const gint *ints, const gchar *b) -{ - g_assert_cmpint (length, ==, 4); - g_assert_cmpint (ints[0], ==, -1); - g_assert_cmpint (ints[1], ==, 0); - g_assert_cmpint (ints[2], ==, 1); - g_assert_cmpint (ints[3], ==, 2); + *new_union = *union_; - if (a != NULL) - g_assert_cmpstr (a, ==, "1"); - if (b != NULL) - g_assert_cmpstr (b, ==, "2"); + return new_union; } -/* GError */ - -void -gi_marshalling_tests_gerror (GError **error) +static void +gi_marshalling_tests_union_free (GIMarshallingTestsUnion *union_) { - GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); - g_set_error_literal (error, quark, - GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + g_slice_free (GIMarshallingTestsUnion, union_); } -/** - * gi_marshalling_tests_gerror_array_in: - * @in_ints: (array zero-terminated): - */ -void -gi_marshalling_tests_gerror_array_in (gint *in_ints G_GNUC_UNUSED, - GError **error) +GType +gi_marshalling_tests_union_get_type (void) { - GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); - g_set_error_literal (error, quark, - GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + static GType type = 0; + + if (type == 0) + { + type = g_boxed_type_register_static ("GIMarshallingTestsUnion", + (GBoxedCopyFunc) + gi_marshalling_tests_union_copy, + (GBoxedFreeFunc) gi_marshalling_tests_union_free); + } + + return type; } /** - * gi_marshalling_tests_gerror_out: - * @error: (out) (allow-none) (transfer full): location for the GError. - * @debug: (out) (allow-none) (transfer full): location for the debug message + * gi_marshalling_tests_union_returnv: * - * Inspired by gst_message_parse_error. + * Returns: (transfer none): */ -void -gi_marshalling_tests_gerror_out (GError **error, gchar **debug) +GIMarshallingTestsUnion * +gi_marshalling_tests_union_returnv (void) { - GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); - g_set_error_literal (error, quark, - GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + static GIMarshallingTestsUnion *union_ = NULL; - if (debug != NULL) + if (union_ == NULL) { - *debug = g_strdup (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DEBUG_MESSAGE); + union_ = g_new (GIMarshallingTestsUnion, 1); + + union_->long_ = 42; } -} -/** - * gi_marshalling_tests_gerror_out_uninitialized: - * @v: (out) (allow-none) (transfer full): - * @v2: (out) (allow-none) (transfer full): - */ -gboolean -gi_marshalling_tests_gerror_out_uninitialized (GError **v G_GNUC_UNUSED, gchar **v2 G_GNUC_UNUSED) -{ - return FALSE; + return union_; } /** - * gi_marshalling_tests_gerror_out_transfer_none: - * @err: (out) (allow-none) (transfer none): location for the GError. - * @debug: (out) (allow-none) (transfer none): location for the debug message - * - * A variant of gi_marshalling_tests_gerror_out() which returns data the caller - * must not free. + * gi_marshalling_tests_union_inv: + * @union_: (transfer none): */ void -gi_marshalling_tests_gerror_out_transfer_none (GError **err, const gchar **debug) +gi_marshalling_tests_union_inv (GIMarshallingTestsUnion *union_) { - static GError error = { 0, - GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, - (gchar *) GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE }; - error.domain = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); - *err = &error; - *debug = GI_MARSHALLING_TESTS_CONSTANT_GERROR_DEBUG_MESSAGE; + g_assert_cmpint (union_->long_, ==, 42); } -/** - * gi_marshalling_tests_gerror_out_transfer_none_uninitialized: - * @v: (out) (allow-none) (transfer none): - * @v2: (out) (allow-none) (transfer none): - */ -gboolean -gi_marshalling_tests_gerror_out_transfer_none_uninitialized (GError **v G_GNUC_UNUSED, const gchar **v2 G_GNUC_UNUSED) +void +gi_marshalling_tests_union_method (GIMarshallingTestsUnion *union_) { - return FALSE; + g_assert_cmpint (union_->long_, ==, 42); } /** - * gi_marshalling_tests_gerror_return: - * - * Yet another variant of gi_marshalling_tests_gerror_out(). - * - * Returns: (transfer full): a GError + * gi_marshalling_tests_structured_union_new: + * @type: Type of #GIMarshallingTestsStructuredUnion to create */ -GError * -gi_marshalling_tests_gerror_return (void) +GIMarshallingTestsStructuredUnion * +gi_marshalling_tests_structured_union_new (GIMarshallingTestsStructuredUnionType type) { - GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + GIMarshallingTestsStructuredUnion *new_union; - return g_error_new_literal (quark, GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); -} + new_union = g_new0 (GIMarshallingTestsStructuredUnion, 1); + new_union->type = type; -static GIMarshallingTestsOverridesStruct * -gi_marshalling_tests_overrides_struct_copy (GIMarshallingTestsOverridesStruct *struct_) -{ - GIMarshallingTestsOverridesStruct *new_struct; + switch (type) + { + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: + break; - new_struct = g_slice_new (GIMarshallingTestsOverridesStruct); + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: + new_union->simple_struct.parent.long_ = 6; + new_union->simple_struct.parent.int8 = 7; + break; - *new_struct = *struct_; + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: + new_union->nested_struct.parent.simple_struct.long_ = 6; + new_union->nested_struct.parent.simple_struct.int8 = 7; + break; - return new_struct; -} + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: + new_union->boxed_struct.parent.long_ = 42; + new_union->boxed_struct.parent.string_ = g_strdup ("hello"); + new_union->boxed_struct.parent.g_strv = g_new0 (gchar *, 4); + new_union->boxed_struct.parent.g_strv[0] = g_strdup ("0"); + new_union->boxed_struct.parent.g_strv[1] = g_strdup ("1"); + new_union->boxed_struct.parent.g_strv[2] = g_strdup ("2"); + new_union->boxed_struct.parent.g_strv[3] = NULL; + break; -static void -gi_marshalling_tests_overrides_struct_free (GIMarshallingTestsOverridesStruct *struct_) -{ - g_slice_free (GIMarshallingTestsOverridesStruct, struct_); -} + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: + new_union->boxed_struct_ptr.parent = g_boxed_copy ( + gi_marshalling_tests_boxed_struct_get_type (), + gi_marshalling_tests_boxed_struct_returnv ()); + break; -GType -gi_marshalling_tests_overrides_struct_get_type (void) -{ - static GType type = 0; + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: + new_union->pointer_struct.parent.long_ = 42; + break; - if (type == 0) - { - type = - g_boxed_type_register_static ("GIMarshallingTestsOverridesStruct", - (GBoxedCopyFunc) - gi_marshalling_tests_overrides_struct_copy, - (GBoxedFreeFunc) gi_marshalling_tests_overrides_struct_free); - } + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: + new_union->single_union.parent.union_.long_ = 42; + break; - return type; -} + default: + g_free (new_union); + g_return_val_if_reached (NULL); + } -GIMarshallingTestsOverridesStruct * -gi_marshalling_tests_overrides_struct_new (void) -{ - return g_slice_new (GIMarshallingTestsOverridesStruct); + return new_union; } -glong -gi_marshalling_tests_overrides_struct_method (GIMarshallingTestsOverridesStruct *self G_GNUC_UNUSED) +static GIMarshallingTestsStructuredUnion * +gi_marshalling_tests_structured_union_copy (GIMarshallingTestsStructuredUnion *union_) { - return 42; -} + GIMarshallingTestsStructuredUnion *new_union; -/** - * gi_marshalling_tests_overrides_struct_returnv: - * - * Returns: (transfer full): - */ -GIMarshallingTestsOverridesStruct * -gi_marshalling_tests_overrides_struct_returnv (void) -{ - return gi_marshalling_tests_overrides_struct_new (); -} + new_union = g_new (GIMarshallingTestsStructuredUnion, 1); + *new_union = *union_; -G_DEFINE_TYPE (GIMarshallingTestsOverridesObject, gi_marshalling_tests_overrides_object, G_TYPE_OBJECT); + switch (union_->type) + { + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: + new_union->boxed_struct.parent.string_ = g_strdup (union_->boxed_struct.parent.string_); + if (union_->boxed_struct.parent.g_strv) + { + guint length = g_strv_length (union_->boxed_struct.parent.g_strv); + guint i; -static void -gi_marshalling_tests_overrides_object_init (GIMarshallingTestsOverridesObject *self G_GNUC_UNUSED) + new_union->boxed_struct.parent.g_strv = g_new0 (gchar *, length + 1); + for (i = 0; i < length; i++) + new_union->boxed_struct.parent.g_strv[i] = g_strdup (union_->boxed_struct.parent.g_strv[i]); + new_union->boxed_struct.parent.g_strv[i] = NULL; + } + break; + + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: + new_union->boxed_struct_ptr.parent = g_boxed_copy ( + gi_marshalling_tests_boxed_struct_get_type (), union_->boxed_struct_ptr.parent); + break; + + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: + break; + + default: + g_return_val_if_reached (new_union); + } + + return new_union; +} + +static void +gi_marshalling_tests_structured_union_free (GIMarshallingTestsStructuredUnion *union_) +{ + switch (union_->type) + { + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT: + g_free (union_->boxed_struct.parent.string_); + g_strfreev (union_->boxed_struct.parent.g_strv); + break; + + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_BOXED_STRUCT_PTR: + g_boxed_free (gi_marshalling_tests_boxed_struct_get_type (), union_->boxed_struct_ptr.parent); + break; + + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NONE: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SINGLE_UNION: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_POINTER_STRUCT: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_SIMPLE_STRUCT: + case GI_MARSHALLING_TESTS_STRUCTURED_UNION_TYPE_NESTED_STRUCT: + break; + + default: + g_assert_not_reached (); + } + + g_free (union_); +} + +GType +gi_marshalling_tests_structured_union_get_type (void) +{ + static GType type = 0; + + if (type == 0) + { + type = g_boxed_type_register_static ("GIMarshallingTestsStructuredUnion", + (GBoxedCopyFunc) + gi_marshalling_tests_structured_union_copy, + (GBoxedFreeFunc) gi_marshalling_tests_structured_union_free); + } + + return type; +} + +GIMarshallingTestsStructuredUnionType +gi_marshalling_tests_structured_union_type (GIMarshallingTestsStructuredUnion *structured_union) +{ + return structured_union->type; +} + +enum +{ + PROP_0, + PROP_INT_ +}; + +static void +gi_marshalling_tests_object_real_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in); + +G_DEFINE_TYPE (GIMarshallingTestsObject, gi_marshalling_tests_object, G_TYPE_OBJECT); + +static void +gi_marshalling_tests_object_init (GIMarshallingTestsObject *self G_GNUC_UNUSED) { } static void -gi_marshalling_tests_overrides_object_finalize (GObject *object) +gi_marshalling_tests_object_finalize (GObject *object) { - G_OBJECT_CLASS (gi_marshalling_tests_overrides_object_parent_class)->finalize (object); + G_OBJECT_CLASS (gi_marshalling_tests_object_parent_class)->finalize (object); } static void -gi_marshalling_tests_overrides_object_class_init (GIMarshallingTestsOverridesObjectClass *klass) +gi_marshalling_tests_object_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); + + switch (prop_id) + { + case PROP_INT_: + GI_MARSHALLING_TESTS_OBJECT (object)->int_ = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gi_marshalling_tests_object_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); + + switch (prop_id) + { + case PROP_INT_: + g_value_set_int (value, GI_MARSHALLING_TESTS_OBJECT (object)->int_); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gi_marshalling_tests_object_class_init (GIMarshallingTestsObjectClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); #if 0 GObjectClass *parent_class = G_OBJECT_CLASS (klass); #endif - object_class->finalize = gi_marshalling_tests_overrides_object_finalize; + object_class->finalize = gi_marshalling_tests_object_finalize; + object_class->set_property = gi_marshalling_tests_object_set_property; + object_class->get_property = gi_marshalling_tests_object_get_property; + + g_object_class_install_property (object_class, PROP_INT_, + g_param_spec_int ("int", "Integer", + "An integer", G_MININT, + G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); + + klass->method_with_default_implementation = gi_marshalling_tests_object_real_method_with_default_implementation; } -GIMarshallingTestsOverridesObject * -gi_marshalling_tests_overrides_object_new (void) +void +gi_marshalling_tests_object_static_method (void) { - return g_object_new (GI_MARSHALLING_TESTS_TYPE_OVERRIDES_OBJECT, NULL); } -glong -gi_marshalling_tests_overrides_object_method (GIMarshallingTestsOverridesObject *self G_GNUC_UNUSED) +void +gi_marshalling_tests_object_method (GIMarshallingTestsObject *object) { - return 42; + g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); + g_assert_cmpint (object->int_, ==, 42); } -/** - * gi_marshalling_tests_overrides_object_returnv: - * - * Returns: (transfer full): - */ -GIMarshallingTestsOverridesObject * -gi_marshalling_tests_overrides_object_returnv (void) +void +gi_marshalling_tests_object_overridden_method (GIMarshallingTestsObject *object) { - return g_object_new (GI_MARSHALLING_TESTS_TYPE_OVERRIDES_OBJECT, NULL); + g_return_if_fail (GI_MARSHALLING_TESTS_IS_OBJECT (object)); + g_assert_cmpint (object->int_, ==, 0); } -/** - * gi_marshalling_tests_filename_list_return: - * - * Returns: (transfer none) (element-type filename): List of filenames - */ -GSList * -gi_marshalling_tests_filename_list_return (void) +GIMarshallingTestsObject * +gi_marshalling_tests_object_new (gint int_) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, "int", int_, NULL); +} + +GIMarshallingTestsObject * +gi_marshalling_tests_object_new_fail (gint int_ G_GNUC_UNUSED, + GError **error) { + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + g_set_error_literal (error, + g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN), + GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, + GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + return NULL; } /** - * gi_marshalling_tests_param_spec_in_bool: + * gi_marshalling_tests_object_method_array_in: + * @ints: (array length=length): */ void -gi_marshalling_tests_param_spec_in_bool (const GParamSpec *param) +gi_marshalling_tests_object_method_array_in (GIMarshallingTestsObject *self G_GNUC_UNUSED, + const gint *ints, + gint length) { - g_assert (G_IS_PARAM_SPEC (param)); - g_assert_cmpint (G_PARAM_SPEC_VALUE_TYPE (param), ==, G_TYPE_BOOLEAN); - g_assert_cmpstr (g_param_spec_get_name ((GParamSpec *) param), ==, "mybool"); + g_assert_cmpint (length, ==, 4); + g_assert_cmpint (ints[0], ==, -1); + g_assert_cmpint (ints[1], ==, 0); + g_assert_cmpint (ints[2], ==, 1); + g_assert_cmpint (ints[3], ==, 2); } /** - * gi_marshalling_tests_param_spec_return: - * - * Returns: (transfer full): a #GParamSpec + * gi_marshalling_tests_object_method_array_out: + * @ints: (out) (array length=length) (transfer none): */ -GParamSpec * -gi_marshalling_tests_param_spec_return (void) +void +gi_marshalling_tests_object_method_array_out (GIMarshallingTestsObject *self G_GNUC_UNUSED, + gint **ints, + gint *length) { - return g_param_spec_string ("test-param", "test", "This is a test", "42", G_PARAM_READABLE); + static gint values[] = { -1, 0, 1, 2 }; + + *length = 4; + *ints = values; } /** - * gi_marshalling_tests_param_spec_out: - * @param: (out): + * gi_marshalling_tests_object_method_array_inout: + * @ints: (inout) (array length=length) (transfer none): + * @length: (inout): */ void -gi_marshalling_tests_param_spec_out (GParamSpec **param) +gi_marshalling_tests_object_method_array_inout (GIMarshallingTestsObject *self G_GNUC_UNUSED, + gint **ints, + gint *length) { - *param = g_param_spec_string ("test-param", "test", "This is a test", "42", G_PARAM_READABLE); + static gint values[] = { -2, -1, 0, 1, 2 }; + + g_assert_cmpint (*length, ==, 4); + g_assert_cmpint ((*ints)[0], ==, -1); + g_assert_cmpint ((*ints)[1], ==, 0); + g_assert_cmpint ((*ints)[2], ==, 1); + g_assert_cmpint ((*ints)[3], ==, 2); + + *length = 5; + *ints = values; } /** - * gi_marshalling_tests_param_spec_out_uninitialized: - * @v: (out): + * gi_marshalling_tests_object_method_array_return: + * + * Returns: (array length=length): */ -gboolean -gi_marshalling_tests_param_spec_out_uninitialized (GParamSpec **v G_GNUC_UNUSED) +const gint * +gi_marshalling_tests_object_method_array_return (GIMarshallingTestsObject *self G_GNUC_UNUSED, + gint *length) { - return FALSE; + static gint ints[] = { -1, 0, 1, 2 }; + + *length = 4; + return ints; } -enum -{ - DUMMY_PROPERTY, - SOME_BOOLEAN_PROPERTY, - SOME_CHAR_PROPERTY, - SOME_UCHAR_PROPERTY, - SOME_INT_PROPERTY, - SOME_UINT_PROPERTY, - SOME_LONG_PROPERTY, - SOME_ULONG_PROPERTY, - SOME_INT64_PROPERTY, - SOME_UINT64_PROPERTY, - SOME_FLOAT_PROPERTY, - SOME_STRING_PROPERTY, - SOME_DOUBLE_PROPERTY, - SOME_STRV_PROPERTY, - SOME_BOXED_STRUCT_PROPERTY, - SOME_VARIANT_PROPERTY, - SOME_BOXED_GLIST_PROPERTY, - SOME_GVALUE_PROPERTY, - SOME_OBJECT_PROPERTY, - SOME_FLAGS_PROPERTY, - SOME_ENUM_PROPERTY, - SOME_BYTE_ARRAY_PROPERTY, - SOME_READONLY_PROPERTY, -}; +/** + * gi_marshalling_tests_object_method_int8_in: + * @in: (in): + */ +void +gi_marshalling_tests_object_method_int8_in (GIMarshallingTestsObject *self, gint8 in) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_in (self, in); +} -G_DEFINE_TYPE (GIMarshallingTestsPropertiesObject, gi_marshalling_tests_properties_object, G_TYPE_OBJECT); +/** + * gi_marshalling_tests_object_method_int8_out: + * @out: (out): + */ +void +gi_marshalling_tests_object_method_int8_out (GIMarshallingTestsObject *self, gint8 *out) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_out (self, out); +} + +/** + * gi_marshalling_tests_object_method_int8_arg_and_out_caller: + * @out: (out): + */ +void +gi_marshalling_tests_object_method_int8_arg_and_out_caller (GIMarshallingTestsObject *self, gint8 arg, gint8 *out) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_arg_and_out_caller (self, arg, out); +} + +/** + * gi_marshalling_tests_object_method_int8_arg_and_out_callee: + * @out: (out): + */ +void +gi_marshalling_tests_object_method_int8_arg_and_out_callee (GIMarshallingTestsObject *self, gint8 arg, gint8 **out) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_int8_arg_and_out_callee (self, arg, out); +} + +/** + * gi_marshalling_tests_object_method_str_arg_out_ret: + * @out: (out): + * + * Returns: (transfer none) + */ +const gchar * +gi_marshalling_tests_object_method_str_arg_out_ret (GIMarshallingTestsObject *self, const gchar *arg, guint *out) +{ + return GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_str_arg_out_ret (self, arg, out); +} + +/** + * gi_marshalling_tests_object_method_with_default_implementation: + * @in: (in): + */ +void +gi_marshalling_tests_object_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->method_with_default_implementation (self, in); +} static void -gi_marshalling_tests_properties_object_init (GIMarshallingTestsPropertiesObject *self G_GNUC_UNUSED) +gi_marshalling_tests_object_real_method_with_default_implementation (GIMarshallingTestsObject *self, gint8 in) +{ + GValue val = { + 0, + }; + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, in); + g_object_set_property (G_OBJECT (self), "int", &val); +} + +/** + * gi_marshalling_tests_object_vfunc_with_callback: (virtual vfunc_with_callback) + * @callback: (scope call) (closure callback_data): + * @callback_data: (allow-none): + */ +void +gi_marshalling_tests_object_vfunc_with_callback (GIMarshallingTestsObject *self G_GNUC_UNUSED, + GIMarshallingTestsCallbackIntInt callback G_GNUC_UNUSED, + void *callback_data G_GNUC_UNUSED) +{ +} + +static int +_callback (int val, void *user_data) +{ + g_assert (user_data == (gpointer) 0xdeadbeef); + return val; +} + +void +gi_marshalling_tests_object_call_vfunc_with_callback (GIMarshallingTestsObject *object) +{ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (object)->vfunc_with_callback (object, _callback, (void *) 0xdeadbeef); +} + +/** + * gi_marshalling_tests_object_none_return: + * + * Returns: (transfer none): + */ +GIMarshallingTestsObject * +gi_marshalling_tests_object_none_return (void) +{ + static GIMarshallingTestsObject *object = NULL; + + if (object == NULL) + { + object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + } + + return object; +} + +/** + * gi_marshalling_tests_object_full_return: + * + * Returns: (transfer full): + */ +GIMarshallingTestsObject * +gi_marshalling_tests_object_full_return (void) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); +} + +/** + * gi_marshalling_tests_object_none_in: + * @object: (transfer none): + */ +void +gi_marshalling_tests_object_none_in (GIMarshallingTestsObject *object) +{ + g_assert_cmpint (object->int_, ==, 42); +} + +/** + * gi_marshalling_tests_object_none_out: + * @object: (out) (transfer none): + */ +void +gi_marshalling_tests_object_none_out (GIMarshallingTestsObject **object) +{ + static GIMarshallingTestsObject *new_object = NULL; + + if (new_object == NULL) + { + new_object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + } + + *object = new_object; +} + +/** + * gi_marshalling_tests_object_none_out_uninitialized: + * @v: (out) (transfer none): + */ +gboolean +gi_marshalling_tests_object_none_out_uninitialized (GIMarshallingTestsObject **v G_GNUC_UNUSED) +{ + return FALSE; +} + +/** + * gi_marshalling_tests_object_full_out: + * @object: (out) (transfer full): + */ +void +gi_marshalling_tests_object_full_out (GIMarshallingTestsObject **object) +{ + *object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); +} + +/** + * gi_marshalling_tests_object_full_out_uninitialized: + * @v: (out) (transfer full): + */ +gboolean +gi_marshalling_tests_object_full_out_uninitialized (GIMarshallingTestsObject **v G_GNUC_UNUSED) +{ + return FALSE; +} + +/** + * gi_marshalling_tests_object_none_inout: + * @object: (inout) (transfer none): + */ +void +gi_marshalling_tests_object_none_inout (GIMarshallingTestsObject **object) +{ + static GIMarshallingTestsObject *new_object = NULL; + + g_assert_cmpint ((*object)->int_, ==, 42); + + if (new_object == NULL) + { + new_object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); + new_object->int_ = 0; + } + + *object = new_object; +} + +/** + * gi_marshalling_tests_object_full_inout: + * @object: (inout) (transfer full): + */ +void +gi_marshalling_tests_object_full_inout (GIMarshallingTestsObject **object) +{ + g_assert_cmpint ((*object)->int_, ==, 42); + + g_object_unref (*object); + *object = g_object_new (GI_MARSHALLING_TESTS_TYPE_OBJECT, NULL); +} + +/** + * gi_marshalling_tests_object_int8_in: + * @in: (in): + */ +void +gi_marshalling_tests_object_int8_in (GIMarshallingTestsObject *object, gint8 in) +{ + gi_marshalling_tests_object_method_int8_in (object, in); +} + +/** + * gi_marshalling_tests_object_int8_out: + * @out: (out): + */ +void +gi_marshalling_tests_object_int8_out (GIMarshallingTestsObject *object, gint8 *out) +{ + gi_marshalling_tests_object_method_int8_out (object, out); +} + +/** + * gi_marshalling_tests_object_vfunc_return_value_only: + */ +glong +gi_marshalling_tests_object_vfunc_return_value_only (GIMarshallingTestsObject *self) +{ + /* make sure that local variables don't get smashed */ + glong return_value; + gulong local = 0x12345678; + return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_only (self); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_one_out_parameter: + * @a: (out): + */ +void +gi_marshalling_tests_object_vfunc_one_out_parameter (GIMarshallingTestsObject *self, gfloat *a) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_one_out_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_one_inout_parameter: + * @a: (inout): + */ +void +gi_marshalling_tests_object_vfunc_one_inout_parameter (GIMarshallingTestsObject *self, gfloat *a) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_one_inout_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_multiple_inout_parameters: + * @a: (inout): + * @b: (inout): + */ +void +gi_marshalling_tests_object_vfunc_multiple_inout_parameters (GIMarshallingTestsObject *self, gfloat *a, gfloat *b) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_multiple_inout_parameters (self, a, b); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_multiple_out_parameters: + * @a: (out): + * @b: (out): + */ +void +gi_marshalling_tests_object_vfunc_multiple_out_parameters (GIMarshallingTestsObject *self, gfloat *a, gfloat *b) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_multiple_out_parameters (self, a, b); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_caller_allocated_out_parameter: + * @a: (out): + */ +void +gi_marshalling_tests_object_vfunc_caller_allocated_out_parameter (GIMarshallingTestsObject *self, GValue *a) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_caller_allocated_out_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_array_out_parameter: + * @a: (out) (array zero-terminated): + */ +void +gi_marshalling_tests_object_vfunc_array_out_parameter (GIMarshallingTestsObject *self, gfloat **a) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_array_out_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_return_value_and_one_out_parameter: + * @a: (out): + */ +glong +gi_marshalling_tests_object_vfunc_return_value_and_one_out_parameter (GIMarshallingTestsObject *self, glong *a) +{ + /* make sure that local variables don't get smashed */ + gulong return_value; + gulong local = 0x12345678; + return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_one_out_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_return_value_and_multiple_out_parameters: + * @a: (out): + * @b: (out): + */ +glong +gi_marshalling_tests_object_vfunc_return_value_and_multiple_out_parameters (GIMarshallingTestsObject *self, glong *a, glong *b) +{ + gulong return_value; + gulong local = 0x12345678; + return_value = + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_multiple_out_parameters (self, a, b); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_return_value_and_one_inout_parameter: + * @a: (inout): + */ +glong +gi_marshalling_tests_object_vfunc_return_value_and_one_inout_parameter (GIMarshallingTestsObject *self, glong *a) +{ + /* make sure that local variables don't get smashed */ + gulong return_value; + gulong local = 0x12345678; + return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_one_inout_parameter (self, a); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_return_value_and_multiple_inout_parameters: + * @a: (inout): + * @b: (inout): + */ +glong +gi_marshalling_tests_object_vfunc_return_value_and_multiple_inout_parameters (GIMarshallingTestsObject *self, glong *a, glong *b) +{ + gulong return_value; + gulong local = 0x12345678; + return_value = + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_value_and_multiple_inout_parameters (self, a, b); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_callback_owned_boxed: + * @callback: (scope call) (closure callback_data): + * @callback_data: (allow-none): + */ +glong +gi_marshalling_tests_callback_owned_boxed (GIMarshallingTestsCallbackOwnedBoxed callback, + void *callback_data) +{ + static GIMarshallingTestsBoxedStruct *box = NULL; + glong ret; + + if (!box) + box = gi_marshalling_tests_boxed_struct_new (); + box->long_++; + callback (box, callback_data); + ret = box->long_; + return ret; +} + +gboolean +gi_marshalling_tests_object_vfunc_meth_with_error (GIMarshallingTestsObject *self, gint x, GError **error) +{ + gulong local = 0x12345678; + gboolean ret = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_meth_with_err (self, + x, + error); + g_assert_cmpint (local, ==, 0x12345678); + return ret; +} + +/** + * gi_marshalling_tests_object_vfunc_return_enum: + */ +GIMarshallingTestsEnum +gi_marshalling_tests_object_vfunc_return_enum (GIMarshallingTestsObject *self) +{ + /* make sure that local variables don't get smashed */ + GIMarshallingTestsEnum return_value; + glong local = 0x12345678; + return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_enum (self); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_out_enum: + * @_enum: (out): + */ +void +gi_marshalling_tests_object_vfunc_out_enum (GIMarshallingTestsObject *self, GIMarshallingTestsEnum *_enum) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_enum (self, _enum); + g_assert_cmpint (local, ==, 0x12345678); +} + +/** + * gi_marshalling_tests_object_vfunc_return_flags: + */ +GIMarshallingTestsFlags +gi_marshalling_tests_object_vfunc_return_flags (GIMarshallingTestsObject *self) +{ + /* make sure that local variables don't get smashed */ + GIMarshallingTestsFlags return_value; + glong local = 0x12345678; + return_value = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_flags (self); + g_assert_cmpint (local, ==, 0x12345678); + return return_value; +} + +/** + * gi_marshalling_tests_object_vfunc_static_name: + */ +gchar * +gi_marshalling_tests_object_vfunc_static_name (void) +{ + GIMarshallingTestsObjectClass *klass; + klass = g_type_class_peek_static (GI_MARSHALLING_TESTS_TYPE_OBJECT); + if (klass->vfunc_static_name) + return klass->vfunc_static_name (); + + return g_strdup (g_type_name (GI_MARSHALLING_TESTS_TYPE_OBJECT)); +} + +/** + * gi_marshalling_tests_object_vfunc_static_typed_name: + * @gtype: + */ +gchar * +gi_marshalling_tests_object_vfunc_static_typed_name (GType gtype) +{ + GIMarshallingTestsObjectClass *klass; + + g_return_val_if_fail (g_type_is_a (gtype, GI_MARSHALLING_TESTS_TYPE_OBJECT), + NULL); + + klass = g_type_class_peek_static (gtype); + if (klass->vfunc_static_name) + return klass->vfunc_static_name (); + + return g_strdup (g_type_name (GI_MARSHALLING_TESTS_TYPE_OBJECT)); +} + +/** + * gi_marshalling_tests_object_vfunc_static_create_new: + * @gtype: + * @int_: + */ +GIMarshallingTestsObject * +gi_marshalling_tests_object_vfunc_static_create_new (GType gtype, gint int_) +{ + GIMarshallingTestsObjectClass *klass; + + g_return_val_if_fail (g_type_is_a (gtype, GI_MARSHALLING_TESTS_TYPE_OBJECT), + NULL); + + klass = g_type_class_peek (gtype); + if (klass->vfunc_static_create_new) + return klass->vfunc_static_create_new (int_); + + return gi_marshalling_tests_object_new (int_); +} + +/** + * gi_marshalling_tests_object_vfunc_static_create_new_out: + * @out: (out): We keep this as first parameter to ensure it's parsed properly + * @gtype: + * @int_: + */ +void +gi_marshalling_tests_object_vfunc_static_create_new_out (GIMarshallingTestsObject **out, + GType gtype, + gint int_) +{ + GIMarshallingTestsObjectClass *klass; + + g_return_if_fail (g_type_is_a (gtype, GI_MARSHALLING_TESTS_TYPE_OBJECT)); + + klass = g_type_class_peek (gtype); + if (klass->vfunc_static_create_new_out) + return klass->vfunc_static_create_new_out (out, int_); + + *out = gi_marshalling_tests_object_new (int_); +} + +/** + * gi_marshalling_tests_object_vfunc_out_flags: + * @flags: (out): + */ +void +gi_marshalling_tests_object_vfunc_out_flags (GIMarshallingTestsObject *self, GIMarshallingTestsFlags *flags) +{ + /* make sure that local variables don't get smashed */ + gulong local = 0x12345678; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_flags (self, flags); + g_assert_cmpuint (local, ==, 0x12345678); +} + +/* NOTE: + * + * The following (get_ref_info_for_*) methods are designed to call vfuncs related + * to object argument marshaling. They do not pass the resulting objects through them + * as regular vfunc wrapper method do, but rather return reference count and floating + * information back to the callers. This is useful because callers can do testing of + * expected reference counts in isolation and from the perspective of C. This is important + * because if there are bugs in the reverse marshaling, they can obfuscate or compound + * bugs in marshaling from the vfuncs. + */ + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_none: + * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. + * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_none (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +{ + GObject *object = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_object_transfer_none (self); + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + + /* Attempt to sink and unref the returned object and avoid any potential leaks */ + g_object_ref_sink (object); + g_object_unref (object); +} + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_full: + * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. + * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_full (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +{ + GObject *object = GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_return_object_transfer_full (self); + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + g_object_unref (object); +} + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_none: + * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. + * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_none (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +{ + GObject *object = NULL; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_object_transfer_none (self, &object); + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + + /* Attempt to sink and unref the returned object and avoid any potential leaks */ + g_object_ref_sink (object); + g_object_unref (object); +} + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_full: + * @ref_count: (out): Ref count of the object returned from the vfunc directly after vfunc call. + * @is_floating: (out): Floating state object returned from the vfunc directly after vfunc call. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_out_object_transfer_full (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating) +{ + GObject *object = NULL; + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_out_object_transfer_full (self, &object); + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + g_object_unref (object); +} + +static void +_vfunc_in_object_destroy_callback (gboolean *destroy_called, + GObject *where_the_object_was G_GNUC_UNUSED) +{ + *destroy_called = TRUE; +} + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_none: + * @type: GType of object to create and pass as in argument to the vfunc + * @ref_count: (out): Ref count of the in object directly after vfunc call. + * @is_floating: (out): Floating state of in object directly after vfunc call. + * + * Calls vfunc_in_object_transfer_none with a new object of the given type. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_none (GIMarshallingTestsObject *self, GType type, guint *ref_count, gboolean *is_floating) +{ + static gboolean destroy_called; + GObject *object; + destroy_called = FALSE; + + object = g_object_new (type, NULL); + g_object_weak_ref (object, (GWeakNotify) _vfunc_in_object_destroy_callback, &destroy_called); + + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_in_object_transfer_none (self, object); + if (destroy_called) + { + *ref_count = 0; + *is_floating = FALSE; + } + else + { + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + g_object_unref (object); + } +} + +/** + * gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_full: + * @type: GType of object to create and pass as in argument to the vfunc + * @ref_count: (out): Ref count of the in object directly after vfunc call. + * @is_floating: (out): Floating state of in object directly after vfunc call. + */ +void +gi_marshalling_tests_object_get_ref_info_for_vfunc_in_object_transfer_full (GIMarshallingTestsObject *self, GType type, guint *ref_count, gboolean *is_floating) +{ + static gboolean destroy_called; + GObject *object; + destroy_called = FALSE; + + object = g_object_new (type, NULL); + g_object_weak_ref (object, (GWeakNotify) _vfunc_in_object_destroy_callback, &destroy_called); + + /* Calling the vfunc takes ownership of the object, so we use a weak_ref to determine + * if the object gets destroyed after the call and appropriately return 0 as the ref count. + */ + GI_MARSHALLING_TESTS_OBJECT_GET_CLASS (self)->vfunc_in_object_transfer_full (self, object); + if (destroy_called) + { + *ref_count = 0; + *is_floating = FALSE; + } + else + { + *ref_count = object->ref_count; + *is_floating = g_object_is_floating (object); + } +} + +G_DEFINE_TYPE (GIMarshallingTestsSubObject, gi_marshalling_tests_sub_object, GI_MARSHALLING_TESTS_TYPE_OBJECT); + +static void +gi_marshalling_tests_sub_object_init (GIMarshallingTestsSubObject *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_sub_object_finalize (GObject *object) +{ + G_OBJECT_CLASS (gi_marshalling_tests_sub_object_parent_class)->finalize (object); +} + +static void +method_deep_hierarchy (GIMarshallingTestsObject *self, gint8 in) +{ + GValue val = { + 0, + }; + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, in); + g_object_set_property (G_OBJECT (self), "int", &val); +} + +static void +gi_marshalling_tests_sub_object_class_init (GIMarshallingTestsSubObjectClass *klass) +{ + G_OBJECT_CLASS (klass)->finalize = gi_marshalling_tests_sub_object_finalize; + GI_MARSHALLING_TESTS_OBJECT_CLASS (klass)->method_deep_hierarchy = method_deep_hierarchy; +} + +void +gi_marshalling_tests_sub_object_sub_method (GIMarshallingTestsSubObject *object) +{ + g_assert_cmpint (GI_MARSHALLING_TESTS_OBJECT (object)->int_, ==, 0); +} + +void +gi_marshalling_tests_sub_object_overwritten_method (GIMarshallingTestsSubObject *object) +{ + g_assert_cmpint (GI_MARSHALLING_TESTS_OBJECT (object)->int_, ==, 0); +} + +G_DEFINE_TYPE (GIMarshallingTestsSubSubObject, + gi_marshalling_tests_sub_sub_object, + GI_MARSHALLING_TESTS_TYPE_SUB_OBJECT); + +static void +gi_marshalling_tests_sub_sub_object_init (GIMarshallingTestsSubSubObject *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_sub_sub_object_class_init (GIMarshallingTestsSubSubObjectClass *klass G_GNUC_UNUSED) +{ +} + +/* Interfaces */ + +static void +gi_marshalling_tests_interface_class_init (void *g_iface G_GNUC_UNUSED, + void *data G_GNUC_UNUSED) +{ +} + +GType +gi_marshalling_tests_interface_get_type (void) +{ + static GType type = 0; + if (type == 0) + { + /* Not adding prerequisite here for test purposes */ + type = g_type_register_static_simple (G_TYPE_INTERFACE, + "GIMarshallingTestsInterface", + sizeof (GIMarshallingTestsInterfaceIface), + (GClassInitFunc) gi_marshalling_tests_interface_class_init, 0, NULL, 0); + } + + return type; +} + +/** + * gi_marshalling_tests_interface_test_int8_in: + * @in: (in): + */ +void +gi_marshalling_tests_interface_test_int8_in (GIMarshallingTestsInterface *self, gint8 in) +{ + GI_MARSHALLING_TESTS_INTERFACE_GET_IFACE (self)->test_int8_in (self, in); +} + +/** + * gi_marshalling_tests_test_interface_test_int8_in: + * @in: (in): + */ +void +gi_marshalling_tests_test_interface_test_int8_in (GIMarshallingTestsInterface *test_iface, gint8 in) +{ + gi_marshalling_tests_interface_test_int8_in (test_iface, in); +} + +static void test_interface_init (GIMarshallingTestsInterfaceIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GIMarshallingTestsInterfaceImpl, gi_marshalling_tests_interface_impl, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GI_MARSHALLING_TESTS_TYPE_INTERFACE, test_interface_init)) + +static void +gi_marshalling_tests_interface_impl_test_int8_in (GIMarshallingTestsInterface *self G_GNUC_UNUSED, + gint8 in G_GNUC_UNUSED) +{ +} + +static void +test_interface_init (GIMarshallingTestsInterfaceIface *iface) +{ + iface->test_int8_in = gi_marshalling_tests_interface_impl_test_int8_in; +} + +static void +gi_marshalling_tests_interface_impl_init (GIMarshallingTestsInterfaceImpl *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_interface_impl_class_init (GIMarshallingTestsInterfaceImplClass *klass G_GNUC_UNUSED) +{ +} + +/** + * gi_marshalling_tests_interface_impl_get_as_interface: + * + * Returns: (transfer none): + */ +GIMarshallingTestsInterface * +gi_marshalling_tests_interface_impl_get_as_interface (GIMarshallingTestsInterfaceImpl *self) +{ + return (GIMarshallingTestsInterface *) self; +} + +static void +gi_marshalling_tests_interface2_class_init (void *g_iface G_GNUC_UNUSED, + void *data G_GNUC_UNUSED) +{ +} + +GType +gi_marshalling_tests_interface2_get_type (void) +{ + static GType type = 0; + if (type == 0) + { + type = g_type_register_static_simple (G_TYPE_INTERFACE, + "GIMarshallingTestsInterface2", + sizeof (GIMarshallingTestsInterface2Iface), + (GClassInitFunc) gi_marshalling_tests_interface2_class_init, 0, NULL, 0); + } + + return type; +} + +static void +gi_marshalling_tests_interface3_class_init (void *g_iface G_GNUC_UNUSED, + void *data G_GNUC_UNUSED) +{ +} + +GType +gi_marshalling_tests_interface3_get_type (void) +{ + static GType type = 0; + if (type == 0) + { + type = g_type_register_static_simple (G_TYPE_INTERFACE, + "GIMarshallingTestsInterface3", + sizeof (GIMarshallingTestsInterface3Iface), + (GClassInitFunc) gi_marshalling_tests_interface3_class_init, 0, NULL, 0); + } + + return type; +} + +/** + * gi_marshalling_tests_interface3_test_variant_array_in: + * @in: (array length=n_in): + * @n_in: + */ +void +gi_marshalling_tests_interface3_test_variant_array_in (GIMarshallingTestsInterface3 *self, GVariant **in, gsize n_in) +{ + GI_MARSHALLING_TESTS_INTERFACE3_GET_IFACE (self)->test_variant_array_in (self, in, n_in); +} + +/** + * gi_marshalling_tests_int_out_out: + * @int0: (out): + * @int1: (out): + */ +void +gi_marshalling_tests_int_out_out (gint *int0, gint *int1) +{ + *int0 = 6; + *int1 = 7; +} + +/** + * gi_marshalling_tests_int_three_in_three_out: + * @a: (in): + * @b: (in): + * @c: (in): + * @out0: (out): + * @out1: (out): + * @out2: (out): + */ +void +gi_marshalling_tests_int_three_in_three_out (gint a, gint b, gint c, gint *out0, gint *out1, gint *out2) +{ + *out0 = a; + *out1 = b; + *out2 = c; +} + +/** + * gi_marshalling_tests_int_return_out: + * @int_: (out): + */ +gint +gi_marshalling_tests_int_return_out (gint *int_) +{ + *int_ = 7; + return 6; +} + +/** + * gi_marshalling_tests_int_two_in_utf8_two_in_with_allow_none: + * @a: (in): Must be 1 + * @b: (in): Must be 2 + * @c: (in) (allow-none): Must be "3" or NULL + * @d: (in) (allow-none): Must be "4" or NULL + */ +void +gi_marshalling_tests_int_two_in_utf8_two_in_with_allow_none (gint a, gint b, const gchar *c, const gchar *d) +{ + g_assert_cmpint (a, ==, 1); + g_assert_cmpint (b, ==, 2); + if (c != NULL) + g_assert_cmpstr (c, ==, "3"); + if (d != NULL) + g_assert_cmpstr (d, ==, "4"); +} + +/** + * gi_marshalling_tests_int_one_in_utf8_two_in_one_allows_none: + * @a: (in): Must be 1 + * @b: (in) (allow-none): Must be "2" or NULL + * @c: (in): Must be "3" + */ +void +gi_marshalling_tests_int_one_in_utf8_two_in_one_allows_none (gint a, const gchar *b, const gchar *c) +{ + g_assert_cmpint (a, ==, 1); + if (b != NULL) + g_assert_cmpstr (b, ==, "2"); + g_assert_cmpstr (c, ==, "3"); +} + +/** + * gi_marshalling_tests_array_in_utf8_two_in: + * @ints: (array length=length): + * @length: + * @a: (in) (allow-none): Must be "1" or NULL + * @b: (in) (allow-none): Must be "2" or NULL + */ +void +gi_marshalling_tests_array_in_utf8_two_in (const gint *ints, gint length, const gchar *a, const gchar *b) +{ + g_assert_cmpint (length, ==, 4); + g_assert_cmpint (ints[0], ==, -1); + g_assert_cmpint (ints[1], ==, 0); + g_assert_cmpint (ints[2], ==, 1); + g_assert_cmpint (ints[3], ==, 2); + + if (a != NULL) + g_assert_cmpstr (a, ==, "1"); + if (b != NULL) + g_assert_cmpstr (b, ==, "2"); +} + +/** + * gi_marshalling_tests_array_in_utf8_two_in_out_of_order: + * @length: + * @a: (in) (allow-none): Must be "1" or NULL + * @ints: (array length=length): + * @b: (in) (allow-none): Must be "2" or NULL + */ +void +gi_marshalling_tests_array_in_utf8_two_in_out_of_order (gint length, const gchar *a, const gint *ints, const gchar *b) +{ + g_assert_cmpint (length, ==, 4); + g_assert_cmpint (ints[0], ==, -1); + g_assert_cmpint (ints[1], ==, 0); + g_assert_cmpint (ints[2], ==, 1); + g_assert_cmpint (ints[3], ==, 2); + + if (a != NULL) + g_assert_cmpstr (a, ==, "1"); + if (b != NULL) + g_assert_cmpstr (b, ==, "2"); +} + +/* GError */ + +void +gi_marshalling_tests_gerror (GError **error) +{ + GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + g_set_error_literal (error, quark, + GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); +} + +/** + * gi_marshalling_tests_gerror_array_in: + * @in_ints: (array zero-terminated): + */ +void +gi_marshalling_tests_gerror_array_in (gint *in_ints G_GNUC_UNUSED, + GError **error) +{ + GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + g_set_error_literal (error, quark, + GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); +} + +/** + * gi_marshalling_tests_gerror_out: + * @error: (out) (allow-none) (transfer full): location for the GError. + * @debug: (out) (allow-none) (transfer full): location for the debug message + * + * Inspired by gst_message_parse_error. + */ +void +gi_marshalling_tests_gerror_out (GError **error, gchar **debug) +{ + GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + g_set_error_literal (error, quark, + GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); + + if (debug != NULL) + { + *debug = g_strdup (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DEBUG_MESSAGE); + } +} + +/** + * gi_marshalling_tests_gerror_out_uninitialized: + * @v: (out) (allow-none) (transfer full): + * @v2: (out) (allow-none) (transfer full): + */ +gboolean +gi_marshalling_tests_gerror_out_uninitialized (GError **v G_GNUC_UNUSED, gchar **v2 G_GNUC_UNUSED) +{ + return FALSE; +} + +/** + * gi_marshalling_tests_gerror_out_transfer_none: + * @err: (out) (allow-none) (transfer none): location for the GError. + * @debug: (out) (allow-none) (transfer none): location for the debug message + * + * A variant of gi_marshalling_tests_gerror_out() which returns data the caller + * must not free. + */ +void +gi_marshalling_tests_gerror_out_transfer_none (GError **err, const gchar **debug) +{ + static GError error = { 0, + GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, + (gchar *) GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE }; + error.domain = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + *err = &error; + *debug = GI_MARSHALLING_TESTS_CONSTANT_GERROR_DEBUG_MESSAGE; +} + +/** + * gi_marshalling_tests_gerror_out_transfer_none_uninitialized: + * @v: (out) (allow-none) (transfer none): + * @v2: (out) (allow-none) (transfer none): + */ +gboolean +gi_marshalling_tests_gerror_out_transfer_none_uninitialized (GError **v G_GNUC_UNUSED, const gchar **v2 G_GNUC_UNUSED) +{ + return FALSE; +} + +/** + * gi_marshalling_tests_gerror_return: + * + * Yet another variant of gi_marshalling_tests_gerror_out(). + * + * Returns: (transfer full): a GError + */ +GError * +gi_marshalling_tests_gerror_return (void) +{ + GQuark quark = g_quark_from_static_string (GI_MARSHALLING_TESTS_CONSTANT_GERROR_DOMAIN); + + return g_error_new_literal (quark, GI_MARSHALLING_TESTS_CONSTANT_GERROR_CODE, GI_MARSHALLING_TESTS_CONSTANT_GERROR_MESSAGE); +} + +static GIMarshallingTestsOverridesStruct * +gi_marshalling_tests_overrides_struct_copy (GIMarshallingTestsOverridesStruct *struct_) +{ + GIMarshallingTestsOverridesStruct *new_struct; + + new_struct = g_slice_new (GIMarshallingTestsOverridesStruct); + + *new_struct = *struct_; + + return new_struct; +} + +static void +gi_marshalling_tests_overrides_struct_free (GIMarshallingTestsOverridesStruct *struct_) +{ + g_slice_free (GIMarshallingTestsOverridesStruct, struct_); +} + +GType +gi_marshalling_tests_overrides_struct_get_type (void) +{ + static GType type = 0; + + if (type == 0) + { + type = + g_boxed_type_register_static ("GIMarshallingTestsOverridesStruct", + (GBoxedCopyFunc) + gi_marshalling_tests_overrides_struct_copy, + (GBoxedFreeFunc) gi_marshalling_tests_overrides_struct_free); + } + + return type; +} + +GIMarshallingTestsOverridesStruct * +gi_marshalling_tests_overrides_struct_new (void) +{ + return g_slice_new (GIMarshallingTestsOverridesStruct); +} + +glong +gi_marshalling_tests_overrides_struct_method (GIMarshallingTestsOverridesStruct *self G_GNUC_UNUSED) +{ + return 42; +} + +/** + * gi_marshalling_tests_overrides_struct_returnv: + * + * Returns: (transfer full): + */ +GIMarshallingTestsOverridesStruct * +gi_marshalling_tests_overrides_struct_returnv (void) +{ + return gi_marshalling_tests_overrides_struct_new (); +} + +G_DEFINE_TYPE (GIMarshallingTestsOverridesObject, gi_marshalling_tests_overrides_object, G_TYPE_OBJECT); + +static void +gi_marshalling_tests_overrides_object_init (GIMarshallingTestsOverridesObject *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_overrides_object_finalize (GObject *object) +{ + G_OBJECT_CLASS (gi_marshalling_tests_overrides_object_parent_class)->finalize (object); +} + +static void +gi_marshalling_tests_overrides_object_class_init (GIMarshallingTestsOverridesObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); +#if 0 + GObjectClass *parent_class = G_OBJECT_CLASS (klass); +#endif + + object_class->finalize = gi_marshalling_tests_overrides_object_finalize; +} + +GIMarshallingTestsOverridesObject * +gi_marshalling_tests_overrides_object_new (void) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_OVERRIDES_OBJECT, NULL); +} + +glong +gi_marshalling_tests_overrides_object_method (GIMarshallingTestsOverridesObject *self G_GNUC_UNUSED) +{ + return 42; +} + +/** + * gi_marshalling_tests_overrides_object_returnv: + * + * Returns: (transfer full): + */ +GIMarshallingTestsOverridesObject * +gi_marshalling_tests_overrides_object_returnv (void) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_OVERRIDES_OBJECT, NULL); +} + +/** + * gi_marshalling_tests_filename_list_return: + * + * Returns: (transfer none) (element-type filename): List of filenames + */ +GSList * +gi_marshalling_tests_filename_list_return (void) +{ + return NULL; +} + +/** + * gi_marshalling_tests_param_spec_in_bool: + */ +void +gi_marshalling_tests_param_spec_in_bool (const GParamSpec *param) +{ + g_assert (G_IS_PARAM_SPEC (param)); + g_assert_cmpint (G_PARAM_SPEC_VALUE_TYPE (param), ==, G_TYPE_BOOLEAN); + g_assert_cmpstr (g_param_spec_get_name ((GParamSpec *) param), ==, "mybool"); +} + +/** + * gi_marshalling_tests_param_spec_return: + * + * Returns: (transfer full): a #GParamSpec + */ +GParamSpec * +gi_marshalling_tests_param_spec_return (void) +{ + return g_param_spec_string ("test-param", "test", "This is a test", "42", G_PARAM_READABLE); +} + +/** + * gi_marshalling_tests_param_spec_out: + * @param: (out): + */ +void +gi_marshalling_tests_param_spec_out (GParamSpec **param) +{ + *param = g_param_spec_string ("test-param", "test", "This is a test", "42", G_PARAM_READABLE); +} + +/** + * gi_marshalling_tests_param_spec_out_uninitialized: + * @v: (out): + */ +gboolean +gi_marshalling_tests_param_spec_out_uninitialized (GParamSpec **v G_GNUC_UNUSED) +{ + return FALSE; +} + +enum +{ + DUMMY_PROPERTY, + SOME_BOOLEAN_PROPERTY, + SOME_CHAR_PROPERTY, + SOME_UCHAR_PROPERTY, + SOME_INT_PROPERTY, + SOME_UINT_PROPERTY, + SOME_LONG_PROPERTY, + SOME_ULONG_PROPERTY, + SOME_INT64_PROPERTY, + SOME_UINT64_PROPERTY, + SOME_FLOAT_PROPERTY, + SOME_STRING_PROPERTY, + SOME_DOUBLE_PROPERTY, + SOME_STRV_PROPERTY, + SOME_BOXED_STRUCT_PROPERTY, + SOME_VARIANT_PROPERTY, + SOME_BOXED_GLIST_PROPERTY, + SOME_GVALUE_PROPERTY, + SOME_OBJECT_PROPERTY, + SOME_FLAGS_PROPERTY, + SOME_ENUM_PROPERTY, + SOME_BYTE_ARRAY_PROPERTY, + SOME_READONLY_PROPERTY, + SOME_DEPRECATED_INT_PROPERTY, + N_PROPERTIES +}; + +G_DEFINE_TYPE (GIMarshallingTestsPropertiesObject, gi_marshalling_tests_properties_object, G_TYPE_OBJECT); + +static void +gi_marshalling_tests_properties_object_init (GIMarshallingTestsPropertiesObject *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_properties_object_finalize (GObject *obj) +{ + GIMarshallingTestsPropertiesObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (obj); + + if (self->some_gvalue) + { + g_boxed_free (G_TYPE_VALUE, self->some_gvalue); + self->some_gvalue = NULL; + } + + g_clear_pointer (&self->some_string, g_free); + g_clear_pointer (&self->some_strv, g_strfreev); + g_clear_pointer (&self->some_boxed_struct, gi_marshalling_tests_boxed_struct_free); + g_clear_pointer (&self->some_byte_array, g_byte_array_unref); + g_clear_pointer (&self->some_variant, g_variant_unref); + g_clear_pointer (&self->some_boxed_glist, g_list_free); + g_clear_object (&self->some_object); + + G_OBJECT_CLASS (gi_marshalling_tests_properties_object_parent_class)->finalize (obj); +} + +static void +gi_marshalling_tests_properties_object_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GIMarshallingTestsPropertiesObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (object); + switch (property_id) + { + case SOME_BOOLEAN_PROPERTY: + g_value_set_boolean (value, self->some_boolean); + break; + case SOME_CHAR_PROPERTY: + g_value_set_schar (value, self->some_char); + break; + case SOME_UCHAR_PROPERTY: + g_value_set_uchar (value, self->some_uchar); + break; + case SOME_INT_PROPERTY: + g_value_set_int (value, self->some_int); + break; + case SOME_UINT_PROPERTY: + g_value_set_uint (value, self->some_uint); + break; + case SOME_LONG_PROPERTY: + g_value_set_long (value, self->some_long); + break; + case SOME_ULONG_PROPERTY: + g_value_set_ulong (value, self->some_ulong); + break; + case SOME_INT64_PROPERTY: + g_value_set_int64 (value, self->some_int64); + break; + case SOME_UINT64_PROPERTY: + g_value_set_uint64 (value, self->some_uint64); + break; + case SOME_FLOAT_PROPERTY: + g_value_set_float (value, self->some_float); + break; + case SOME_DOUBLE_PROPERTY: + g_value_set_double (value, self->some_double); + break; + case SOME_STRING_PROPERTY: + g_value_set_string (value, self->some_string); + break; + case SOME_STRV_PROPERTY: + g_value_set_boxed (value, self->some_strv); + break; + case SOME_BOXED_STRUCT_PROPERTY: + g_value_set_boxed (value, self->some_boxed_struct); + break; + case SOME_BOXED_GLIST_PROPERTY: + g_value_set_boxed (value, self->some_boxed_glist); + break; + case SOME_GVALUE_PROPERTY: + g_value_set_boxed (value, self->some_gvalue); + break; + case SOME_VARIANT_PROPERTY: + g_value_set_variant (value, self->some_variant); + break; + case SOME_OBJECT_PROPERTY: + g_value_set_object (value, self->some_object); + break; + case SOME_FLAGS_PROPERTY: + g_value_set_flags (value, self->some_flags); + break; + case SOME_ENUM_PROPERTY: + g_value_set_enum (value, self->some_enum); + break; + case SOME_BYTE_ARRAY_PROPERTY: + g_value_set_boxed (value, self->some_byte_array); + break; + case SOME_READONLY_PROPERTY: + g_value_set_int (value, 42); + break; + case SOME_DEPRECATED_INT_PROPERTY: + g_value_set_int (value, self->some_deprecated_int); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gi_marshalling_tests_properties_object_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GIMarshallingTestsPropertiesObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (object); + switch (property_id) + { + case SOME_BOOLEAN_PROPERTY: + self->some_boolean = g_value_get_boolean (value); + break; + case SOME_CHAR_PROPERTY: + self->some_char = g_value_get_schar (value); + break; + case SOME_UCHAR_PROPERTY: + self->some_uchar = g_value_get_uchar (value); + break; + case SOME_INT_PROPERTY: + self->some_int = g_value_get_int (value); + break; + case SOME_UINT_PROPERTY: + self->some_uint = g_value_get_uint (value); + break; + case SOME_LONG_PROPERTY: + self->some_long = g_value_get_long (value); + break; + case SOME_ULONG_PROPERTY: + self->some_ulong = g_value_get_ulong (value); + break; + case SOME_INT64_PROPERTY: + self->some_int64 = g_value_get_int64 (value); + break; + case SOME_UINT64_PROPERTY: + self->some_uint64 = g_value_get_uint64 (value); + break; + case SOME_FLOAT_PROPERTY: + self->some_float = g_value_get_float (value); + break; + case SOME_DOUBLE_PROPERTY: + self->some_double = g_value_get_double (value); + break; + case SOME_STRING_PROPERTY: + g_clear_pointer (&self->some_string, g_free); + self->some_string = g_value_dup_string (value); + break; + case SOME_STRV_PROPERTY: + g_strfreev (self->some_strv); + self->some_strv = g_strdupv (g_value_get_boxed (value)); + break; + case SOME_BOXED_STRUCT_PROPERTY: + gi_marshalling_tests_boxed_struct_free (self->some_boxed_struct); + self->some_boxed_struct = gi_marshalling_tests_boxed_struct_copy (g_value_get_boxed (value)); + break; + case SOME_BOXED_GLIST_PROPERTY: + g_list_free (self->some_boxed_glist); + self->some_boxed_glist = g_list_copy (g_value_get_boxed (value)); + break; + case SOME_GVALUE_PROPERTY: + if (self->some_gvalue) + g_boxed_free (G_TYPE_VALUE, self->some_gvalue); + self->some_gvalue = g_value_dup_boxed (value); + break; + case SOME_VARIANT_PROPERTY: + if (self->some_variant != NULL) + g_variant_unref (self->some_variant); + self->some_variant = g_value_get_variant (value); + if (self->some_variant != NULL) + g_variant_ref (self->some_variant); + break; + case SOME_OBJECT_PROPERTY: + if (self->some_object != NULL) + g_object_unref (self->some_object); + self->some_object = g_value_dup_object (value); + break; + case SOME_FLAGS_PROPERTY: + self->some_flags = g_value_get_flags (value); + break; + case SOME_ENUM_PROPERTY: + self->some_enum = g_value_get_enum (value); + break; + case SOME_BYTE_ARRAY_PROPERTY: + if (self->some_byte_array != NULL) + g_byte_array_unref (self->some_byte_array); + self->some_byte_array = g_value_dup_boxed (value); + break; + case SOME_DEPRECATED_INT_PROPERTY: + self->some_deprecated_int = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GParamSpec *properties_object_properties[N_PROPERTIES] = { + NULL, +}; + +static void +gi_marshalling_tests_properties_object_class_init (GIMarshallingTestsPropertiesObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gi_marshalling_tests_properties_object_finalize; + object_class->get_property = gi_marshalling_tests_properties_object_get_property; + object_class->set_property = gi_marshalling_tests_properties_object_set_property; + + properties_object_properties[SOME_BOOLEAN_PROPERTY] = + g_param_spec_boolean ("some-boolean", + "some-boolean", + "some-boolean", + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_CHAR_PROPERTY] = + g_param_spec_char ("some-char", + "some-char", + "some-char", G_MININT8, + G_MAXINT8, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_UCHAR_PROPERTY] = + g_param_spec_uchar ("some-uchar", + "some-uchar", + "some-uchar", 0, + G_MAXUINT8, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_INT_PROPERTY] = + g_param_spec_int ("some-int", "some-int", + "some-int", G_MININT, + G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_UINT_PROPERTY] = + g_param_spec_uint ("some-uint", + "some-uint", + "some-uint", 0, + G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_LONG_PROPERTY] = + g_param_spec_long ("some-long", + "some-long", + "some-long", G_MINLONG, + G_MAXLONG, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_ULONG_PROPERTY] = + g_param_spec_ulong ("some-ulong", + "some-ulong", + "some-ulong", 0, + G_MAXULONG, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_INT64_PROPERTY] = + g_param_spec_int64 ("some-int64", + "some-int64", + "some-int64", + G_MININT64, G_MAXINT64, + 0, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_UINT64_PROPERTY] = + g_param_spec_uint64 ("some-uint64", + "some-uint64", + "some-uint64", 0, + G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_FLOAT_PROPERTY] = + g_param_spec_float ("some-float", + "some-float", + "some-float", + -1 * G_MAXFLOAT, + G_MAXFLOAT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_DOUBLE_PROPERTY] = + g_param_spec_double ("some-double", + "some-double", + "some-double", + -1 * G_MAXDOUBLE, + G_MAXDOUBLE, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_STRING_PROPERTY] = + g_param_spec_string ("some-string", + "some-string", + "some-string", + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_STRV_PROPERTY] = + g_param_spec_boxed ("some-strv", + "some-strv", + "some-strv", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_BOXED_STRUCT_PROPERTY] = + g_param_spec_boxed ("some-boxed-struct", + "some-boxed-struct", + "some-boxed-struct", + gi_marshalling_tests_boxed_struct_get_type (), G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesObject:some-boxed-glist: (type GLib.List(gint)) (transfer none): + */ + properties_object_properties[SOME_BOXED_GLIST_PROPERTY] = + g_param_spec_boxed ("some-boxed-glist", + "some-boxed-glist", + "some-boxed-glist", + gi_marshalling_tests_boxed_glist_get_type (), G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_GVALUE_PROPERTY] = + g_param_spec_boxed ("some-gvalue", + "some-gvalue", + "some-gvalue", + G_TYPE_VALUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_VARIANT_PROPERTY] = + g_param_spec_variant ("some-variant", + "some-variant", + "some-variant", + G_VARIANT_TYPE_ANY, + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_OBJECT_PROPERTY] = + g_param_spec_object ("some-object", + "some-object", + "some-object", + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_FLAGS_PROPERTY] = + g_param_spec_flags ("some-flags", + "some-flags", + "some-flags", + GI_MARSHALLING_TESTS_TYPE_FLAGS, + GI_MARSHALLING_TESTS_FLAGS_VALUE1, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_ENUM_PROPERTY] = + g_param_spec_enum ("some-enum", + "some-enum", + "some-enum", + GI_MARSHALLING_TESTS_TYPE_GENUM, + GI_MARSHALLING_TESTS_GENUM_VALUE1, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_BYTE_ARRAY_PROPERTY] = + g_param_spec_boxed ("some-byte-array", + "some-byte-array", + "some-byte-array", + G_TYPE_BYTE_ARRAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + properties_object_properties[SOME_READONLY_PROPERTY] = + g_param_spec_int ("some-readonly", + "some-readonly", + "some-readonly", + G_MININT, G_MAXINT, 0, + G_PARAM_READABLE); + + properties_object_properties[SOME_DEPRECATED_INT_PROPERTY] = + g_param_spec_int ("some-deprecated-int", "some-deprecated-int", + "some-deprecated-int", G_MININT, + G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT | G_PARAM_DEPRECATED); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties_object_properties); +} + +GIMarshallingTestsPropertiesObject * +gi_marshalling_tests_properties_object_new (void) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_PROPERTIES_OBJECT, NULL); +} + +G_DEFINE_FINAL_TYPE (GIMarshallingTestsPropertiesAccessorsObject, + gi_marshalling_tests_properties_accessors_object, + G_TYPE_OBJECT) + +GIMarshallingTestsPropertiesAccessorsObject * +gi_marshalling_tests_properties_accessors_object_new (void) +{ + return g_object_new (GI_MARSHALLING_TESTS_TYPE_ACCESSORS_TESTS_PROPERTIES_OBJECT, NULL); +} + +static void +gi_marshalling_tests_properties_accessors_object_init (GIMarshallingTestsPropertiesAccessorsObject *self G_GNUC_UNUSED) +{ +} + +static void +gi_marshalling_tests_properties_accessors_object_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GIMarshallingTestsPropertiesAccessorsObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_ACCESSORS_OBJECT (object); + + switch (property_id) + { + case SOME_BOOLEAN_PROPERTY: + g_value_set_boolean (value, self->some_boolean); + break; + case SOME_CHAR_PROPERTY: + g_value_set_schar (value, self->some_char); + break; + case SOME_UCHAR_PROPERTY: + g_value_set_uchar (value, self->some_uchar); + break; + case SOME_INT_PROPERTY: + g_value_set_int (value, self->some_int); + break; + case SOME_UINT_PROPERTY: + g_value_set_uint (value, self->some_uint); + break; + case SOME_LONG_PROPERTY: + g_value_set_long (value, self->some_long); + break; + case SOME_ULONG_PROPERTY: + g_value_set_ulong (value, self->some_ulong); + break; + case SOME_INT64_PROPERTY: + g_value_set_int64 (value, self->some_int64); + break; + case SOME_UINT64_PROPERTY: + g_value_set_uint64 (value, self->some_uint64); + break; + case SOME_FLOAT_PROPERTY: + g_value_set_float (value, self->some_float); + break; + case SOME_DOUBLE_PROPERTY: + g_value_set_double (value, self->some_double); + break; + case SOME_STRING_PROPERTY: + g_value_set_string (value, self->some_string); + break; + case SOME_STRV_PROPERTY: + g_value_set_boxed (value, self->some_strv); + break; + case SOME_BOXED_STRUCT_PROPERTY: + g_value_set_boxed (value, self->some_boxed_struct); + break; + case SOME_BOXED_GLIST_PROPERTY: + g_value_set_boxed (value, self->some_boxed_glist); + break; + case SOME_GVALUE_PROPERTY: + g_value_set_boxed (value, self->some_gvalue); + break; + case SOME_VARIANT_PROPERTY: + g_value_set_variant (value, self->some_variant); + break; + case SOME_OBJECT_PROPERTY: + g_value_set_object (value, self->some_object); + break; + case SOME_FLAGS_PROPERTY: + g_value_set_flags (value, self->some_flags); + break; + case SOME_ENUM_PROPERTY: + g_value_set_enum (value, self->some_enum); + break; + case SOME_BYTE_ARRAY_PROPERTY: + g_value_set_boxed (value, self->some_byte_array); + break; + case SOME_READONLY_PROPERTY: + g_value_set_int (value, 42); + break; + case SOME_DEPRECATED_INT_PROPERTY: + g_value_set_int (value, self->some_deprecated_int); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gi_marshalling_tests_properties_accessors_object_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GIMarshallingTestsPropertiesAccessorsObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_ACCESSORS_OBJECT (object); + + switch (property_id) + { + case SOME_BOOLEAN_PROPERTY: + self->some_boolean = g_value_get_boolean (value); + break; + case SOME_CHAR_PROPERTY: + self->some_char = g_value_get_schar (value); + break; + case SOME_UCHAR_PROPERTY: + self->some_uchar = g_value_get_uchar (value); + break; + case SOME_INT_PROPERTY: + self->some_int = g_value_get_int (value); + break; + case SOME_UINT_PROPERTY: + self->some_uint = g_value_get_uint (value); + break; + case SOME_LONG_PROPERTY: + self->some_long = g_value_get_long (value); + break; + case SOME_ULONG_PROPERTY: + self->some_ulong = g_value_get_ulong (value); + break; + case SOME_INT64_PROPERTY: + self->some_int64 = g_value_get_int64 (value); + break; + case SOME_UINT64_PROPERTY: + self->some_uint64 = g_value_get_uint64 (value); + break; + case SOME_FLOAT_PROPERTY: + self->some_float = g_value_get_float (value); + break; + case SOME_DOUBLE_PROPERTY: + self->some_double = g_value_get_double (value); + break; + case SOME_STRING_PROPERTY: + g_clear_pointer (&self->some_string, g_free); + self->some_string = g_value_dup_string (value); + break; + case SOME_STRV_PROPERTY: + g_strfreev (self->some_strv); + self->some_strv = g_strdupv (g_value_get_boxed (value)); + break; + case SOME_BOXED_STRUCT_PROPERTY: + gi_marshalling_tests_boxed_struct_free (self->some_boxed_struct); + self->some_boxed_struct = gi_marshalling_tests_boxed_struct_copy (g_value_get_boxed (value)); + break; + case SOME_BOXED_GLIST_PROPERTY: + g_list_free (self->some_boxed_glist); + self->some_boxed_glist = g_list_copy (g_value_get_boxed (value)); + break; + case SOME_GVALUE_PROPERTY: + if (self->some_gvalue) + g_boxed_free (G_TYPE_VALUE, self->some_gvalue); + self->some_gvalue = g_value_dup_boxed (value); + break; + case SOME_VARIANT_PROPERTY: + if (self->some_variant != NULL) + g_variant_unref (self->some_variant); + self->some_variant = g_value_get_variant (value); + if (self->some_variant != NULL) + g_variant_ref (self->some_variant); + break; + case SOME_OBJECT_PROPERTY: + if (self->some_object != NULL) + g_object_unref (self->some_object); + self->some_object = g_value_dup_object (value); + break; + case SOME_FLAGS_PROPERTY: + self->some_flags = g_value_get_flags (value); + break; + case SOME_ENUM_PROPERTY: + self->some_enum = g_value_get_enum (value); + break; + case SOME_BYTE_ARRAY_PROPERTY: + if (self->some_byte_array != NULL) + g_byte_array_unref (self->some_byte_array); + self->some_byte_array = g_value_dup_boxed (value); + break; + case SOME_DEPRECATED_INT_PROPERTY: + self->some_deprecated_int = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gi_marshalling_tests_properties_accessors_object_dispose (GObject *obj) +{ + GIMarshallingTestsPropertiesAccessorsObject *self; + self = GI_MARSHALLING_TESTS_PROPERTIES_ACCESSORS_OBJECT (obj); + + if (self->some_gvalue) + { + g_boxed_free (G_TYPE_VALUE, self->some_gvalue); + self->some_gvalue = NULL; + } + + g_clear_pointer (&self->some_string, g_free); + g_clear_pointer (&self->some_strv, g_strfreev); + g_clear_pointer (&self->some_boxed_struct, gi_marshalling_tests_boxed_struct_free); + g_clear_pointer (&self->some_byte_array, g_byte_array_unref); + g_clear_pointer (&self->some_variant, g_variant_unref); + g_clear_pointer (&self->some_boxed_glist, g_list_free); + g_clear_object (&self->some_object); + + G_OBJECT_CLASS (gi_marshalling_tests_properties_accessors_object_parent_class)->dispose (obj); +} + +static GParamSpec *accessors_object_properties[N_PROPERTIES] = { + NULL, +}; + +static void +gi_marshalling_tests_properties_accessors_object_class_init (GIMarshallingTestsPropertiesAccessorsObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gi_marshalling_tests_properties_accessors_object_dispose; + object_class->get_property = gi_marshalling_tests_properties_accessors_object_get_property; + object_class->set_property = gi_marshalling_tests_properties_accessors_object_set_property; + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-boolean: (setter set_boolean) (getter get_boolean): + */ + accessors_object_properties[SOME_BOOLEAN_PROPERTY] = + g_param_spec_boolean ("some-boolean", + "some-boolean", + "some-boolean", + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-char: (setter set_char) (getter get_char): + */ + accessors_object_properties[SOME_CHAR_PROPERTY] = + g_param_spec_char ("some-char", + "some-char", + "some-char", + G_MININT8, + G_MAXINT8, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-uchar: (setter set_uchar) (getter get_uchar): + */ + accessors_object_properties[SOME_UCHAR_PROPERTY] = + g_param_spec_uchar ("some-uchar", + "some-uchar", + "some-uchar", 0, + G_MAXUINT8, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-int: (setter set_int) (getter get_int): + */ + accessors_object_properties[SOME_INT_PROPERTY] = + g_param_spec_int ("some-int", "some-int", + "some-int", G_MININT, + G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-uint: (setter set_uint) (getter get_uint): + */ + accessors_object_properties[SOME_UINT_PROPERTY] = + g_param_spec_uint ("some-uint", + "some-uint", + "some-uint", 0, + G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-long: (setter set_long) (getter get_long): + */ + accessors_object_properties[SOME_LONG_PROPERTY] = + g_param_spec_long ("some-long", + "some-long", + "some-long", G_MINLONG, + G_MAXLONG, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-ulong: (setter set_ulong) (getter get_ulong): + */ + accessors_object_properties[SOME_ULONG_PROPERTY] = + g_param_spec_ulong ("some-ulong", + "some-ulong", + "some-ulong", 0, + G_MAXULONG, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-int64: (setter set_int64) (getter get_int64): + */ + accessors_object_properties[SOME_INT64_PROPERTY] = + g_param_spec_int64 ("some-int64", + "some-int64", + "some-int64", + G_MININT64, G_MAXINT64, + 0, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-uint64: (setter set_uint64) (getter get_uint64): + */ + accessors_object_properties[SOME_UINT64_PROPERTY] = + g_param_spec_uint64 ("some-uint64", + "some-uint64", + "some-uint64", 0, + G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-float: (setter set_float) (getter get_float): + */ + accessors_object_properties[SOME_FLOAT_PROPERTY] = + g_param_spec_float ("some-float", + "some-float", + "some-float", + -1 * G_MAXFLOAT, + G_MAXFLOAT, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-double: (setter set_double) (getter get_double): + */ + accessors_object_properties[SOME_DOUBLE_PROPERTY] = + g_param_spec_double ("some-double", + "some-double", + "some-double", + -1 * G_MAXDOUBLE, + G_MAXDOUBLE, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-string: (setter set_string) (getter get_string): + */ + accessors_object_properties[SOME_STRING_PROPERTY] = + g_param_spec_string ("some-string", + "some-string", + "some-string", + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-strv: (setter set_strv) (getter get_strv): + */ + accessors_object_properties[SOME_STRV_PROPERTY] = + g_param_spec_boxed ("some-strv", + "some-strv", + "some-strv", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-boxed-struct: (setter set_boxed_struct) (getter get_boxed_struct): + */ + accessors_object_properties[SOME_BOXED_STRUCT_PROPERTY] = + g_param_spec_boxed ("some-boxed-struct", + "some-boxed-struct", + "some-boxed-struct", + gi_marshalling_tests_boxed_struct_get_type (), + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-boxed-glist: (type GLib.List(gint)) (transfer none) (setter set_boxed_glist) (getter get_boxed_glist): + */ + accessors_object_properties[SOME_BOXED_GLIST_PROPERTY] = + g_param_spec_boxed ("some-boxed-glist", + "some-boxed-glist", + "some-boxed-glist", + gi_marshalling_tests_boxed_glist_get_type (), + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-gvalue: (setter set_gvalue) (getter get_gvalue): + */ + accessors_object_properties[SOME_GVALUE_PROPERTY] = + g_param_spec_boxed ("some-gvalue", + "some-gvalue", + "some-gvalue", + G_TYPE_VALUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-variant: (setter set_variant) (getter get_variant): + */ + accessors_object_properties[SOME_VARIANT_PROPERTY] = + g_param_spec_variant ("some-variant", + "some-variant", + "some-variant", + G_VARIANT_TYPE_ANY, + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-object: (setter set_object) (getter get_object): + */ + accessors_object_properties[SOME_OBJECT_PROPERTY] = + g_param_spec_object ("some-object", + "some-object", + "some-object", + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-flags: (setter set_flags) (getter get_flags): + */ + accessors_object_properties[SOME_FLAGS_PROPERTY] = + g_param_spec_flags ("some-flags", + "some-flags", + "some-flags", + GI_MARSHALLING_TESTS_TYPE_FLAGS, + GI_MARSHALLING_TESTS_FLAGS_VALUE1, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-enum: (setter set_enum) (getter get_enum): + */ + accessors_object_properties[SOME_ENUM_PROPERTY] = + g_param_spec_enum ("some-enum", + "some-enum", + "some-enum", + GI_MARSHALLING_TESTS_TYPE_GENUM, + GI_MARSHALLING_TESTS_GENUM_VALUE1, + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-byte_array: (setter set_byte_array) (getter get_byte_array): + */ + accessors_object_properties[SOME_BYTE_ARRAY_PROPERTY] = + g_param_spec_boxed ("some-byte-array", + "some-byte-array", + "some-byte-array", + G_TYPE_BYTE_ARRAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-readonly: (getter get_readonly): + */ + accessors_object_properties[SOME_READONLY_PROPERTY] = + g_param_spec_int ("some-readonly", + "some-readonly", + "some-readonly", + G_MININT, G_MAXINT, 0, + G_PARAM_READABLE); + + /** + * GIMarshallingTestsPropertiesAccessorsObject:some-deprecated-int: (setter set_deprecated_int) (getter get_deprecated_int): + * Deprecated: 0.1 + */ + accessors_object_properties[SOME_DEPRECATED_INT_PROPERTY] = + g_param_spec_int ("some-deprecated-int", + "some-deprecated-int", + "some-deprecated-int", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | + G_PARAM_DEPRECATED); + + g_object_class_install_properties (object_class, N_PROPERTIES, accessors_object_properties); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_boolean: (set-property some-boolean) + * @self: + * @some_boolean: + */ +void +gi_marshalling_tests_properties_accessors_object_set_boolean (GIMarshallingTestsPropertiesAccessorsObject *self, gboolean some_boolean) +{ + if (self->some_boolean == some_boolean) + return; + + self->some_boolean = some_boolean; + g_object_notify (G_OBJECT (self), "some-boolean"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_char: (set-property some-char) + * @self: + * @some_char: + */ +void +gi_marshalling_tests_properties_accessors_object_set_char (GIMarshallingTestsPropertiesAccessorsObject *self, gchar some_char) +{ + if (self->some_char == some_char) + return; + + self->some_char = some_char; + g_object_notify (G_OBJECT (self), "some-char"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_uchar: (set-property some-uchar) + * @self: + * @some_uchar: + */ +void +gi_marshalling_tests_properties_accessors_object_set_uchar (GIMarshallingTestsPropertiesAccessorsObject *self, guchar some_uchar) +{ + if (self->some_uchar == some_uchar) + return; + + self->some_uchar = some_uchar; + g_object_notify (G_OBJECT (self), "some-uchar"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_int: (set-property some-int) + * @self: + * @some_int: + */ +void +gi_marshalling_tests_properties_accessors_object_set_int (GIMarshallingTestsPropertiesAccessorsObject *self, gint some_int) +{ + if (self->some_int == some_int) + return; + + self->some_int = some_int; + g_object_notify (G_OBJECT (self), "some-int"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_uint: (set-property some-uint) + * @self: + * @some_uint: + */ +void +gi_marshalling_tests_properties_accessors_object_set_uint (GIMarshallingTestsPropertiesAccessorsObject *self, guint some_uint) +{ + if (self->some_uint == some_uint) + return; + + self->some_uint = some_uint; + g_object_notify (G_OBJECT (self), "some-uint"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_long: (set-property some-long) + * @self: + * @some_long: + */ +void +gi_marshalling_tests_properties_accessors_object_set_long (GIMarshallingTestsPropertiesAccessorsObject *self, glong some_long) +{ + if (self->some_long == some_long) + return; + + self->some_long = some_long; + g_object_notify (G_OBJECT (self), "some-long"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_ulong: (set-property some-ulong) + * @self: + * @some_ulong: + */ +void +gi_marshalling_tests_properties_accessors_object_set_ulong (GIMarshallingTestsPropertiesAccessorsObject *self, gulong some_ulong) +{ + if (self->some_ulong == some_ulong) + return; + + self->some_ulong = some_ulong; + g_object_notify (G_OBJECT (self), "some-ulong"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_int64: (set-property some-int64) + * @self: + * @some_int64: + */ +void +gi_marshalling_tests_properties_accessors_object_set_int64 (GIMarshallingTestsPropertiesAccessorsObject *self, gint64 some_int64) +{ + if (self->some_int64 == some_int64) + return; + + self->some_int64 = some_int64; + g_object_notify (G_OBJECT (self), "some-int64"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_uint64: (set-property some-uint64) + * @self: + * @some_uint64: + */ +void +gi_marshalling_tests_properties_accessors_object_set_uint64 (GIMarshallingTestsPropertiesAccessorsObject *self, guint64 some_uint64) +{ + if (self->some_uint64 == some_uint64) + return; + + self->some_uint64 = some_uint64; + g_object_notify (G_OBJECT (self), "some-uint64"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_float: (set-property some-float) + * @self: + * @some_float: + */ +void +gi_marshalling_tests_properties_accessors_object_set_float (GIMarshallingTestsPropertiesAccessorsObject *self, gfloat some_float) +{ + if (self->some_float == some_float) + return; + + self->some_float = some_float; + g_object_notify (G_OBJECT (self), "some-float"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_double: (set-property some-double) + * @self: + * @some_double: + */ +void +gi_marshalling_tests_properties_accessors_object_set_double (GIMarshallingTestsPropertiesAccessorsObject *self, gdouble some_double) +{ + if (self->some_double == some_double) + return; + + self->some_double = some_double; + g_object_notify (G_OBJECT (self), "some-double"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_string: (set-property some-string) + * @self: + * @some_string: + */ +void +gi_marshalling_tests_properties_accessors_object_set_string (GIMarshallingTestsPropertiesAccessorsObject *self, gchar *some_string) +{ + if (g_strcmp0 (self->some_string, some_string) == 0) + return; + + g_set_str (&self->some_string, some_string); + g_object_notify (G_OBJECT (self), "some-string"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_strv: (set-property some-strv) + * @self: + * @some_strv: + */ +void +gi_marshalling_tests_properties_accessors_object_set_strv (GIMarshallingTestsPropertiesAccessorsObject *self, GStrv some_strv) +{ + if (self->some_strv == some_strv) + return; + + if (self->some_strv && some_strv && + g_strv_equal ((const char **) self->some_strv, (const char **) some_strv)) + return; + + g_clear_pointer (&self->some_strv, g_strfreev); + self->some_strv = g_strdupv (some_strv); + g_object_notify (G_OBJECT (self), "some-strv"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_boxed_struct: (set-property some-boxed-struct) + * @self: + * @some_boxed_struct: + */ +void +gi_marshalling_tests_properties_accessors_object_set_boxed_struct (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsBoxedStruct *some_boxed_struct) +{ + if (self->some_boxed_struct == some_boxed_struct) + return; + + g_clear_pointer (&self->some_boxed_struct, gi_marshalling_tests_boxed_struct_free); + self->some_boxed_struct = gi_marshalling_tests_boxed_struct_copy (some_boxed_struct); + g_object_notify (G_OBJECT (self), "some-boxed-struct"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_boxed_glist: (set-property some-boxed-glist) + * @self: + * @some_boxed_glist: (element-type int): + */ +void +gi_marshalling_tests_properties_accessors_object_set_boxed_glist (GIMarshallingTestsPropertiesAccessorsObject *self, GList *some_boxed_glist) +{ + if (self->some_boxed_glist == some_boxed_glist) + return; + + g_clear_pointer (&self->some_boxed_glist, g_list_free); + self->some_boxed_glist = g_list_copy (some_boxed_glist); + g_object_notify (G_OBJECT (self), "some-boxed-glist"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_gvalue: (set-property some-gvalue) + * @self: + * @some_gvalue: + */ +void +gi_marshalling_tests_properties_accessors_object_set_gvalue (GIMarshallingTestsPropertiesAccessorsObject *self, GValue *some_gvalue) +{ + if (self->some_gvalue == some_gvalue) + return; + + if (self->some_gvalue) + g_boxed_free (G_TYPE_VALUE, self->some_gvalue); + + self->some_gvalue = g_boxed_copy (G_TYPE_VALUE, some_gvalue); + g_object_notify (G_OBJECT (self), "some-gvalue"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_variant: (set-property some-variant) + * @self: + * @some_variant: + */ +void +gi_marshalling_tests_properties_accessors_object_set_variant (GIMarshallingTestsPropertiesAccessorsObject *self, GVariant *some_variant) +{ + if (self->some_variant == some_variant) + return; + + g_clear_pointer (&self->some_variant, g_variant_unref); + self->some_variant = g_variant_ref_sink (some_variant); + g_object_notify (G_OBJECT (self), "some-variant"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_object: (set-property some-object) + * @self: + * @some_object: + */ +void +gi_marshalling_tests_properties_accessors_object_set_object (GIMarshallingTestsPropertiesAccessorsObject *self, GObject *some_object) +{ + if (self->some_object == some_object) + return; + + g_set_object (&self->some_object, some_object); + g_object_notify (G_OBJECT (self), "some-object"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_flags: (set-property some-flags) + * @self: + * @some_flags: + */ +void +gi_marshalling_tests_properties_accessors_object_set_flags (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsFlags some_flags) +{ + if (self->some_flags == some_flags) + return; + + self->some_flags = some_flags; + g_object_notify (G_OBJECT (self), "some-flags"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_enum: (set-property some-enum) + * @self: + * @some_enum: + */ +void +gi_marshalling_tests_properties_accessors_object_set_enum (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsGEnum some_enum) +{ + if (self->some_enum == some_enum) + return; + + self->some_enum = some_enum; + g_object_notify (G_OBJECT (self), "some-enum"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_byte_array: (set-property some-byte-array) + * @self: + * @some_byte_array: + */ +void +gi_marshalling_tests_properties_accessors_object_set_byte_array (GIMarshallingTestsPropertiesAccessorsObject *self, GByteArray *some_byte_array) +{ + if (self->some_byte_array == some_byte_array) + return; + + g_clear_pointer (&self->some_byte_array, g_byte_array_unref); + self->some_byte_array = g_byte_array_ref (some_byte_array); + g_object_notify (G_OBJECT (self), "some-byte-array"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_set_deprecated_int: (set-property some-deprecated-int) + * @self: + * @some_deprecated_int: + */ +void +gi_marshalling_tests_properties_accessors_object_set_deprecated_int (GIMarshallingTestsPropertiesAccessorsObject *self, gint some_deprecated_int) +{ + if (self->some_deprecated_int == some_deprecated_int) + return; + + self->some_deprecated_int = some_deprecated_int; + g_object_notify (G_OBJECT (self), "some-deprecated-int"); +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_boolean: (get-property some-boolean) + * @self: + */ +gboolean +gi_marshalling_tests_properties_accessors_object_get_boolean (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_boolean; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_char: (get-property some-char) + * @self: + */ +gchar +gi_marshalling_tests_properties_accessors_object_get_char (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_char; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_uchar: (get-property some-uchar) + * @self: + */ +guchar +gi_marshalling_tests_properties_accessors_object_get_uchar (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_uchar; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_int: (get-property some-int) + * @self: + */ +gint +gi_marshalling_tests_properties_accessors_object_get_int (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_int; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_uint: (get-property some-uint) + * @self: + */ +guint +gi_marshalling_tests_properties_accessors_object_get_uint (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_uint; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_long: (get-property some-long) + * @self: + */ +glong +gi_marshalling_tests_properties_accessors_object_get_long (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_long; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_ulong: (get-property some-ulong) + * @self: + */ +gulong +gi_marshalling_tests_properties_accessors_object_get_ulong (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_ulong; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_int64: (get-property some-int64) + * @self: + */ +gint64 +gi_marshalling_tests_properties_accessors_object_get_int64 (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_int64; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_uint64: (get-property some-uint64) + * @self: + */ +guint64 +gi_marshalling_tests_properties_accessors_object_get_uint64 (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_uint64; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_float: (get-property some-float) + * @self: + */ +gfloat +gi_marshalling_tests_properties_accessors_object_get_float (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_float; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_double: (get-property some-double) + * @self: + */ +gdouble +gi_marshalling_tests_properties_accessors_object_get_double (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_double; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_string: (get-property some-string) + * @self: + */ +const gchar * +gi_marshalling_tests_properties_accessors_object_get_string (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_string; +} + +/** + * gi_marshalling_tests_properties_accessors_object_get_strv: (get-property some-strv) + * @self: + * + * Returns: (transfer none): + */ +gchar ** +gi_marshalling_tests_properties_accessors_object_get_strv (GIMarshallingTestsPropertiesAccessorsObject *self) { + return self->some_strv; } -static void -gi_marshalling_tests_properties_object_finalize (GObject *obj) +/** + * gi_marshalling_tests_properties_accessors_object_get_boxed_struct: (get-property some-boxed-struct) + * @self: + * + * Returns: (transfer none): + */ +GIMarshallingTestsBoxedStruct * +gi_marshalling_tests_properties_accessors_object_get_boxed_struct (GIMarshallingTestsPropertiesAccessorsObject *self) { - GIMarshallingTestsPropertiesObject *self; - self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (obj); - - if (self->some_gvalue) - { - g_boxed_free (G_TYPE_VALUE, self->some_gvalue); - self->some_gvalue = NULL; - } - - g_clear_pointer (&self->some_string, g_free); - g_clear_pointer (&self->some_strv, g_strfreev); - g_clear_pointer (&self->some_boxed_struct, gi_marshalling_tests_boxed_struct_free); - g_clear_pointer (&self->some_byte_array, g_byte_array_unref); - g_clear_pointer (&self->some_variant, g_variant_unref); - g_clear_pointer (&self->some_boxed_glist, g_list_free); - g_clear_object (&self->some_object); + return self->some_boxed_struct; +} - G_OBJECT_CLASS (gi_marshalling_tests_properties_object_parent_class)->finalize (obj); +/** + * gi_marshalling_tests_properties_accessors_object_get_boxed_glist: (get-property some-boxed-glist) + * @self: + * + * Returns: (element-type int) (transfer none): + */ +GList * +gi_marshalling_tests_properties_accessors_object_get_boxed_glist (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_boxed_glist; } -static void -gi_marshalling_tests_properties_object_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) +/** + * gi_marshalling_tests_properties_accessors_object_get_gvalue: (get-property some-gvalue) + * @self: + * + * Returns: (transfer none): + */ +GValue * +gi_marshalling_tests_properties_accessors_object_get_gvalue (GIMarshallingTestsPropertiesAccessorsObject *self) { - GIMarshallingTestsPropertiesObject *self; - self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (object); - switch (property_id) - { - case SOME_BOOLEAN_PROPERTY: - g_value_set_boolean (value, self->some_boolean); - break; - case SOME_CHAR_PROPERTY: - g_value_set_schar (value, self->some_char); - break; - case SOME_UCHAR_PROPERTY: - g_value_set_uchar (value, self->some_uchar); - break; - case SOME_INT_PROPERTY: - g_value_set_int (value, self->some_int); - break; - case SOME_UINT_PROPERTY: - g_value_set_uint (value, self->some_uint); - break; - case SOME_LONG_PROPERTY: - g_value_set_long (value, self->some_long); - break; - case SOME_ULONG_PROPERTY: - g_value_set_ulong (value, self->some_ulong); - break; - case SOME_INT64_PROPERTY: - g_value_set_int64 (value, self->some_int64); - break; - case SOME_UINT64_PROPERTY: - g_value_set_uint64 (value, self->some_uint64); - break; - case SOME_FLOAT_PROPERTY: - g_value_set_float (value, self->some_float); - break; - case SOME_DOUBLE_PROPERTY: - g_value_set_double (value, self->some_double); - break; - case SOME_STRING_PROPERTY: - g_value_set_string (value, self->some_string); - break; - case SOME_STRV_PROPERTY: - g_value_set_boxed (value, self->some_strv); - break; - case SOME_BOXED_STRUCT_PROPERTY: - g_value_set_boxed (value, self->some_boxed_struct); - break; - case SOME_BOXED_GLIST_PROPERTY: - g_value_set_boxed (value, self->some_boxed_glist); - break; - case SOME_GVALUE_PROPERTY: - g_value_set_boxed (value, self->some_gvalue); - break; - case SOME_VARIANT_PROPERTY: - g_value_set_variant (value, self->some_variant); - break; - case SOME_OBJECT_PROPERTY: - g_value_set_object (value, self->some_object); - break; - case SOME_FLAGS_PROPERTY: - g_value_set_flags (value, self->some_flags); - break; - case SOME_ENUM_PROPERTY: - g_value_set_enum (value, self->some_enum); - break; - case SOME_BYTE_ARRAY_PROPERTY: - g_value_set_boxed (value, self->some_byte_array); - break; - case SOME_READONLY_PROPERTY: - g_value_set_int (value, 42); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } + return self->some_gvalue; } -static void -gi_marshalling_tests_properties_object_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +/** + * gi_marshalling_tests_properties_accessors_object_get_variant: (get-property some-variant) + * @self: + * + * Returns: (transfer none): + */ +GVariant * +gi_marshalling_tests_properties_accessors_object_get_variant (GIMarshallingTestsPropertiesAccessorsObject *self) { - GIMarshallingTestsPropertiesObject *self; - self = GI_MARSHALLING_TESTS_PROPERTIES_OBJECT (object); - switch (property_id) - { - case SOME_BOOLEAN_PROPERTY: - self->some_boolean = g_value_get_boolean (value); - break; - case SOME_CHAR_PROPERTY: - self->some_char = g_value_get_schar (value); - break; - case SOME_UCHAR_PROPERTY: - self->some_uchar = g_value_get_uchar (value); - break; - case SOME_INT_PROPERTY: - self->some_int = g_value_get_int (value); - break; - case SOME_UINT_PROPERTY: - self->some_uint = g_value_get_uint (value); - break; - case SOME_LONG_PROPERTY: - self->some_long = g_value_get_long (value); - break; - case SOME_ULONG_PROPERTY: - self->some_ulong = g_value_get_ulong (value); - break; - case SOME_INT64_PROPERTY: - self->some_int64 = g_value_get_int64 (value); - break; - case SOME_UINT64_PROPERTY: - self->some_uint64 = g_value_get_uint64 (value); - break; - case SOME_FLOAT_PROPERTY: - self->some_float = g_value_get_float (value); - break; - case SOME_DOUBLE_PROPERTY: - self->some_double = g_value_get_double (value); - break; - case SOME_STRING_PROPERTY: - g_clear_pointer (&self->some_string, g_free); - self->some_string = g_value_dup_string (value); - break; - case SOME_STRV_PROPERTY: - g_strfreev (self->some_strv); - self->some_strv = g_strdupv (g_value_get_boxed (value)); - break; - case SOME_BOXED_STRUCT_PROPERTY: - gi_marshalling_tests_boxed_struct_free (self->some_boxed_struct); - self->some_boxed_struct = gi_marshalling_tests_boxed_struct_copy (g_value_get_boxed (value)); - break; - case SOME_BOXED_GLIST_PROPERTY: - g_list_free (self->some_boxed_glist); - self->some_boxed_glist = g_list_copy (g_value_get_boxed (value)); - break; - case SOME_GVALUE_PROPERTY: - if (self->some_gvalue) - g_boxed_free (G_TYPE_VALUE, self->some_gvalue); - self->some_gvalue = g_value_dup_boxed (value); - break; - case SOME_VARIANT_PROPERTY: - if (self->some_variant != NULL) - g_variant_unref (self->some_variant); - self->some_variant = g_value_get_variant (value); - if (self->some_variant != NULL) - g_variant_ref (self->some_variant); - break; - case SOME_OBJECT_PROPERTY: - if (self->some_object != NULL) - g_object_unref (self->some_object); - self->some_object = g_value_dup_object (value); - break; - case SOME_FLAGS_PROPERTY: - self->some_flags = g_value_get_flags (value); - break; - case SOME_ENUM_PROPERTY: - self->some_enum = g_value_get_enum (value); - break; - case SOME_BYTE_ARRAY_PROPERTY: - if (self->some_byte_array != NULL) - g_byte_array_unref (self->some_byte_array); - self->some_byte_array = g_value_dup_boxed (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } + return self->some_variant; } -static void -gi_marshalling_tests_properties_object_class_init (GIMarshallingTestsPropertiesObjectClass *klass) +/** + * gi_marshalling_tests_properties_accessors_object_get_object: (get-property some-object) + * @self: + * + * Returns: (transfer none): + */ +GObject * +gi_marshalling_tests_properties_accessors_object_get_object (GIMarshallingTestsPropertiesAccessorsObject *self) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); + return self->some_object; +} - object_class->finalize = gi_marshalling_tests_properties_object_finalize; - object_class->get_property = gi_marshalling_tests_properties_object_get_property; - object_class->set_property = gi_marshalling_tests_properties_object_set_property; +/** + * gi_marshalling_tests_properties_accessors_object_get_flags: (get-property some-flags) + * @self: + */ +GIMarshallingTestsFlags +gi_marshalling_tests_properties_accessors_object_get_flags (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_flags; +} - g_object_class_install_property (object_class, SOME_BOOLEAN_PROPERTY, - g_param_spec_boolean ("some-boolean", - "some-boolean", - "some-boolean", - FALSE, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_CHAR_PROPERTY, - g_param_spec_char ("some-char", - "some-char", - "some-char", G_MININT8, - G_MAXINT8, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_UCHAR_PROPERTY, - g_param_spec_uchar ("some-uchar", - "some-uchar", - "some-uchar", 0, - G_MAXUINT8, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_INT_PROPERTY, - g_param_spec_int ("some-int", "some-int", - "some-int", G_MININT, - G_MAXINT, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); +/** + * gi_marshalling_tests_properties_accessors_object_get_enum: (get-property some-enum) + * @self: + */ +GIMarshallingTestsGEnum +gi_marshalling_tests_properties_accessors_object_get_enum (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_enum; +} - g_object_class_install_property (object_class, SOME_UINT_PROPERTY, - g_param_spec_uint ("some-uint", - "some-uint", - "some-uint", 0, - G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_LONG_PROPERTY, - g_param_spec_long ("some-long", - "some-long", - "some-long", G_MINLONG, - G_MAXLONG, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_ULONG_PROPERTY, - g_param_spec_ulong ("some-ulong", - "some-ulong", - "some-ulong", 0, - G_MAXULONG, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_INT64_PROPERTY, - g_param_spec_int64 ("some-int64", - "some-int64", - "some-int64", - G_MININT64, G_MAXINT64, - 0, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_UINT64_PROPERTY, - g_param_spec_uint64 ("some-uint64", - "some-uint64", - "some-uint64", 0, - G_MAXUINT64, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_FLOAT_PROPERTY, - g_param_spec_float ("some-float", - "some-float", - "some-float", - -1 * G_MAXFLOAT, - G_MAXFLOAT, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_DOUBLE_PROPERTY, - g_param_spec_double ("some-double", - "some-double", - "some-double", - -1 * G_MAXDOUBLE, - G_MAXDOUBLE, 0, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_STRING_PROPERTY, - g_param_spec_string ("some-string", - "some-string", - "some-string", - NULL, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_STRV_PROPERTY, - g_param_spec_boxed ("some-strv", - "some-strv", - "some-strv", - G_TYPE_STRV, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_BOXED_STRUCT_PROPERTY, - g_param_spec_boxed ("some-boxed-struct", - "some-boxed-struct", - "some-boxed-struct", - gi_marshalling_tests_boxed_struct_get_type (), G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); +/** + * gi_marshalling_tests_properties_accessors_object_get_byte_array: (get-property some-byte-array) + * @self: + * + * Returns: (transfer none): + */ +GByteArray * +gi_marshalling_tests_properties_accessors_object_get_byte_array (GIMarshallingTestsPropertiesAccessorsObject *self) +{ + return self->some_byte_array; +} - /** - * GIMarshallingTestsPropertiesObject:some-boxed-glist: (type GLib.List(gint)) (transfer none): - */ - g_object_class_install_property (object_class, SOME_BOXED_GLIST_PROPERTY, - g_param_spec_boxed ("some-boxed-glist", - "some-boxed-glist", - "some-boxed-glist", - gi_marshalling_tests_boxed_glist_get_type (), G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_GVALUE_PROPERTY, - g_param_spec_boxed ("some-gvalue", - "some-gvalue", - "some-gvalue", - G_TYPE_VALUE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_VARIANT_PROPERTY, - g_param_spec_variant ("some-variant", - "some-variant", - "some-variant", - G_VARIANT_TYPE_ANY, - NULL, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_OBJECT_PROPERTY, - g_param_spec_object ("some-object", - "some-object", - "some-object", - G_TYPE_OBJECT, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_FLAGS_PROPERTY, - g_param_spec_flags ("some-flags", - "some-flags", - "some-flags", - GI_MARSHALLING_TESTS_TYPE_FLAGS, - GI_MARSHALLING_TESTS_FLAGS_VALUE1, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_ENUM_PROPERTY, - g_param_spec_enum ("some-enum", - "some-enum", - "some-enum", - GI_MARSHALLING_TESTS_TYPE_GENUM, - GI_MARSHALLING_TESTS_GENUM_VALUE1, - G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_BYTE_ARRAY_PROPERTY, - g_param_spec_boxed ("some-byte-array", - "some-byte-array", - "some-byte-array", - G_TYPE_BYTE_ARRAY, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (object_class, SOME_READONLY_PROPERTY, - g_param_spec_int ("some-readonly", - "some-readonly", - "some-readonly", - G_MININT, G_MAXINT, 0, - G_PARAM_READABLE)); +/** + * gi_marshalling_tests_properties_accessors_object_get_readonly: (get-property some-readonly) + * @self: + */ +gint +gi_marshalling_tests_properties_accessors_object_get_readonly (GIMarshallingTestsPropertiesAccessorsObject *self G_GNUC_UNUSED) +{ + return 42; } -GIMarshallingTestsPropertiesObject * -gi_marshalling_tests_properties_object_new (void) +/** + * gi_marshalling_tests_properties_accessors_object_get_deprecated_int: (get-property some-deprecated-int) + * @self: + */ +gint +gi_marshalling_tests_properties_accessors_object_get_deprecated_int (GIMarshallingTestsPropertiesAccessorsObject *self) { - return g_object_new (GI_MARSHALLING_TESTS_TYPE_PROPERTIES_OBJECT, NULL); + return self->some_deprecated_int; } G_DEFINE_TYPE (GIMarshallingTestsSignalsObject, gi_marshalling_tests_signals_object, G_TYPE_OBJECT); diff --git a/subprojects/gobject-introspection-tests/gimarshallingtests.h b/subprojects/gobject-introspection-tests/gimarshallingtests.h index 6640ca24c..db01c8fc7 100644 --- a/subprojects/gobject-introspection-tests/gimarshallingtests.h +++ b/subprojects/gobject-introspection-tests/gimarshallingtests.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2010-2012 Collabora, Ltd. SPDX-FileCopyrightText: 2010 Johan Dahlin SPDX-FileCopyrightText: 2010 Sugar Labs @@ -24,7 +25,7 @@ SPDX-FileCopyrightText: 2014 RIFT.io, Inc. SPDX-FileCopyrightText: 2014 SuSE SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. SPDX-FileCopyrightText: 2017 Christoph Reiter -SPDX-FileCopyrightText: 2016-2017, 2023 Philip Chimento +SPDX-FileCopyrightText: 2016-2017, 2023, 2025 Philip Chimento SPDX-FileCopyrightText: 2018 Tomasz Miąsko SPDX-FileCopyrightText: 2019 Stéphane Seng SPDX-FileCopyrightText: 2020-2023 Marco Trevisan @@ -34,6 +35,7 @@ SPDX-FileCopyrightText: 2021 Carlos Garnacho #pragma once +#include /* size_t */ #include /* off_t, time_t */ #include @@ -56,6 +58,9 @@ typedef struct _GIMarshallingTestsBoxedStruct GIMarshallingTestsBoxedStruct; 0x20, 0x2665, 0x20, 0x75, 0x74, \ 0x66, 0x38 } +GI_TEST_EXTERN +void gi_marshalling_tests_cleanup_unaligned_buffer (void); + /* Booleans */ GI_TEST_EXTERN @@ -784,6 +789,9 @@ const gint *gi_marshalling_tests_array_fixed_int_return (void); GI_TEST_EXTERN const gshort *gi_marshalling_tests_array_fixed_short_return (void); +GI_TEST_EXTERN +const guint8 *gi_marshalling_tests_array_fixed_return_unaligned (void); + GI_TEST_EXTERN void gi_marshalling_tests_array_fixed_int_in (const gint *ints); @@ -799,6 +807,9 @@ void gi_marshalling_tests_array_fixed_out (gint **ints); GI_TEST_EXTERN gboolean gi_marshalling_tests_array_fixed_out_uninitialized (gint **v G_GNUC_UNUSED); +GI_TEST_EXTERN +void gi_marshalling_tests_array_fixed_out_unaligned (const guint8 **v); + GI_TEST_EXTERN void gi_marshalling_tests_array_fixed_out_struct (GIMarshallingTestsSimpleStruct **structs); @@ -819,6 +830,9 @@ const gint *gi_marshalling_tests_array_return (gint *length); GI_TEST_EXTERN const gint *gi_marshalling_tests_array_return_etc (gint first, gint *length, gint last, gint *sum); +GI_TEST_EXTERN +const guint8 *gi_marshalling_tests_array_return_unaligned (gsize *len); + GI_TEST_EXTERN void gi_marshalling_tests_array_in (const gint *ints, gint length); @@ -879,6 +893,9 @@ void gi_marshalling_tests_array_out (gint **ints, gint *length); GI_TEST_EXTERN gboolean gi_marshalling_tests_array_out_uninitialized (gint **v G_GNUC_UNUSED, gint *length G_GNUC_UNUSED); +GI_TEST_EXTERN +void gi_marshalling_tests_array_out_unaligned (const guint8 **v, gsize *len); + GI_TEST_EXTERN void gi_marshalling_tests_array_out_etc (gint first, gint **ints, gint *length, gint last, gint *sum); @@ -908,9 +925,15 @@ gchar **gi_marshalling_tests_array_zero_terminated_return_null (void); GI_TEST_EXTERN GIMarshallingTestsBoxedStruct **gi_marshalling_tests_array_zero_terminated_return_struct (void); +GI_TEST_EXTERN +GIMarshallingTestsBoxedStruct *gi_marshalling_tests_array_zero_terminated_return_sequential_struct (void); + GI_TEST_EXTERN gunichar *gi_marshalling_tests_array_zero_terminated_return_unichar (void); +GI_TEST_EXTERN +const guint8 *gi_marshalling_tests_array_zero_terminated_return_unaligned (void); + GI_TEST_EXTERN void gi_marshalling_tests_array_zero_terminated_in (gchar **utf8s); @@ -920,6 +943,9 @@ void gi_marshalling_tests_array_zero_terminated_out (const gchar ***utf8s); GI_TEST_EXTERN gboolean gi_marshalling_tests_array_zero_terminated_out_uninitialized (const gchar ***v G_GNUC_UNUSED); +GI_TEST_EXTERN +void gi_marshalling_tests_array_zero_terminated_out_unaligned (const guint8 **v); + GI_TEST_EXTERN void gi_marshalling_tests_array_zero_terminated_inout (const gchar ***utf8s); @@ -932,6 +958,118 @@ GVariant **gi_marshalling_tests_array_gvariant_container_in (GVariant **variants GI_TEST_EXTERN GVariant **gi_marshalling_tests_array_gvariant_full_in (GVariant **variants); +/* Full complement of array-of-UTF8 in/out tests for fixed-length array, + * explicit-length array, zero-terminated array. GArray, GPtrArray, GList, and + * GSList are already covered below. */ + +GI_TEST_EXTERN +const gchar *const *gi_marshalling_tests_length_array_utf8_none_return (size_t *out_length); + +GI_TEST_EXTERN +const gchar **gi_marshalling_tests_length_array_utf8_container_return (size_t *out_length); + +GI_TEST_EXTERN +gchar **gi_marshalling_tests_length_array_utf8_full_return (size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_none_in (const gchar *const *array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_container_in (const gchar **array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_full_in (gchar **array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_none_out (const gchar *const **array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_container_out (const gchar ***array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_full_out (gchar ***array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_none_inout (const gchar *const **array_inout, size_t *inout_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_container_inout (const gchar ***array_inout, size_t *inout_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_utf8_full_inout (gchar ***array_inout, size_t *inout_length); + +GI_TEST_EXTERN +const gchar *const *gi_marshalling_tests_zero_terminated_array_utf8_none_return (void); + +GI_TEST_EXTERN +const gchar **gi_marshalling_tests_zero_terminated_array_utf8_container_return (void); + +GI_TEST_EXTERN +gchar **gi_marshalling_tests_zero_terminated_array_utf8_full_return (void); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_none_in (const gchar *const *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_container_in (const gchar **array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_full_in (gchar **array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_none_out (const gchar *const **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_container_out (const gchar ***array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_full_out (gchar ***array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_none_inout (const gchar *const **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_container_inout (const gchar ***array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_utf8_full_inout (gchar ***array_inout); + +GI_TEST_EXTERN +const gchar *const *gi_marshalling_tests_fixed_array_utf8_none_return (void); + +GI_TEST_EXTERN +const gchar **gi_marshalling_tests_fixed_array_utf8_container_return (void); + +GI_TEST_EXTERN +gchar **gi_marshalling_tests_fixed_array_utf8_full_return (void); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_none_in (const gchar *const *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_container_in (const gchar **array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_full_in (gchar **array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_none_out (const gchar *const **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_container_out (const gchar ***array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_full_out (gchar ***array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_none_inout (const gchar *const **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_container_inout (const gchar ***array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_utf8_full_inout (gchar ***array_inout); + /* GArray */ GI_TEST_EXTERN @@ -961,6 +1099,12 @@ void gi_marshalling_tests_garray_uint64_none_in (GArray *array_); GI_TEST_EXTERN void gi_marshalling_tests_garray_utf8_none_in (GArray *array_); +GI_TEST_EXTERN +void gi_marshalling_tests_garray_utf8_container_in (GArray *array_); + +GI_TEST_EXTERN +void gi_marshalling_tests_garray_utf8_full_in (GArray *array_); + GI_TEST_EXTERN void gi_marshalling_tests_garray_utf8_none_out (GArray **array_); @@ -1014,6 +1158,12 @@ GPtrArray *gi_marshalling_tests_gptrarray_boxed_struct_full_return (void); GI_TEST_EXTERN void gi_marshalling_tests_gptrarray_utf8_none_in (GPtrArray *parray_); +GI_TEST_EXTERN +void gi_marshalling_tests_gptrarray_utf8_container_in (GPtrArray *parray_); + +GI_TEST_EXTERN +void gi_marshalling_tests_gptrarray_utf8_full_in (GPtrArray *parray_); + GI_TEST_EXTERN void gi_marshalling_tests_gptrarray_utf8_none_out (GPtrArray **parray_); @@ -1049,6 +1199,12 @@ GByteArray *gi_marshalling_tests_bytearray_full_return (void); GI_TEST_EXTERN void gi_marshalling_tests_bytearray_none_in (GByteArray *v); +GI_TEST_EXTERN +void gi_marshalling_tests_bytearray_full_out (GByteArray **v); + +GI_TEST_EXTERN +void gi_marshalling_tests_bytearray_full_inout (GByteArray **v); + /* GBytes */ GI_TEST_EXTERN @@ -1074,6 +1230,116 @@ gboolean gi_marshalling_tests_gstrv_out_uninitialized (GStrv *v G_GNUC_UNUSED); GI_TEST_EXTERN void gi_marshalling_tests_gstrv_inout (GStrv *g_strv); +/* Array of GStrv's */ + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_length_array_of_gstrv_transfer_full_return (size_t *out_length); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_length_array_of_gstrv_transfer_container_return (size_t *out_length); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_length_array_of_gstrv_transfer_none_return (size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_none_in (GStrv *array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_container_in (GStrv *array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_full_in (GStrv *array, size_t length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_none_out (GStrv **array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_container_out (GStrv **array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_full_out (GStrv **array_out, size_t *out_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_none_inout (GStrv **array_inout, size_t *inout_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_container_inout (GStrv **array_inout, size_t *inout_length); + +GI_TEST_EXTERN +void gi_marshalling_tests_length_array_of_gstrv_transfer_full_inout (GStrv **array_inout, size_t *inout_length); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_return (void); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_return (void); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_return (void); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_none_inout (GStrv **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_container_inout (GStrv **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_zero_terminated_array_of_gstrv_transfer_full_inout (GStrv **array_inout); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_return (void); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_return (void); + +GI_TEST_EXTERN +GStrv *gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_return (void); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_in (GStrv *array); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_out (GStrv **array_out); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_none_inout (GStrv **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_container_inout (GStrv **array_inout); + +GI_TEST_EXTERN +void gi_marshalling_tests_fixed_array_of_gstrv_transfer_full_inout (GStrv **array_inout); + /* GList */ GI_TEST_EXTERN @@ -1100,6 +1366,12 @@ void gi_marshalling_tests_glist_uint32_none_in (GList *list); GI_TEST_EXTERN void gi_marshalling_tests_glist_utf8_none_in (GList *list); +GI_TEST_EXTERN +void gi_marshalling_tests_glist_utf8_container_in (GList *list); + +GI_TEST_EXTERN +void gi_marshalling_tests_glist_utf8_full_in (GList *list); + GI_TEST_EXTERN void gi_marshalling_tests_glist_utf8_none_out (GList **list); @@ -1147,6 +1419,12 @@ void gi_marshalling_tests_gslist_int_none_in (GSList *list); GI_TEST_EXTERN void gi_marshalling_tests_gslist_utf8_none_in (GSList *list); +GI_TEST_EXTERN +void gi_marshalling_tests_gslist_utf8_container_in (GSList *list); + +GI_TEST_EXTERN +void gi_marshalling_tests_gslist_utf8_full_in (GSList *list); + GI_TEST_EXTERN void gi_marshalling_tests_gslist_utf8_none_out (GSList **list); @@ -1304,6 +1582,9 @@ GValue *gi_marshalling_tests_gvalue_flat_array_round_trip (const GValue one, const GValue two, const GValue three); +GI_TEST_EXTERN +void gi_marshalling_tests_gvalue_float (const GValue *float_value, const GValue *double_value); + /* GClosure */ GI_TEST_EXTERN @@ -1758,6 +2039,26 @@ struct _GIMarshallingTestsObjectClass * @flags: (out): */ void (*vfunc_out_flags) (GIMarshallingTestsObject *self, GIMarshallingTestsFlags *flags); + + /** + * GIMarshallingTestsObjectClass::vfunc_static_name: + */ + gchar *(*vfunc_static_name) (void); + + /** + * GIMarshallingTestsObjectClass::vfunc_static_create_new: + * @int_: + * + * Returns: (transfer full): + */ + GIMarshallingTestsObject *(*vfunc_static_create_new) (gint int_); + + /** + * GIMarshallingTestsObjectClass::vfunc_static_create_new_out: + * @out: (out): + * @int_: + */ + void (*vfunc_static_create_new_out) (GIMarshallingTestsObject **out, gint int_); }; struct _GIMarshallingTestsObject @@ -1866,6 +2167,18 @@ GIMarshallingTestsFlags gi_marshalling_tests_object_vfunc_return_flags (GIMarsha GI_TEST_EXTERN void gi_marshalling_tests_object_vfunc_out_flags (GIMarshallingTestsObject *self, GIMarshallingTestsFlags *flags); +GI_TEST_EXTERN +gchar *gi_marshalling_tests_object_vfunc_static_name (void); + +GI_TEST_EXTERN +gchar *gi_marshalling_tests_object_vfunc_static_typed_name (GType gtype); + +GI_TEST_EXTERN +GIMarshallingTestsObject *gi_marshalling_tests_object_vfunc_static_create_new (GType gtype, gint int_); + +GI_TEST_EXTERN +void gi_marshalling_tests_object_vfunc_static_create_new_out (GIMarshallingTestsObject **out, GType gtype, gint int_); + GI_TEST_EXTERN void gi_marshalling_tests_object_get_ref_info_for_vfunc_return_object_transfer_none (GIMarshallingTestsObject *self, guint *ref_count, gboolean *is_floating); @@ -2233,6 +2546,7 @@ struct _GIMarshallingTestsPropertiesObject gchar some_char; guchar some_uchar; gint some_int; + gint some_deprecated_int; guint some_uint; glong some_long; gulong some_ulong; @@ -2263,6 +2577,187 @@ GType gi_marshalling_tests_properties_object_get_type (void) G_GNUC_CONST; GI_TEST_EXTERN GIMarshallingTestsPropertiesObject *gi_marshalling_tests_properties_object_new (void); +/* Object with properties setters/getters */ + +#define GI_MARSHALLING_TESTS_TYPE_ACCESSORS_TESTS_PROPERTIES_OBJECT \ + (gi_marshalling_tests_properties_accessors_object_get_type ()) + +GI_TEST_EXTERN +G_DECLARE_FINAL_TYPE (GIMarshallingTestsPropertiesAccessorsObject, + gi_marshalling_tests_properties_accessors_object, + GI_MARSHALLING_TESTS, + PROPERTIES_ACCESSORS_OBJECT, + GObject); + +struct _GIMarshallingTestsPropertiesAccessorsObject +{ + GObject parent_instance; + + gboolean some_boolean; + gchar some_char; + guchar some_uchar; + gint some_int; + gint some_deprecated_int; + guint some_uint; + glong some_long; + gulong some_ulong; + gint64 some_int64; + guint64 some_uint64; + gfloat some_float; + gdouble some_double; + gchar *some_string; + gchar **some_strv; + GIMarshallingTestsBoxedStruct *some_boxed_struct; + GList *some_boxed_glist; + GValue *some_gvalue; + GVariant *some_variant; + GObject *some_object; + GIMarshallingTestsFlags some_flags; + GIMarshallingTestsGEnum some_enum; + GByteArray *some_byte_array; +}; + +GI_TEST_EXTERN +GIMarshallingTestsPropertiesAccessorsObject * +gi_marshalling_tests_properties_accessors_object_new (void); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_boolean (GIMarshallingTestsPropertiesAccessorsObject *self, gboolean some_boolean); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_char (GIMarshallingTestsPropertiesAccessorsObject *self, gchar some_char); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_uchar (GIMarshallingTestsPropertiesAccessorsObject *self, guchar some_uchar); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_int (GIMarshallingTestsPropertiesAccessorsObject *self, gint some_int); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_uint (GIMarshallingTestsPropertiesAccessorsObject *self, guint some_uint); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_long (GIMarshallingTestsPropertiesAccessorsObject *self, glong some_long); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_ulong (GIMarshallingTestsPropertiesAccessorsObject *self, gulong some_ulong); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_int64 (GIMarshallingTestsPropertiesAccessorsObject *self, gint64 some_int64); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_uint64 (GIMarshallingTestsPropertiesAccessorsObject *self, guint64 some_uint64); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_float (GIMarshallingTestsPropertiesAccessorsObject *self, gfloat some_float); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_double (GIMarshallingTestsPropertiesAccessorsObject *self, gdouble some_double); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_string (GIMarshallingTestsPropertiesAccessorsObject *self, gchar *some_string); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_strv (GIMarshallingTestsPropertiesAccessorsObject *self, GStrv some_strv); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_boxed_struct (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsBoxedStruct *some_boxed_struct); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_boxed_glist (GIMarshallingTestsPropertiesAccessorsObject *self, GList *some_boxed_glist); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_gvalue (GIMarshallingTestsPropertiesAccessorsObject *self, GValue *some_gvalue); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_variant (GIMarshallingTestsPropertiesAccessorsObject *self, GVariant *some_variant); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_object (GIMarshallingTestsPropertiesAccessorsObject *self, GObject *some_object); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_flags (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsFlags some_flags); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_enum (GIMarshallingTestsPropertiesAccessorsObject *self, GIMarshallingTestsGEnum some_enum); + +GI_TEST_EXTERN +void gi_marshalling_tests_properties_accessors_object_set_byte_array (GIMarshallingTestsPropertiesAccessorsObject *self, GByteArray *some_byte_array); + +GI_TEST_EXTERN +gboolean gi_marshalling_tests_properties_accessors_object_get_boolean (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gchar gi_marshalling_tests_properties_accessors_object_get_char (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +guchar gi_marshalling_tests_properties_accessors_object_get_uchar (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gint gi_marshalling_tests_properties_accessors_object_get_int (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +guint gi_marshalling_tests_properties_accessors_object_get_uint (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +glong gi_marshalling_tests_properties_accessors_object_get_long (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gulong gi_marshalling_tests_properties_accessors_object_get_ulong (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gint64 gi_marshalling_tests_properties_accessors_object_get_int64 (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +guint64 gi_marshalling_tests_properties_accessors_object_get_uint64 (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gfloat gi_marshalling_tests_properties_accessors_object_get_float (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gdouble gi_marshalling_tests_properties_accessors_object_get_double (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +const gchar *gi_marshalling_tests_properties_accessors_object_get_string (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gchar **gi_marshalling_tests_properties_accessors_object_get_strv (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GIMarshallingTestsBoxedStruct *gi_marshalling_tests_properties_accessors_object_get_boxed_struct (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GList *gi_marshalling_tests_properties_accessors_object_get_boxed_glist (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GValue *gi_marshalling_tests_properties_accessors_object_get_gvalue (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GVariant *gi_marshalling_tests_properties_accessors_object_get_variant (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GObject *gi_marshalling_tests_properties_accessors_object_get_object (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GIMarshallingTestsFlags gi_marshalling_tests_properties_accessors_object_get_flags (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GIMarshallingTestsGEnum gi_marshalling_tests_properties_accessors_object_get_enum (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +GByteArray *gi_marshalling_tests_properties_accessors_object_get_byte_array (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +gint gi_marshalling_tests_properties_accessors_object_get_readonly (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +G_DEPRECATED +gint gi_marshalling_tests_properties_accessors_object_get_deprecated_int (GIMarshallingTestsPropertiesAccessorsObject *self); + +GI_TEST_EXTERN +G_DEPRECATED +void gi_marshalling_tests_properties_accessors_object_set_deprecated_int (GIMarshallingTestsPropertiesAccessorsObject *self, gint some_deprecated_int); + /* Signals object */ #define GI_MARSHALLING_TESTS_TYPE_SIGNALS_OBJECT (gi_marshalling_tests_signals_object_get_type ()) diff --git a/subprojects/gobject-introspection-tests/gimarshallingtestsextra.c b/subprojects/gobject-introspection-tests/gimarshallingtestsextra.c new file mode 100644 index 000000000..fac6dcd3e --- /dev/null +++ b/subprojects/gobject-introspection-tests/gimarshallingtestsextra.c @@ -0,0 +1,194 @@ +/* gimarshallingtestsextra.c + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2016 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include + +#include "gimarshallingtestsextra.h" + +void +gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1) +{ + GError *error, *error1; + + g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v)), ==, + g_type_name (G_TYPE_ERROR)); + g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v1)), ==, + g_type_name (G_TYPE_ERROR)); + + error = (GError *) g_value_get_boxed (v); + error1 = (GError *) g_value_get_boxed (v1); + + g_assert_cmpint (error->domain, ==, error1->domain); + g_assert_cmpint (error->code, ==, error1->code); + g_assert_cmpstr (error->message, ==, error1->message); +} + +/** + * gi_marshalling_tests_nullable_gerror: + * @error: (in) (nullable) (transfer full): + * an optional #GError. + * + * So we've got an API which takes a `GError *` as `(in)` argument, + * and we want this to be optional, so we added `(nullable)`. + * + * Returns: 1 or 0, depending if the error was set. + */ +gboolean +gi_marshalling_tests_nullable_gerror (GError *error) +{ + gboolean retval = error ? 1 : 0; + g_clear_error(&error); + return retval; +} + +/** + * gi_marshalling_tests_ghashtable_enum_none_in: + * @hash_table: (element-type gint GIMarshallingTestsExtraEnum) (transfer none): + */ +void +gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table) +{ + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (1))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (2))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (3))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3); +} + +/** + * gi_marshalling_tests_ghashtable_enum_none_return: + * + * Returns: (element-type gint GIMarshallingTestsExtraEnum) (transfer none): + */ +GHashTable * +gi_marshalling_tests_ghashtable_enum_none_return (void) +{ + static GHashTable *hash_table = NULL; + + if (hash_table == NULL) + { + hash_table = g_hash_table_new (NULL, NULL); + g_hash_table_insert (hash_table, GINT_TO_POINTER (1), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (2), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (3), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3)); + } + + return hash_table; +} + +/** + * gi_marshalling_tests_filename_copy: + * @path_in: (type filename) (nullable) + * + * Returns: (type filename) (nullable) + */ +gchar * +gi_marshalling_tests_filename_copy (gchar *path_in) +{ + return g_strdup (path_in); +} + +/** + * gi_marshalling_tests_filename_to_glib_repr: + * @path_in: (type filename) (nullable) + * + * Returns: (array length=len) (element-type guint8) + */ +gchar * +gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len) +{ + *len = strlen (path_in); + return g_strdup (path_in); +} + +/** + * gi_marshalling_tests_filename_exists: + * @path: (type filename) + */ +gboolean +gi_marshalling_tests_filename_exists (gchar *path) +{ + return g_file_test (path, G_FILE_TEST_EXISTS); +} + +/** + * gi_marshalling_tests_enum_array_return_type: + * @n_members: (out): The number of members + * + * Returns: (array length=n_members) (transfer full): An array of enum values + */ +GIMarshallingTestsExtraEnum * +gi_marshalling_tests_enum_array_return_type (gsize *n_members) +{ + GIMarshallingTestsExtraEnum *res = g_new0 (GIMarshallingTestsExtraEnum, 3); + + *n_members = 3; + + res[0] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1; + res[1] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2; + res[2] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3; + + return res; +} + +GType +gi_marshalling_tests_extra_flags_get_type (void) +{ + static GType type = 0; + if (G_UNLIKELY (type == 0)) + { + static const GFlagsValue values[] = { + { GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1, + "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1", "value1" }, + { GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2, + "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2", "value2" }, + { 0, NULL, NULL } + }; + type = g_flags_register_static ( + g_intern_static_string ("GIMarshallingTestsExtraFlags"), values); + } + + return type; +} + +/** + * gi_marshalling_tests_extra_flags_large_in: + */ +void +gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value) +{ + g_assert_cmpint (value, ==, GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2); +} + +/** + * gi_marshalling_tests_extra_utf8_full_return_invalid: + */ +gchar * +gi_marshalling_tests_extra_utf8_full_return_invalid (void) +{ + return g_strdup ("invalid utf8 \xff\xfe"); +} + +/** + * gi_marshalling_tests_extra_utf8_full_out_invalid: + * @utf8: (out) (transfer full): + */ +void +gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8) +{ + *utf8 = g_strdup ("invalid utf8 \xff\xfe"); +} diff --git a/subprojects/gobject-introspection-tests/gimarshallingtestsextra.h b/subprojects/gobject-introspection-tests/gimarshallingtestsextra.h new file mode 100644 index 000000000..a0873f342 --- /dev/null +++ b/subprojects/gobject-introspection-tests/gimarshallingtestsextra.h @@ -0,0 +1,70 @@ +/* gimarshallingtestsextra.h + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2016 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include +#include + +#include "gitestmacros.h" + +typedef enum +{ + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1, + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2, + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3 = 42 +} GIMarshallingTestsExtraEnum; + +typedef enum +{ + GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1 = 0, + GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2 = (gint) (1 << 31), +} GIMarshallingTestsExtraFlags; + +GI_TEST_EXTERN +GType gi_marshalling_tests_extra_flags_get_type (void) G_GNUC_CONST; +#define GI_MARSHALLING_TESTS_TYPE_EXTRA_FLAGS (gi_marshalling_tests_extra_flags_get_type ()) + +GI_TEST_EXTERN +void gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1); +GI_TEST_EXTERN +gboolean gi_marshalling_tests_nullable_gerror (GError *error); + +GI_TEST_EXTERN +void gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table); +GI_TEST_EXTERN +GHashTable *gi_marshalling_tests_ghashtable_enum_none_return (void); + +GI_TEST_EXTERN +gchar *gi_marshalling_tests_filename_copy (gchar *path_in); +GI_TEST_EXTERN +gboolean gi_marshalling_tests_filename_exists (gchar *path); +GI_TEST_EXTERN +gchar *gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len); + +GI_TEST_EXTERN +GIMarshallingTestsExtraEnum *gi_marshalling_tests_enum_array_return_type (gsize *n_members); + +GI_TEST_EXTERN +void gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value); + +GI_TEST_EXTERN +gchar *gi_marshalling_tests_extra_utf8_full_return_invalid (void); +GI_TEST_EXTERN +void gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8); diff --git a/subprojects/gobject-introspection-tests/gitestmacros.h b/subprojects/gobject-introspection-tests/gitestmacros.h index 033fa0f93..d49f4fad5 100644 --- a/subprojects/gobject-introspection-tests/gitestmacros.h +++ b/subprojects/gobject-introspection-tests/gitestmacros.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2014 Chun-wei Fan */ diff --git a/subprojects/gobject-introspection-tests/meson.build b/subprojects/gobject-introspection-tests/meson.build index d42d51b9c..e839550c8 100644 --- a/subprojects/gobject-introspection-tests/meson.build +++ b/subprojects/gobject-introspection-tests/meson.build @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Philip Chimento + project( 'gobject-introspection-tests', 'c', version: 'unversioned', meson_version: '>= 0.61', @@ -13,7 +16,7 @@ build_cairo = get_option('cairo') install_dir = get_option('install_dir') install = (install_dir != '') -glib_version = '>= 2.70' +glib_version = '>= 2.66.8' glib_dep = dependency('glib-2.0', version: glib_version) gobject_dep = dependency('gobject-2.0', version: glib_version) gio_dep = dependency('gio-2.0', version: glib_version) @@ -65,10 +68,17 @@ regress_sources = [ 'foo.h', 'regress.c', 'regress.h', + 'regressextra.c', + 'regressextra.h', ] regress_unix_sources = ['regress-unix.c', 'regress-unix.h'] warnlib_sources = ['warnlib.c', 'warnlib.h'] -gimarshallingtests_sources = ['gimarshallingtests.c', 'gimarshallingtests.h'] +gimarshallingtests_sources = [ + 'gimarshallingtests.c', + 'gimarshallingtests.h', + 'gimarshallingtestsextra.c', + 'gimarshallingtestsextra.h', +] libutility = library( 'utility', diff --git a/subprojects/gobject-introspection-tests/meson_options.txt b/subprojects/gobject-introspection-tests/meson_options.txt index 5b67b28b1..7aad216c0 100644 --- a/subprojects/gobject-introspection-tests/meson_options.txt +++ b/subprojects/gobject-introspection-tests/meson_options.txt @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento option('cairo', type: 'boolean', value: true, description: 'Cairo support') diff --git a/subprojects/gobject-introspection-tests/regress-unix.c b/subprojects/gobject-introspection-tests/regress-unix.c index ffdc0892b..3cfd6d00d 100644 --- a/subprojects/gobject-introspection-tests/regress-unix.c +++ b/subprojects/gobject-introspection-tests/regress-unix.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: MIT SPDX-FileCopyrightText: 2024 Simon McVittie */ diff --git a/subprojects/gobject-introspection-tests/regress-unix.h b/subprojects/gobject-introspection-tests/regress-unix.h index aa2cd14e0..768713e4d 100644 --- a/subprojects/gobject-introspection-tests/regress-unix.h +++ b/subprojects/gobject-introspection-tests/regress-unix.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: MIT SPDX-FileCopyrightText: 2024 Simon McVittie */ diff --git a/subprojects/gobject-introspection-tests/regress.c b/subprojects/gobject-introspection-tests/regress.c index 0cdaf909a..122313204 100644 --- a/subprojects/gobject-introspection-tests/regress.c +++ b/subprojects/gobject-introspection-tests/regress.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008-2015 Colin Walters SPDX-FileCopyrightText: 2008 Johan Bilien SPDX-FileCopyrightText: 2008 Lucas Almeida Rocha @@ -34,7 +35,7 @@ SPDX-FileCopyrightText: 2015, 2018 Christoph Reiter SPDX-FileCopyrightText: 2015 Debarshi Ray SPDX-FileCopyrightText: 2015 Ben Iofel SPDX-FileCopyrightText: 2016 Lionel Landwerlin -SPDX-FileCopyrightText: 2016-2019 Philip Chimento +SPDX-FileCopyrightText: 2016-2019, 2024 Philip Chimento SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. SPDX-FileCopyrightText: 2017 Rico Tzschichholz SPDX-FileCopyrightText: 2018-2019 Tomasz Miąsko @@ -57,20 +58,6 @@ SPDX-FileCopyrightText: 2024 Simon McVittie #include "regress.h" -static gboolean abort_on_error = TRUE; - -#define ASSERT_VALUE(condition) \ - if (abort_on_error) \ - g_assert (condition); \ - else \ - g_warn_if_fail (condition); - -void -regress_set_abort_on_error (gboolean in) -{ - abort_on_error = in; -} - /* return annotations */ /** @@ -105,14 +92,14 @@ regress_test_boolean (gboolean in) gboolean regress_test_boolean_true (gboolean in) { - ASSERT_VALUE (in == TRUE); + g_assert_true (in); return in; } gboolean regress_test_boolean_false (gboolean in) { - ASSERT_VALUE (in == FALSE); + g_assert_false (in); return in; } @@ -2105,6 +2092,12 @@ regress_test_boxed_c_new (void) return boxed; } +gboolean +regress_test_boxed_c_name_conflict (RegressTestBoxedC *boxed) +{ + return boxed->name_conflict; +} + static RegressTestBoxedC * regress_test_boxed_c_ref (RegressTestBoxedC *boxed) { @@ -3004,6 +2997,7 @@ regress_test_obj_emit_sig_with_obj_full (RegressTestObj *obj) 0, g_steal_pointer (&obj_param)); } +#if GLIB_CHECK_VERSION(2, 68, 0) void regress_test_obj_emit_sig_with_gstrv_full (RegressTestObj *obj) { @@ -3014,6 +3008,7 @@ regress_test_obj_emit_sig_with_gstrv_full (RegressTestObj *obj) 0, g_strv_builder_end (builder)); g_strv_builder_unref (builder); } +#endif #ifndef GI_TEST_DISABLE_CAIRO void @@ -4319,16 +4314,45 @@ regress_test_async_ready_callback (GAsyncReadyCallback callback) G_GNUC_END_IGNORE_DEPRECATIONS } +static GSList *async_function_tasks; + /** * regress_test_function_async: * */ void regress_test_function_async (int io_priority G_GNUC_UNUSED, - GCancellable *cancellable G_GNUC_UNUSED, - GAsyncReadyCallback callback G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED) + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + GTask *task = g_task_new (NULL, cancellable, callback, user_data); + + async_function_tasks = g_slist_prepend (async_function_tasks, task); +} + +/** + * regress_test_function_thaw_async: + * + * Returns: the number of callbacks that were thawed. + */ +int +regress_test_function_thaw_async (void) +{ + int retval = 0; + GSList *node; + + for (node = async_function_tasks; node != NULL; node = node->next) + { + GTask *task = node->data; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + retval++; + } + + g_slist_free (async_function_tasks); + async_function_tasks = NULL; + return retval; } /** @@ -4336,9 +4360,9 @@ regress_test_function_async (int io_priority G_GNUC_UNUSED, * */ gboolean -regress_test_function_finish (GAsyncResult *res G_GNUC_UNUSED, GError **error G_GNUC_UNUSED) +regress_test_function_finish (GAsyncResult *res, GError **error) { - return TRUE; + return g_task_propagate_boolean (G_TASK(res), error); } /** @@ -4397,16 +4421,46 @@ regress_test_obj_new_callback (RegressTestCallbackUserData callback, gpointer us return g_object_new (REGRESS_TEST_TYPE_OBJ, NULL); } +static GSList *async_constructor_tasks; + /** * regress_test_obj_new_async: * */ void regress_test_obj_new_async (const char *x G_GNUC_UNUSED, - GCancellable *cancellable G_GNUC_UNUSED, - GAsyncReadyCallback callback G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED) + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + GTask *task = g_task_new (NULL, cancellable, callback, user_data); + + async_constructor_tasks = g_slist_prepend (async_constructor_tasks, task); +} + +/** + * regress_test_obj_constructor_thaw_async: + * + * Returns: the number of callbacks that were thawed. + */ +int +regress_test_obj_constructor_thaw_async (void) +{ + int retval = 0; + GSList *node; + + for (node = async_constructor_tasks; node != NULL; node = node->next) + { + GTask *task = node->data; + RegressTestObj *obj = g_object_new (REGRESS_TEST_TYPE_OBJ, NULL); + g_task_return_pointer (task, obj, g_object_unref); + g_object_unref (task); + retval++; + } + + g_slist_free (async_constructor_tasks); + async_constructor_tasks = NULL; + return retval; } /** @@ -4414,10 +4468,9 @@ regress_test_obj_new_async (const char *x G_GNUC_UNUSED, * */ RegressTestObj * -regress_test_obj_new_finish (GAsyncResult *res G_GNUC_UNUSED, - GError **error G_GNUC_UNUSED) +regress_test_obj_new_finish (GAsyncResult *res, GError **error) { - return g_object_new (REGRESS_TEST_TYPE_OBJ, NULL); + return g_task_propagate_pointer (G_TASK (res), error); } /** @@ -5066,17 +5119,47 @@ regress_test_array_struct_in_none (RegressTestStructA *arr, gsize len) g_assert_cmpint (arr[2].some_int, ==, 303); } +static GSList *async_method_tasks; + /** * regress_test_obj_function_async: * */ void -regress_test_obj_function_async (RegressTestObj *self G_GNUC_UNUSED, +regress_test_obj_function_async (RegressTestObj *self, int io_priority G_GNUC_UNUSED, - GCancellable *cancellable G_GNUC_UNUSED, - GAsyncReadyCallback callback G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED) + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = g_task_new (self, cancellable, callback, user_data); + + async_method_tasks = g_slist_prepend (async_method_tasks, task); +} + +/** + * regress_test_obj_function_thaw_async: + * @self: + * + * Returns: the number of callbacks that were thawed. + */ +int +regress_test_obj_function_thaw_async (RegressTestObj *self G_GNUC_UNUSED) { + int retval = 0; + GSList *node; + + for (node = async_method_tasks; node != NULL; node = node->next) + { + GTask *task = node->data; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + retval++; + } + + g_slist_free (async_method_tasks); + async_method_tasks = NULL; + return retval; } /** @@ -5084,9 +5167,9 @@ regress_test_obj_function_async (RegressTestObj *self G_GNUC_UNUSED, * */ gboolean -regress_test_obj_function_finish (RegressTestObj *self G_GNUC_UNUSED, GAsyncResult *res G_GNUC_UNUSED, GError **error G_GNUC_UNUSED) +regress_test_obj_function_finish (RegressTestObj *self G_GNUC_UNUSED, GAsyncResult *res, GError **error) { - return TRUE; + return g_task_propagate_boolean (G_TASK(res), error); } /** @@ -5098,3 +5181,100 @@ regress_test_obj_function_sync (RegressTestObj *self G_GNUC_UNUSED, int io_prior { return TRUE; } + + +/** + * regress_test_obj_function2: + * @self: a #RegressTestObj + * @io_priority: a number + * @cancellable: (nullable): a #GCancellable, or %NULL + * @test_cb: (nullable) (scope notified) (closure test_data): match reporting callback + * @test_data: user data for @test_cb + * @test_destroy: (destroy test_data): Destroy notify for @match_data + * @callback: the function to call on completion + * @user_data: the data to pass to @callback + * + * This is an example taken from FPrint: `fp_device_verify`. + */ +void +regress_test_obj_function2 (RegressTestObj *self, + int io_priority G_GNUC_UNUSED, + GCancellable *cancellable, + RegressTestCallbackUserData test_cb, + gpointer test_data, + GDestroyNotify test_destroy, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = g_task_new (self, cancellable, callback, user_data); + + async_method_tasks = g_slist_prepend (async_method_tasks, task); + + if (test_cb != NULL) + test_cb (test_data); + + if (test_destroy != NULL) + test_destroy (test_data); +} + +/** + * regress_test_obj_function2_finish: + * @self: A #TestObj + * @result: A #GAsyncResult + * @match: (out): An extra parameter, similar to `fp_device_verify_finish()` + * @some_obj: (out) (transfer full) (nullable): An output object, or %NULL to ignore + * @error: Return location for errors, or %NULL to ignore + * + * Finish function for `regress_test_obj_function2_async()`. + * + * Returns: (type void): %FALSE on error, %TRUE otherwise + */ +gboolean +regress_test_obj_function2_finish (RegressTestObj *self G_GNUC_UNUSED, + GAsyncResult *result, + gboolean *match, + GObject **some_obj, + GError **error) +{ + gboolean res = g_task_propagate_boolean (G_TASK (result), error); + + *match = res; + if (some_obj) + *some_obj = NULL; + + return res; +} + +/** + * regress_test_obj_function2_sync: + * + */ +gboolean +regress_test_obj_function2_sync (RegressTestObj *self G_GNUC_UNUSED, int io_priority G_GNUC_UNUSED) +{ + return TRUE; +} + +G_DEFINE_FLAGS_TYPE (RegressTestDiscontinuousFlags, regress_test_discontinuous_flags, G_DEFINE_ENUM_VALUE (REGRESS_TEST_DISCONTINUOUS_FLAG1, "discontinuous1"), G_DEFINE_ENUM_VALUE (REGRESS_TEST_DISCONTINUOUS_FLAG2, "discontinuous2")) + +/** + * regress_test_discontinuous_1_with_private_values: + * + * Returns: REGRESS_TEST_DISCONTINUOUS_FLAG1 with a private value lower than the smallest RegressTestDiscontinuousFlags enum value. + */ +RegressTestDiscontinuousFlags +regress_test_discontinuous_1_with_private_values (void) +{ + return 1 << 3 | REGRESS_TEST_DISCONTINUOUS_FLAG1; +} + +/** + * regress_test_discontinuous_2_with_private_values: + * + * Returns: REGRESS_TEST_DISCONTINUOUS_FLAG2 with a private value higher than the largest RegressTestDiscontinuousFlags enum value. + */ +RegressTestDiscontinuousFlags +regress_test_discontinuous_2_with_private_values (void) +{ + return 1 << 30 | REGRESS_TEST_DISCONTINUOUS_FLAG2; +} \ No newline at end of file diff --git a/subprojects/gobject-introspection-tests/regress.h b/subprojects/gobject-introspection-tests/regress.h index d7e96ab9b..e7c05e436 100644 --- a/subprojects/gobject-introspection-tests/regress.h +++ b/subprojects/gobject-introspection-tests/regress.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008-2013, 2015 Colin Walters SPDX-FileCopyrightText: 2008 Johan Bilien SPDX-FileCopyrightText: 2008-2010 Johan Dahlin @@ -40,7 +41,7 @@ SPDX-FileCopyrightText: 2015 Debarshi Ray SPDX-FileCopyrightText: 2016 Intel SPDX-FileCopyrightText: 2016 Lionel Landwerlin SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. -SPDX-FileCopyrightText: 2016-2019 Philip Chimento +SPDX-FileCopyrightText: 2016-2019, 2024 Philip Chimento SPDX-FileCopyrightText: 2017 Rico Tzschichholz SPDX-FileCopyrightText: 2018-2019 Tomasz Miąsko SPDX-FileCopyrightText: 2020 Centricular @@ -447,6 +448,24 @@ typedef enum REGRESS_TEST_FLAG3 = 1 << 2, } RegressTestFlags; +typedef enum +{ + REGRESS_TEST_DISCONTINUOUS_FLAG1 = 1 << 9, + REGRESS_TEST_DISCONTINUOUS_FLAG2 = 1 << 29 +} RegressTestDiscontinuousFlags; + +GI_TEST_EXTERN +GType regress_test_discontinuous_flags_get_type (void) G_GNUC_CONST; +#define REGRESS_TEST_TYPE_DISCONTINUOUS_FLAGS (regress_test_discontinuous_flags_get_type ()) + +GI_TEST_EXTERN +RegressTestDiscontinuousFlags +regress_test_discontinuous_1_with_private_values (void); + +GI_TEST_EXTERN +RegressTestDiscontinuousFlags +regress_test_discontinuous_2_with_private_values (void); + GI_TEST_EXTERN GType regress_test_enum_get_type (void) G_GNUC_CONST; #define REGRESS_TEST_TYPE_ENUM (regress_test_enum_get_type ()) @@ -785,6 +804,7 @@ struct _RegressTestBoxedC { guint refcount; guint another_thing; + gboolean name_conflict; }; GI_TEST_EXTERN @@ -793,6 +813,9 @@ GType regress_test_boxed_c_get_type (void); GI_TEST_EXTERN RegressTestBoxedC *regress_test_boxed_c_new (void); +GI_TEST_EXTERN +gboolean regress_test_boxed_c_name_conflict (RegressTestBoxedC *boxed); + /** * RegressTestBoxedD: (copy-func regress_test_boxed_d_copy) * (free-func regress_test_boxed_d_free) @@ -862,6 +885,26 @@ struct _RegressTestObjClass */ void (*allow_none_vfunc) (RegressTestObj *obj, RegressTestObj *two); + /** + * RegressTestObjClass::static_vfunc: + */ + gboolean (*static_vfunc) (void); + + /** + * RegressTestObjClass::static_vfunc_params: + * @value: + * @error: + * + * Return: (transfer none): + */ + RegressTestObj *(*static_vfunc_params) (gint value, GError **error); + + /** + * RegressTestObjClass::static_vfunc_out: + * @two: (out): + */ + void (*static_vfunc_out) (RegressTestObj **two); + guint test_signal; guint test_signal_with_static_scope_arg; @@ -899,8 +942,10 @@ void regress_test_obj_emit_sig_with_obj (RegressTestObj *obj); GI_TEST_EXTERN void regress_test_obj_emit_sig_with_obj_full (RegressTestObj *obj); +#if GLIB_CHECK_VERSION(2, 68, 0) GI_TEST_EXTERN void regress_test_obj_emit_sig_with_gstrv_full (RegressTestObj *obj); +#endif GI_TEST_EXTERN void regress_test_obj_emit_sig_with_foreign_struct (RegressTestObj *obj); @@ -1299,6 +1344,9 @@ void regress_test_function_async (int io_priority, GAsyncReadyCallback callback, gpointer user_data); +GI_TEST_EXTERN +int regress_test_function_thaw_async (void); + GI_TEST_EXTERN gboolean regress_test_function_finish (GAsyncResult *res, GError **error); @@ -1321,6 +1369,10 @@ void regress_test_obj_new_async (const char *x G_GNUC_UNUSED, GCancellable *cancellable G_GNUC_UNUSED, GAsyncReadyCallback callback G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED); + +GI_TEST_EXTERN +int regress_test_obj_constructor_thaw_async (void); + GI_TEST_EXTERN RegressTestObj *regress_test_obj_new_finish (GAsyncResult *res G_GNUC_UNUSED, GError **error G_GNUC_UNUSED); @@ -1682,6 +1734,29 @@ void regress_test_obj_function_async (RegressTestObj *self, GAsyncReadyCallback callback, gpointer user_data); +GI_TEST_EXTERN +void regress_test_obj_function2 (RegressTestObj *self, + int io_priority, + GCancellable *cancellable, + RegressTestCallbackUserData test_cb, + gpointer test_data, + GDestroyNotify test_destroy, + GAsyncReadyCallback callback, + gpointer user_data); + +GI_TEST_EXTERN +gboolean regress_test_obj_function2_finish (RegressTestObj *self, + GAsyncResult *result, + gboolean *match, + GObject **some_obj, + GError **error); + +GI_TEST_EXTERN +gboolean regress_test_obj_function2_sync (RegressTestObj *self, int io_priority); + +GI_TEST_EXTERN +int regress_test_obj_function_thaw_async (RegressTestObj *self); + GI_TEST_EXTERN gboolean regress_test_obj_function_finish (RegressTestObj *self, GAsyncResult *res, GError **error); diff --git a/subprojects/gobject-introspection-tests/regressextra.c b/subprojects/gobject-introspection-tests/regressextra.c new file mode 100644 index 000000000..71b9b87d9 --- /dev/null +++ b/subprojects/gobject-introspection-tests/regressextra.c @@ -0,0 +1,602 @@ +/* +SPDX-License-Identifier: LGPL-2.1-or-later +SPDX-FileCopyrightText: 2018 PyGObject contributors +*/ + +#include + +#ifndef GI_TEST_DISABLE_CAIRO +#include +#endif /* GI_TEST_DISABLE_CAIRO */ +#include + +#include "regress.h" +#include "regressextra.h" + +struct _RegressTestBoxedCWrapper +{ + RegressTestBoxedC *cptr; +}; + +RegressTestBoxedCWrapper * +regress_test_boxed_c_wrapper_new (void) +{ + RegressTestBoxedCWrapper *boxed; + boxed = g_slice_new (RegressTestBoxedCWrapper); + boxed->cptr = regress_test_boxed_c_new (); + return boxed; +} + +RegressTestBoxedCWrapper * +regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self) +{ + RegressTestBoxedCWrapper *ret_boxed; + ret_boxed = g_slice_new (RegressTestBoxedCWrapper); + ret_boxed->cptr = g_boxed_copy (regress_test_boxed_c_get_type (), self->cptr); + return ret_boxed; +} + +static void +regress_test_boxed_c_wrapper_free (RegressTestBoxedCWrapper *boxed) +{ + g_boxed_free (regress_test_boxed_c_get_type (), boxed->cptr); + g_slice_free (RegressTestBoxedCWrapper, boxed); +} + +G_DEFINE_BOXED_TYPE (RegressTestBoxedCWrapper, + regress_test_boxed_c_wrapper, + regress_test_boxed_c_wrapper_copy, + regress_test_boxed_c_wrapper_free); + +/** + * regress_test_boxed_c_wrapper_get + * @self: a #RegressTestBoxedCWrapper objects + * + * Returns: (transfer none): associated #RegressTestBoxedC + **/ +RegressTestBoxedC * +regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self) +{ + return self->cptr; +} + +/** + * regress_test_array_of_non_utf8_strings + * Returns: (transfer full) (allow-none) (array zero-terminated=1): Array of strings + */ +gchar ** +regress_test_array_of_non_utf8_strings (void) +{ + char **ret = g_new (char *, 2); + ret[0] = g_strdup ("Andr\351 Lur\347at"); + ret[1] = NULL; + return ret; +} + +/** + * regress_test_array_fixed_boxed_none_out + * @objs: (out) (array fixed-size=2) (transfer none): An array of #RegressTestBoxedC + **/ +void +regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs) +{ + static RegressTestBoxedC **arr; + + if (arr == NULL) + { + arr = g_new0 (RegressTestBoxedC *, 3); + arr[0] = regress_test_boxed_c_new (); + arr[1] = regress_test_boxed_c_new (); + } + + *objs = arr; +} + +/** + * regress_test_gvalue_out_boxed: + * @value: (out) (transfer full): the output gvalue + * @init: (in): the initialisation value + **/ +void +regress_test_gvalue_out_boxed (GValue *value, int init) +{ + RegressTestBoxed rtb; + GValue v = G_VALUE_INIT; + + memset (&rtb, 0, sizeof (rtb)); + rtb.some_int8 = init; + g_value_init (&v, REGRESS_TEST_TYPE_BOXED); + g_value_set_boxed (&v, &rtb); + *value = v; +} + +/** + * regress_test_glist_boxed_none_return + * Return value: (element-type RegressTestBoxedC) (transfer none): + **/ +GList * +regress_test_glist_boxed_none_return (guint count) +{ + static GList *list = NULL; + if (!list) + { + while (count > 0) + { + list = g_list_prepend (list, regress_test_boxed_c_new ()); + count--; + } + } + + return list; +} + +/** + * regress_test_glist_boxed_full_return + * Return value: (element-type RegressTestBoxedC) (transfer full): + **/ +GList * +regress_test_glist_boxed_full_return (guint count) +{ + GList *list = NULL; + while (count > 0) + { + list = g_list_prepend (list, regress_test_boxed_c_new ()); + count--; + } + return list; +} + +/** + * regress_test_array_of_fundamental_objects_in + * @list: (array length=len) (element-type RegressTestFundamentalObject): An array of #RegressTestFundamentalObject + * @len: length of the list + **/ +gboolean +regress_test_array_of_fundamental_objects_in (RegressTestFundamentalObject **list, gsize len) +{ + gsize i; + + for (i = 0; i < len; i++) + { + if (!REGRESS_TEST_IS_FUNDAMENTAL_OBJECT (list[i])) + { + return FALSE; + } + } + return TRUE; +} + +/** + * regress_test_array_of_fundamental_objects_out + * @len: (out): length of the list + * Returns: (array length=len) (transfer full): An array of #RegressTestFundamentalObject + **/ +RegressTestFundamentalObject ** +regress_test_array_of_fundamental_objects_out (gsize *len) +{ + RegressTestFundamentalObject **objs; + int i; + + objs = g_new (RegressTestFundamentalObject *, 2); + + for (i = 0; i < 2; i++) + { + objs[i] = (RegressTestFundamentalObject *) regress_test_fundamental_sub_object_new ("foo"); + } + *len = 2; + return objs; +} + +/** + * regress_test_fundamental_argument_in + * @obj: (transfer full): A #RegressTestFundamentalObject + **/ +gboolean +regress_test_fundamental_argument_in (RegressTestFundamentalObject *obj) +{ + gboolean retval = REGRESS_TEST_IS_FUNDAMENTAL_OBJECT (obj); + regress_test_fundamental_object_unref(obj); + return retval; +} + +/** + * regress_test_fundamental_argument_out + * @obj: (transfer none): A #RegressTestFundamentalObject + * Returns: (transfer none): Same #RegressTestFundamentalObject + **/ +RegressTestFundamentalObject * +regress_test_fundamental_argument_out (RegressTestFundamentalObject *obj) +{ + return obj; +} + +#ifndef GI_TEST_DISABLE_CAIRO + +/** + * regress_test_cairo_context_none_return: + * + * Returns: (transfer none): + */ +cairo_t * +regress_test_cairo_context_none_return (void) +{ + static cairo_t *cr; + + if (cr == NULL) + { + cairo_surface_t *surface; + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); + cr = cairo_create (surface); + cairo_surface_destroy (surface); + } + + return cr; +} + +/** + * regress_test_cairo_context_full_in: + * @context: (transfer full): + */ +void +regress_test_cairo_context_full_in (cairo_t *context) +{ + cairo_destroy (context); +} + +/** + * regress_test_cairo_path_full_return: + * + * Returns: (transfer full): + */ +cairo_path_t * +regress_test_cairo_path_full_return (void) +{ + cairo_t *cr = regress_test_cairo_context_none_return (); + + return cairo_copy_path (cr); +} + +/** + * regress_test_cairo_path_none_in: + * @path: (transfer none): + */ +void +regress_test_cairo_path_none_in (cairo_path_t *path) +{ + cairo_t *cr = regress_test_cairo_context_full_return (); + cairo_append_path (cr, path); + g_assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS); + cairo_destroy (cr); +} + +/** + * regress_test_cairo_path_full_in_full_return: + * @path: (transfer full): + * + * Returns: (transfer full): + */ +cairo_path_t * +regress_test_cairo_path_full_in_full_return (cairo_path_t *path) +{ + return path; +} + +/** + * regress_test_cairo_pattern_full_in: + * @pattern: (transfer full): + */ +void +regress_test_cairo_pattern_full_in (cairo_pattern_t *pattern) +{ + cairo_pattern_destroy (pattern); +} + +/** + * regress_test_cairo_pattern_none_in: + * @pattern: (transfer none): + */ +void +regress_test_cairo_pattern_none_in (cairo_pattern_t *pattern) +{ + cairo_t *cr = regress_test_cairo_context_full_return (); + cairo_set_source (cr, pattern); + g_assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS); + cairo_destroy (cr); +} + +/** + * regress_test_cairo_pattern_none_return: + * + * Returns: (transfer none): + */ +cairo_pattern_t * +regress_test_cairo_pattern_none_return (void) +{ + static cairo_pattern_t *pattern; + + if (pattern == NULL) + { + pattern = cairo_pattern_create_rgb (0.1, 0.2, 0.3); + } + + return pattern; +} + +/** + * regress_test_cairo_pattern_full_return: + * + * Returns: (transfer full): + */ +cairo_pattern_t * +regress_test_cairo_pattern_full_return (void) +{ + cairo_pattern_t *pattern = cairo_pattern_create_rgb (0.5, 0.6, 0.7); + return pattern; +} + +/** + * regress_test_cairo_region_full_in: + * @region: (transfer full): + */ +void +regress_test_cairo_region_full_in (cairo_region_t *region) +{ + cairo_region_destroy (region); +} + +/** + * regress_test_cairo_surface_full_in: + * @surface: (transfer full): + */ +void +regress_test_cairo_surface_full_in (cairo_surface_t *surface) +{ + g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32); + g_assert (cairo_image_surface_get_width (surface) == 10); + g_assert (cairo_image_surface_get_height (surface) == 10); + cairo_surface_destroy (surface); +} + +/** + * regress_test_cairo_font_options_full_return: + * + * Returns: (transfer full): + */ +cairo_font_options_t * +regress_test_cairo_font_options_full_return (void) +{ + return cairo_font_options_create (); +} + +/** + * regress_test_cairo_font_options_none_return: + * + * Returns: (transfer none): + */ +cairo_font_options_t * +regress_test_cairo_font_options_none_return (void) +{ + static cairo_font_options_t *options; + + if (options == NULL) + options = cairo_font_options_create (); + + return options; +} + +/** + * regress_test_cairo_font_options_full_in: + * @options: (transfer full): + */ +void +regress_test_cairo_font_options_full_in (cairo_font_options_t *options) +{ + cairo_font_options_destroy (options); +} + +/** + * regress_test_cairo_font_options_none_in: + * @options: (transfer none): + */ +void +regress_test_cairo_font_options_none_in (cairo_font_options_t *options G_GNUC_UNUSED) +{ +} + +/** + * regress_test_cairo_font_face_full_return: + * @cairo: (transfer none): + * + * Returns: (transfer full): + */ +cairo_font_face_t * +regress_test_cairo_font_face_full_return (cairo_t *cairo) +{ + cairo_font_face_t *font_face = cairo_get_font_face (cairo); + + return cairo_font_face_reference (font_face); +} + +/** + * regress_test_cairo_scaled_font_full_return: + * @cairo: (transfer none): + * + * Returns: (transfer full): + */ +cairo_scaled_font_t * +regress_test_cairo_scaled_font_full_return (cairo_t *cairo) +{ + cairo_scaled_font_t *scaled_font = cairo_get_scaled_font (cairo); + + return cairo_scaled_font_reference (scaled_font); +} + +/** + * regress_test_cairo_matrix_none_in: + * @matrix: (transfer none): + */ +void +regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix) +{ + cairo_matrix_t m = *matrix; + g_assert (m.x0 == 0); + g_assert (m.y0 == 0); + g_assert (m.xx == 1); + g_assert (m.xy == 0); + g_assert (m.yy == 1); + g_assert (m.yx == 0); +} + +/** + * regress_test_cairo_matrix_none_return: + * Returns: (transfer none): + */ +cairo_matrix_t * +regress_test_cairo_matrix_none_return (void) +{ + static cairo_matrix_t matrix; + cairo_matrix_init_identity (&matrix); + return &matrix; +} + +/** + * regress_test_cairo_matrix_out_caller_allocates: + * @matrix: (out): + */ +void +regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix) +{ + cairo_matrix_t m; + cairo_matrix_init_identity (&m); + *matrix = m; +} + +#endif + +G_DEFINE_TYPE (RegressTestAction, regress_test_action, G_TYPE_INITIALLY_UNOWNED) + +enum +{ + SIGNAL_0, + ACTION_SIGNAL, + ACTION2_SIGNAL, + LAST_SIGNAL +}; + +static guint regress_test_action_signals[LAST_SIGNAL] = { 0 }; + +static RegressTestAction * +regress_test_action_do_action (RegressTestAction *self G_GNUC_UNUSED) +{ + RegressTestAction *ret = g_object_new (regress_test_action_get_type (), NULL); + + return ret; +} + +static RegressTestAction * +regress_test_action_do_action2 (RegressTestAction *self G_GNUC_UNUSED) +{ + return NULL; +} + +static void +regress_test_action_init (RegressTestAction *self G_GNUC_UNUSED) +{ +} + +static void +regress_test_action_class_init (RegressTestActionClass *klass) +{ + /** + * RegressTestAction::action: + * + * An action signal. + * + * Returns: (transfer full): another #RegressTestAction + */ + regress_test_action_signals[ACTION_SIGNAL] = + g_signal_new_class_handler ("action", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (regress_test_action_do_action), NULL, NULL, + NULL, regress_test_action_get_type (), 0); + + /** + * RegressTestAction::action2: + * + * Another action signal. + * + * Returns: (transfer full): another #RegressTestAction + */ + regress_test_action_signals[ACTION2_SIGNAL] = + g_signal_new_class_handler ("action2", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (regress_test_action_do_action2), NULL, NULL, + NULL, regress_test_action_get_type (), 0); +} + +/* + * RegressBitmask + * + * Mimic a primitive, fundamental type. + */ + +static void +regress_value_init_bitmask (GValue *value) +{ + value->data[0].v_uint64 = 0; +} + +static void +regress_value_copy_bitmask (const GValue *src_value, GValue *dest_value) +{ + dest_value->data[0].v_uint64 = src_value->data[0].v_uint64; +} + +static void +_value_transform_uint64_bitmask (const GValue *src_value, GValue *dest_value) +{ + dest_value->data[0].v_uint64 = src_value->data[0].v_uint64; +} + +static void +_value_transform_bitmask_uint64 (const GValue *src_value, GValue *dest_value) +{ + dest_value->data[0].v_uint64 = src_value->data[0].v_uint64; +} + +static const GTypeValueTable _regress_bitmask_value_table = { + regress_value_init_bitmask, + NULL, + regress_value_copy_bitmask, + NULL, + (char *) NULL, + NULL, + (char *) NULL, + NULL +}; + +GType +regress_bitmask_get_type (void) +{ + static GType regress_bitmask_type = 0; + + if (g_once_init_enter (®ress_bitmask_type)) + { + GTypeInfo _info = { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, &_regress_bitmask_value_table }; + GTypeFundamentalInfo _finfo = { 0 }; + GType _type = g_type_register_fundamental ( + g_type_fundamental_next (), + "RegressBitmask", &_info, &_finfo, 0); + + g_once_init_leave (®ress_bitmask_type, _type); + + g_value_register_transform_func ( + REGRESS_TYPE_BITMASK, G_TYPE_UINT64, + _value_transform_bitmask_uint64); + g_value_register_transform_func ( + G_TYPE_UINT64, REGRESS_TYPE_BITMASK, + _value_transform_uint64_bitmask); + } + + return regress_bitmask_type; +} diff --git a/subprojects/gobject-introspection-tests/regressextra.h b/subprojects/gobject-introspection-tests/regressextra.h new file mode 100644 index 000000000..41c2ca582 --- /dev/null +++ b/subprojects/gobject-introspection-tests/regressextra.h @@ -0,0 +1,128 @@ +/* +SPDX-License-Identifier: LGPL-2.1-or-later +SPDX-FileCopyrightText: 2018 PyGObject contributors +*/ + +#pragma once + +#ifndef GI_TEST_DISABLE_CAIRO +#include +#endif /* GI_TEST_DISABLE_CAIRO */ +#include +#include + +#include "gitestmacros.h" +#include "regress.h" + +typedef struct _RegressTestBoxedCWrapper RegressTestBoxedCWrapper; + +GI_TEST_EXTERN +GType regress_test_boxed_c_wrapper_get_type (void); + +GI_TEST_EXTERN +RegressTestBoxedCWrapper *regress_test_boxed_c_wrapper_new (void); +GI_TEST_EXTERN +RegressTestBoxedCWrapper *regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self); +GI_TEST_EXTERN +RegressTestBoxedC *regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self); + +GI_TEST_EXTERN +gchar **regress_test_array_of_non_utf8_strings (void); +GI_TEST_EXTERN +void regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs); +GI_TEST_EXTERN +void regress_test_gvalue_out_boxed (GValue *value, int init); +GI_TEST_EXTERN +GList *regress_test_glist_boxed_none_return (guint count); +GI_TEST_EXTERN +GList *regress_test_glist_boxed_full_return (guint count); + +GI_TEST_EXTERN +gboolean regress_test_array_of_fundamental_objects_in (RegressTestFundamentalObject **list, gsize len); +GI_TEST_EXTERN +RegressTestFundamentalObject **regress_test_array_of_fundamental_objects_out (gsize *len); +GI_TEST_EXTERN +gboolean regress_test_fundamental_argument_in (RegressTestFundamentalObject *obj); +GI_TEST_EXTERN +RegressTestFundamentalObject *regress_test_fundamental_argument_out (RegressTestFundamentalObject *obj); + +#ifndef GI_TEST_DISABLE_CAIRO + +GI_TEST_EXTERN +cairo_t *regress_test_cairo_context_none_return (void); +GI_TEST_EXTERN +void regress_test_cairo_context_full_in (cairo_t *context); +GI_TEST_EXTERN +cairo_path_t *regress_test_cairo_path_full_return (void); +GI_TEST_EXTERN +void regress_test_cairo_path_none_in (cairo_path_t *path); +GI_TEST_EXTERN +cairo_path_t *regress_test_cairo_path_full_in_full_return (cairo_path_t *path); +GI_TEST_EXTERN +void regress_test_cairo_pattern_full_in (cairo_pattern_t *pattern); +GI_TEST_EXTERN +void regress_test_cairo_pattern_none_in (cairo_pattern_t *pattern); +GI_TEST_EXTERN +cairo_pattern_t *regress_test_cairo_pattern_none_return (void); +GI_TEST_EXTERN +cairo_pattern_t *regress_test_cairo_pattern_full_return (void); +GI_TEST_EXTERN +cairo_font_options_t *regress_test_cairo_font_options_full_return (void); +GI_TEST_EXTERN +cairo_font_options_t *regress_test_cairo_font_options_none_return (void); +GI_TEST_EXTERN +void regress_test_cairo_font_options_full_in (cairo_font_options_t *options); +GI_TEST_EXTERN +void regress_test_cairo_font_options_none_in (cairo_font_options_t *options); +GI_TEST_EXTERN +cairo_font_face_t *regress_test_cairo_font_face_full_return (cairo_t *cairo); +GI_TEST_EXTERN +cairo_scaled_font_t *regress_test_cairo_scaled_font_full_return (cairo_t *cairo); +GI_TEST_EXTERN +void regress_test_cairo_region_full_in (cairo_region_t *region); +GI_TEST_EXTERN +void regress_test_cairo_surface_full_in (cairo_surface_t *surface); +GI_TEST_EXTERN +void regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix); +GI_TEST_EXTERN +cairo_matrix_t *regress_test_cairo_matrix_none_return (void); +GI_TEST_EXTERN +void regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix); + +#endif + +/* RegressTestAction */ + +typedef struct +{ + GInitiallyUnowned parent; +} RegressTestAction; + +typedef struct +{ + GInitiallyUnownedClass parent_class; +} RegressTestActionClass; + +GI_TEST_EXTERN +GType regress_test_action_get_type (void); + +/** + * RegressBitmask: + * + * A fundamental type that describes a 64-bit bitmask. + * + * This type resembles GStreamer's Bitmask type. + */ + +/** + * REGRESS_TYPE_BITMASK: + * + * a #GValue type that represents a 64-bit bitmask. + * + * Returns: the #GType of RegressBitmask (which is not explicitly typed) + */ + +#define REGRESS_TYPE_BITMASK (regress_bitmask_get_type ()) + +GI_TEST_EXTERN +GType regress_bitmask_get_type (void); diff --git a/subprojects/gobject-introspection-tests/tools/iwyu.imp b/subprojects/gobject-introspection-tests/tools/iwyu.imp index c60aa05a7..0b5e4a795 100644 --- a/subprojects/gobject-introspection-tests/tools/iwyu.imp +++ b/subprojects/gobject-introspection-tests/tools/iwyu.imp @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento [ diff --git a/subprojects/gobject-introspection-tests/tools/run_clang_format.sh b/subprojects/gobject-introspection-tests/tools/run_clang_format.sh old mode 100644 new mode 100755 index cad287799..86a1b569a --- a/subprojects/gobject-introspection-tests/tools/run_clang_format.sh +++ b/subprojects/gobject-introspection-tests/tools/run_clang_format.sh @@ -1,6 +1,6 @@ #!/bin/bash -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento CLANG_FORMAT=${CLANG_FORMAT:-clang-format} diff --git a/subprojects/gobject-introspection-tests/tools/run_iwyu.sh b/subprojects/gobject-introspection-tests/tools/run_iwyu.sh old mode 100644 new mode 100755 index e893f5626..4b9d19369 --- a/subprojects/gobject-introspection-tests/tools/run_iwyu.sh +++ b/subprojects/gobject-introspection-tests/tools/run_iwyu.sh @@ -1,6 +1,6 @@ #!/bin/bash -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Philip Chimento IWYU=${IWYU:-include-what-you-use} diff --git a/subprojects/gobject-introspection-tests/utility.c b/subprojects/gobject-introspection-tests/utility.c index e5bac32ca..8e4ccfd34 100644 --- a/subprojects/gobject-introspection-tests/utility.c +++ b/subprojects/gobject-introspection-tests/utility.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008 Johan Dahlin SPDX-FileCopyrightText: 2009 Andreas Rottmann */ @@ -13,9 +14,19 @@ G_DEFINE_TYPE (UtilityObject, utility_object, G_TYPE_OBJECT); * **/ +static void +utility_object_finalize (GObject *gobj) +{ + UtilityObject *self = UTILITY_OBJECT(gobj); + if (self->destroy_notify) + self->destroy_notify(self->user_data); + G_OBJECT_CLASS(utility_object_parent_class)->finalize(gobj); +} + static void utility_object_class_init (UtilityObjectClass *klass G_GNUC_UNUSED) { + G_OBJECT_CLASS(klass)->finalize = utility_object_finalize; } static void @@ -32,19 +43,21 @@ utility_object_init (UtilityObject *object G_GNUC_UNUSED) * @destroy: */ void -utility_object_watch_dir (UtilityObject *object G_GNUC_UNUSED, +utility_object_watch_dir (UtilityObject *object, const char *path G_GNUC_UNUSED, UtilityFileFunc func G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED, - GDestroyNotify destroy G_GNUC_UNUSED) + gpointer user_data, + GDestroyNotify destroy) { + object->user_data = user_data; + object->destroy_notify = destroy; } /** * utility_dir_foreach: - * @path:: + * @path: * @func: (scope call): - * @user_data:: + * @user_data: * */ void diff --git a/subprojects/gobject-introspection-tests/utility.h b/subprojects/gobject-introspection-tests/utility.h index c8aeb074e..59259a16d 100644 --- a/subprojects/gobject-introspection-tests/utility.h +++ b/subprojects/gobject-introspection-tests/utility.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2008 Colin Walters SPDX-FileCopyrightText: 2008 Johan Dahlin SPDX-FileCopyrightText: 2008-2009 Andreas Rottmann @@ -21,6 +22,9 @@ typedef struct _UtilityObjectClass UtilityObjectClass; struct _UtilityObject { GObject parent_instance; + /*< private >*/ + void *user_data; + GDestroyNotify destroy_notify; }; struct _UtilityObjectClass diff --git a/subprojects/gobject-introspection-tests/warnlib.c b/subprojects/gobject-introspection-tests/warnlib.c index 9b844e700..8cc4cce55 100644 --- a/subprojects/gobject-introspection-tests/warnlib.c +++ b/subprojects/gobject-introspection-tests/warnlib.c @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2012 Colin Walters SPDX-FileCopyrightText: 2013 Dieter Verfaillie */ diff --git a/subprojects/gobject-introspection-tests/warnlib.h b/subprojects/gobject-introspection-tests/warnlib.h index 8a1e52289..bed24772e 100644 --- a/subprojects/gobject-introspection-tests/warnlib.h +++ b/subprojects/gobject-introspection-tests/warnlib.h @@ -1,4 +1,5 @@ /* +SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND MIT SPDX-FileCopyrightText: 2012 Colin Walters SPDX-FileCopyrightText: 2013 Dieter Verfaillie */ diff --git a/test/check-headers.sh b/test/check-headers.sh old mode 100644 new mode 100755 index 56a977c44..4ec336287 --- a/test/check-headers.sh +++ b/test/check-headers.sh @@ -29,39 +29,39 @@ if [ -n "$SELFTEST" ]; then # config.h is included test_env - echo "#include " > gjs/program.c + echo "#include " > cjs/program.c expect_success # config.h must be in angle brackets test_env - echo '#include "config.h"' > gjs/program.c + echo '#include "config.h"' > cjs/program.c expect_failure # public headers are skipped test_env - echo "#include " > gjs/macros.h + echo "#include " > cjs/macros.h expect_success # config.h must be included test_env - echo "#include " > gjs/program.c + echo "#include " > cjs/program.c expect_failure # config.h is included first test_env - echo '#include ' > gjs/program.c - echo '#include ' >> gjs/program.c + echo '#include ' > cjs/program.c + echo '#include ' >> cjs/program.c expect_success # config.h must be included first test_env - echo '#include ' > gjs/program.c - echo '#include ' >> gjs/program.c + echo '#include ' > cjs/program.c + echo '#include ' >> cjs/program.c expect_failure # other non-include things can come before the include test_env - cat > gjs/program.h < cjs/program.h < @@ -70,7 +70,7 @@ EOF # spaces are taken into account test_env - cat > gjs/program.c < cjs/program.c < #endif @@ -80,7 +80,7 @@ EOF # header blocks in right order test_env - cat > gjs/program.c < cjs/program.c < #include #include @@ -92,7 +92,7 @@ EOF # header blocks in wrong order test_env - cat > gjs/program.c < cjs/program.c < #include #include @@ -168,7 +168,7 @@ function check_config_header { files=$(find gi gjs libgjs-private modules test util \ -name '*.c' -o -name '*.cpp' -o -name '*.h') for file in $files; do - if [[ "$file" == "gjs/gjs.h" || "$file" == "gjs/macros.h" ]]; then continue; fi + if [[ "$file" == "cjs/gjs.h" || "$file" == "cjs/macros.h" ]]; then continue; fi if grep -ql "^GJS_EXPORT" "$file"; then continue; fi check_config_header "$file" done diff --git a/test/check-pch.sh b/test/check-pch.sh old mode 100644 new mode 100755 index fd27b10f5..777074c4e --- a/test/check-pch.sh +++ b/test/check-pch.sh @@ -15,7 +15,7 @@ if [ -n "$SELFTEST" ]; then test_paths+=("$code_path") cd "$code_path" mkdir gjs gi - echo "#include " >> gjs/gjs_pch.hh + echo "#include " >> cjs/gjs_pch.hh } expect_success() { @@ -53,12 +53,12 @@ if [ -n "$SELFTEST" ]; then expect_failure test_env - echo "#include " >> gjs/gjs_pch.hh + echo "#include " >> cjs/gjs_pch.hh echo "#include " >> gi/code.c expect_failure test_env - echo "#include // check-pch: ignore, yes" >> gjs/gjs_pch.hh + echo "#include // check-pch: ignore, yes" >> cjs/gjs_pch.hh echo "#include " >> gi/code.c expect_success @@ -74,14 +74,14 @@ if [ -n "$SELFTEST" ]; then test_env echo "#include " >> gi/code.c - echo '#include "local/header.h"' >> gjs/gjs_pch.hh + echo '#include "local/header.h"' >> cjs/gjs_pch.hh expect_failure test_env echo "# include " >> gi/code.c echo "# include " >> gi/code.c echo " # include " >> gi/other-file.c - echo "# include " >> gjs/gjs_pch.hh + echo "# include " >> cjs/gjs_pch.hh expect_success test_env @@ -98,7 +98,7 @@ if [ -n "$SELFTEST" ]; then echo "#include " >> gi/code.c echo "//#include " >> gi/invalid-file.c echo "// #include " >> gi/invalid-file.c - echo "//#include " >> gjs/gjs_pch.hh + echo "//#include " >> cjs/gjs_pch.hh expect_success test_env @@ -114,7 +114,7 @@ if [ -n "$SELFTEST" ]; then exit 0 fi -PCH_FILES=(gjs/gjs_pch.hh) +PCH_FILES=(cjs/gjs_pch.hh) IGNORE_COMMENT="check-pch: ignore" CODE_PATHS=(gjs gi) diff --git a/test/extra/Dockerfile b/test/extra/Dockerfile index 14bcbe13c..5fc7f5210 100644 --- a/test/extra/Dockerfile +++ b/test/extra/Dockerfile @@ -3,25 +3,20 @@ # === Build Spidermonkey stage === -FROM registry.fedoraproject.org/fedora:40 AS mozjs-build -ARG MOZJS_BRANCH=mozjs115 +FROM registry.fedoraproject.org/fedora:42 AS mozjs-build +ARG MOZJS_BRANCH=mozjs140 ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} ARG BUILD_OPTS= ENV SHELL=/bin/bash -# mozjs115 cannot be built with python3.11 -# cbindgen should be included in builddep(mozjs128) RUN dnf -y install 'dnf-command(builddep)' \ - autoconf213 \ - cbindgen \ clang \ git \ llvm \ llvm-devel \ make \ - python3.10 \ - python-packaging \ + python3-packaging \ rust \ which \ xz @@ -34,7 +29,6 @@ RUN mkdir -p mozjs/_build WORKDIR /root/mozjs/_build -ENV PYTHON3=/usr/bin/python3.10 RUN ../js/src/configure --prefix=/usr --libdir=/usr/lib64 --disable-jemalloc \ --with-system-zlib --with-intl-api ${BUILD_OPTS} RUN make -j$(nproc) @@ -43,9 +37,9 @@ RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs # === Actual Docker image === -FROM registry.fedoraproject.org/fedora:40 +FROM registry.fedoraproject.org/fedora:42 -ARG LOCALES=tr_TR +ARG LOCALES=ar_EG en_CA fa_IR tr_TR ENV SHELL=/bin/bash # List is comprised of base dependencies for CI scripts, gjs, and debug packages @@ -83,6 +77,7 @@ RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ gtk4-debuginfo \ gtk4-debugsource \ gtk4-devel \ + iwyu \ lcov \ libasan \ libubsan \ @@ -91,7 +86,9 @@ RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ ninja-build \ pkgconf \ readline-devel \ + sysprof-devel \ systemtap-sdt-devel \ + systemtap-sdt-dtrace \ valgrind \ which \ Xvfb \ diff --git a/test/extra/Dockerfile.debug b/test/extra/Dockerfile.debug deleted file mode 100644 index 876e45727..000000000 --- a/test/extra/Dockerfile.debug +++ /dev/null @@ -1,140 +0,0 @@ -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2020 Philip Chimento - -# === Build stage === - -FROM registry.fedoraproject.org/fedora:40 AS build -ARG MOZJS_BRANCH=mozjs115 -ARG MOZJS_BUILDDEPS=${MOZJS_BRANCH} -ARG BUILD_OPTS= - -ENV SHELL=/bin/bash - -# mozjs115 cannot be built with python3.11 -# cbindgen should be included in builddep(mozjs128) -RUN dnf -y install 'dnf-command(builddep)' \ - autoconf213 \ - cbindgen \ - clang \ - clang-devel \ - cmake \ - git \ - llvm \ - llvm-devel \ - make \ - ninja-build \ - python3.10 \ - python3-packaging \ - rust \ - which \ - xz -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.22.src.tar.gz /root/include-what-you-use/ -WORKDIR /root/include-what-you-use -RUN tar xzf include-what-you-use-0.22.src.tar.gz --strip-components=1 - -WORKDIR /root/include-what-you-use/_build - -RUN cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/usr .. -RUN ninja -RUN DESTDIR=/root/iwyu-install ninja install - -WORKDIR /root - -RUN git clone --no-tags --depth 1 https://github.com/ptomato/mozjs.git -b ${MOZJS_BRANCH} -RUN mkdir -p mozjs/_build - -WORKDIR /root/mozjs/_build - -ENV PYTHON3=/usr/bin/python3.10 -RUN ../js/src/configure --prefix=/usr --libdir=/usr/lib64 --disable-jemalloc \ - --with-system-zlib --with-intl-api --enable-debug \ - ${BUILD_OPTS} -RUN make -j$(nproc) -RUN DESTDIR=/root/mozjs-install make install -RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs - -# === Actual Docker image === - -FROM registry.fedoraproject.org/fedora:40 - -ARG LOCALES=tr_TR -ENV SHELL=/bin/bash - -# List is comprised of base dependencies for CI scripts, gjs, and debug packages -# needed for informative stack traces, e.g. in Valgrind. -# -# Do everything in one RUN command so that the dnf cache is not cached in the -# final Docker image. -RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ - binutils \ - cairo-debuginfo \ - cairo-debugsource \ - cairo-gobject-devel \ - clang \ - compiler-rt \ - dbus-daemon \ - dbus-x11 \ - diffutils \ - fontconfig-debuginfo \ - fontconfig-debugsource \ - gcc-c++ \ - git \ - glib2-debuginfo \ - glib2-debugsource \ - glib2-devel \ - glibc-debuginfo \ - glibc-gconv-extra \ - glibc-locale-source \ - gnome-desktop-testing \ - gobject-introspection-debuginfo \ - gobject-introspection-debugsource \ - gobject-introspection-devel \ - gtk3-debuginfo \ - gtk3-debugsource \ - gtk3-devel \ - gtk4-debuginfo \ - gtk4-debugsource \ - gtk4-devel \ - lcov \ - libasan \ - libubsan \ - libtsan \ - meson \ - ninja-build \ - pkgconf \ - readline-devel \ - sysprof-devel \ - systemtap-sdt-devel \ - valgrind \ - which \ - Xvfb \ - xz \ - && \ - dnf clean all && rm -rf /var/cache/dnf - -COPY --from=build /root/mozjs-install/usr /usr -COPY --from=build /root/iwyu-install/usr /usr -RUN ln -s /usr/bin/iwyu_tool.py /usr/bin/iwyu_tool - -# Enable sudo for wheel users -RUN sed -i -e 's/# %wheel/%wheel/' -e '0,/%wheel/{s/%wheel/# %wheel/}' \ - /etc/sudoers - -ENV HOST_USER_ID 5555 -RUN useradd -u $HOST_USER_ID -G wheel -ms /bin/bash user - -# Enable locales needed for specific tests -RUN for locale in ${LOCALES}; do \ - localedef --verbose --force -i "$locale" "$locale" || true; \ - localedef --verbose --force -i "$locale" -f UTF-8 "$locale".UTF-8 || true; \ - done - -USER user -WORKDIR /home/user - -ENV LANG C.UTF-8 diff --git a/test/extra/do_environment.sh b/test/extra/do_environment.sh old mode 100644 new mode 100755 diff --git a/test/gjs-test-call-args.cpp b/test/gjs-test-call-args.cpp index fb625e574..b12202584 100644 --- a/test/gjs-test-call-args.cpp +++ b/test/gjs-test-call-args.cpp @@ -21,8 +21,8 @@ #include // for UniqueChars #include +#include "cjs/auto.h" #include "cjs/jsapi-util-args.h" -#include "cjs/jsapi-util.h" #include "test/gjs-test-common.h" #include "test/gjs-test-utils.h" @@ -30,12 +30,6 @@ 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*, - const char*) __attribute__((analyzer_noreturn)); -#endif - #define assert_match(str, pattern) \ G_STMT_START { \ const char *__s1 = (str), *__s2 = (pattern); \ @@ -118,7 +112,7 @@ JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(one_of_each_type) bool boolval; JS::UniqueChars strval; - GjsAutoChar fileval; + Gjs::AutoChar fileval; JS::RootedString jsstrval(cx); int intval; unsigned uintval; @@ -188,7 +182,7 @@ JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(one_of_each_nullable_type) JS::UniqueChars strval; - GjsAutoChar fileval; + Gjs::AutoChar fileval; JS::RootedString jsstrval(cx); JS::RootedObject objval(cx); retval = gjs_parse_call_args(cx, "oneOfEachNullableType", args, "?s?F?S?o", @@ -235,12 +229,13 @@ JSNATIVE_BAD_NULLABLE_TEST_FUNC(double, "f"); "val", &val); \ JSNATIVE_TEST_FUNC_END +using Gjs::AutoChar; JSNATIVE_BAD_TYPE_TEST_FUNC(bool, "i"); JSNATIVE_BAD_TYPE_TEST_FUNC(int, "u"); JSNATIVE_BAD_TYPE_TEST_FUNC(unsigned, "t"); JSNATIVE_BAD_TYPE_TEST_FUNC(int64_t, "f"); JSNATIVE_BAD_TYPE_TEST_FUNC(double, "b"); -JSNATIVE_BAD_TYPE_TEST_FUNC(GjsAutoChar, "i"); +JSNATIVE_BAD_TYPE_TEST_FUNC(AutoChar, "i"); #undef JSNATIVE_BAD_TYPE_TEST_FUNC @@ -288,7 +283,7 @@ static JSFunctionSpec native_test_funcs[] = { JS_FN("unsignedInvalidType", unsigned_invalid_type, 0, 0), JS_FN("int64_tInvalidType", int64_t_invalid_type, 0, 0), JS_FN("doubleInvalidType", double_invalid_type, 0, 0), - JS_FN("GjsAutoCharInvalidType", GjsAutoChar_invalid_type, 0, 0), + JS_FN("AutoCharInvalidType", AutoChar_invalid_type, 0, 0), JS_FN("UniqueCharsInvalidType", UniqueChars_invalid_type, 0, 0), JS_FN("JSStringInvalidType", JSString_invalid_type, 0, 0), JS_FN("objectInvalidType", object_invalid_type, 0, 0), @@ -343,7 +338,7 @@ run_code_expect_exception(GjsUnitTestFixture *fx, JS::RootedValue ignored(fx->cx); ok = JS::Evaluate(fx->cx, options, source, &ignored); g_assert_false(ok); - GjsAutoChar message = gjs_test_get_exception_message(fx->cx); + Gjs::AutoChar message{gjs_test_get_exception_message(fx->cx)}; g_assert_nonnull(message); /* Cheap way to shove an expected exception message into the data argument */ @@ -434,8 +429,8 @@ gjs_test_add_tests_for_parse_call_args(void) "doubleInvalidType(false)" "//*Wrong type for b, got double?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-autochar-type", - "GjsAutoCharInvalidType(1)" - "//*Wrong type for i, got GjsAutoChar?"); + "AutoCharInvalidType(1)" + "//*Wrong type for i, got Gjs::AutoChar?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-autojschar-type", "UniqueCharsInvalidType(1)" "//*Wrong type for i, got JS::UniqueChars?"); diff --git a/test/gjs-test-common.cpp b/test/gjs-test-common.cpp index 2de4b487e..d2d57f425 100644 --- a/test/gjs-test-common.cpp +++ b/test/gjs-test-common.cpp @@ -15,12 +15,6 @@ #include "test/gjs-test-common.h" -// COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 -#ifdef __clang_analyzer__ -void g_assertion_message(const char*, const char*, int, const char*, - const char*) __attribute__((analyzer_noreturn)); -#endif - char* gjs_test_get_exception_message(JSContext* cx) { if (!JS_IsExceptionPending(cx)) return nullptr; diff --git a/test/gjs-test-coverage.cpp b/test/gjs-test-coverage.cpp index 756d0edc1..520417f76 100644 --- a/test/gjs-test-coverage.cpp +++ b/test/gjs-test-coverage.cpp @@ -14,15 +14,10 @@ #include #include +#include "cjs/auto.h" #include "cjs/context.h" #include "cjs/coverage.h" -#include "cjs/jsapi-util.h" - -// COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 -#ifdef __clang_analyzer__ -void g_assertion_message(const char*, const char*, int, const char*, - const char*) __attribute__((analyzer_noreturn)); -#endif +#include "cjs/gerror-result.h" typedef struct _GjsCoverageFixture { GjsContext *context; @@ -38,7 +33,7 @@ static void replace_file(GFile *file, const char *contents) { - GjsAutoError error; + Gjs::AutoError error; g_file_replace_contents(file, contents, strlen(contents), NULL /* etag */, FALSE /* make backup */, G_FILE_CREATE_NONE, NULL /* etag out */, NULL /* cancellable */, &error); @@ -196,7 +191,7 @@ assert_coverage_data_contains_value_for_key(const char *data, g_assert_nonnull(sf_line); - GjsAutoChar actual = g_strndup(&sf_line[strlen(key)], strlen(value)); + Gjs::AutoChar actual{g_strndup(&sf_line[strlen(key)], strlen(value))}; g_assert_cmpstr(value, ==, actual); } @@ -637,7 +632,7 @@ has_function_name(const char *line, while (*(line - 1) != ',') ++line; - GjsAutoChar actual = g_strndup(line, strlen(expected_function_name)); + Gjs::AutoChar actual{g_strndup(line, strlen(expected_function_name))}; g_assert_cmpstr(actual, ==, expected_function_name); } @@ -682,7 +677,7 @@ has_function_line(const char *line, /* Advance past "FN:" */ line += 3; - GjsAutoChar actual = g_strndup(line, strlen(expected_function_line)); + Gjs::AutoChar actual{g_strndup(line, strlen(expected_function_line))}; g_assert_cmpstr(actual, ==, expected_function_line); } @@ -737,9 +732,9 @@ hit_count_is_more_than_for_function(const char *line, max_buf_size = strcspn(line, "\n"); detected_function = g_new(char, max_buf_size + 1); - GjsAutoChar format_string = g_strdup_printf("%%5u,%%%zus", max_buf_size); + Gjs::AutoChar format_string{g_strdup_printf("%%5u,%%%zus", max_buf_size)}; -// clang-format off + // clang-format off #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") @@ -1228,87 +1223,87 @@ void gjs_test_add_tests_for_coverage() gjs_coverage_fixture_tear_down }; - add_test_for_fixture("/cjs/coverage/file_duplicated_into_output_path", + add_test_for_fixture("/gjs/coverage/file_duplicated_into_output_path", &coverage_fixture, test_covered_file_is_duplicated_into_output_if_path, NULL); - add_test_for_fixture("/cjs/coverage/file_duplicated_full_resource_path", + add_test_for_fixture("/gjs/coverage/file_duplicated_full_resource_path", &coverage_fixture, test_covered_file_is_duplicated_into_output_if_resource, NULL); - add_test_for_fixture("/cjs/coverage/contents_preserved_accumulate_mode", + add_test_for_fixture("/gjs/coverage/contents_preserved_accumulate_mode", &coverage_fixture, test_previous_contents_preserved, NULL); - add_test_for_fixture("/cjs/coverage/new_contents_appended_accumulate_mode", + add_test_for_fixture("/gjs/coverage/new_contents_appended_accumulate_mode", &coverage_fixture, test_new_contents_written, NULL); - add_test_for_fixture("/cjs/coverage/expected_source_file_name_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/expected_source_file_name_written_to_coverage_data", &coverage_fixture, test_expected_source_file_name_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/entry_not_written_for_nonexistent_file", + add_test_for_fixture("/gjs/coverage/entry_not_written_for_nonexistent_file", &coverage_fixture, test_expected_entry_not_written_for_nonexistent_file, NULL); - add_test_for_fixture("/cjs/coverage/single_branch_coverage_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/single_branch_coverage_written_to_coverage_data", &coverage_fixture, test_single_branch_coverage_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/multiple_branch_coverage_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/multiple_branch_coverage_written_to_coverage_data", &coverage_fixture, test_multiple_branch_coverage_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/branches_for_multiple_case_statements_fallthrough", + add_test_for_fixture("/gjs/coverage/branches_for_multiple_case_statements_fallthrough", &coverage_fixture, test_branches_for_multiple_case_statements_fallthrough, NULL); - add_test_for_fixture("/cjs/coverage/not_hit_branch_point_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/not_hit_branch_point_written_to_coverage_data", &coverage_fixture, test_branch_not_hit_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/function_names_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/function_names_written_to_coverage_data", &coverage_fixture, test_function_names_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/function_lines_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/function_lines_written_to_coverage_data", &coverage_fixture, test_function_lines_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/function_hit_counts_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/big_function_hit_counts_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/big_function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_for_big_functions_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/little_function_hit_counts_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/little_function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_for_little_functions_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/total_function_coverage_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/total_function_coverage_written_to_coverage_data", &coverage_fixture, test_total_function_coverage_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/single_line_hit_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/single_line_hit_written_to_coverage_data", &coverage_fixture, test_single_line_hit_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/hits_on_multiline_if_cond", + add_test_for_fixture("/gjs/coverage/hits_on_multiline_if_cond", &coverage_fixture, test_hits_on_multiline_if_cond, NULL); - add_test_for_fixture("/cjs/coverage/full_line_tally_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/full_line_tally_written_to_coverage_data", &coverage_fixture, test_full_line_tally_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/no_hits_for_unexecuted_file", + add_test_for_fixture("/gjs/coverage/no_hits_for_unexecuted_file", &coverage_fixture, test_no_hits_to_coverage_data_for_unexecuted, NULL); - add_test_for_fixture("/cjs/coverage/end_of_record_section_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/end_of_record_section_written_to_coverage_data", &coverage_fixture, test_end_of_record_section_written_to_coverage_data, NULL); @@ -1319,11 +1314,11 @@ void gjs_test_add_tests_for_coverage() gjs_coverage_multiple_source_files_to_single_output_fixture_tear_down }; - add_test_for_fixture("/cjs/coverage/multiple_source_file_records_written_to_coverage_data", + add_test_for_fixture("/gjs/coverage/multiple_source_file_records_written_to_coverage_data", &coverage_for_multiple_files_to_single_output_fixture, test_multiple_source_file_records_written_to_coverage_data, NULL); - add_test_for_fixture("/cjs/coverage/correct_line_coverage_data_written_for_both_sections", + add_test_for_fixture("/gjs/coverage/correct_line_coverage_data_written_for_both_sections", &coverage_for_multiple_files_to_single_output_fixture, test_correct_line_coverage_data_written_for_both_source_file_sections, NULL); diff --git a/test/gjs-test-jsapi-utils.cpp b/test/gjs-test-jsapi-utils.cpp index f52cb2850..9c3a4eac0 100644 --- a/test/gjs-test-jsapi-utils.cpp +++ b/test/gjs-test-jsapi-utils.cpp @@ -14,7 +14,9 @@ #include #include -#include "cjs/jsapi-util.h" +#include "cjs/auto.h" +#include "cjs/gerror-result.h" +#include "test/gjs-test-utils.h" struct _GjsTestObject { GObject parent_instance; @@ -49,7 +51,7 @@ static void teardown(Fixture* fx, const void*) { } using GjsAutoTestObject = - GjsAutoPointer; + Gjs::AutoPointer; static void test_gjs_autopointer_size() { g_assert_cmpuint(sizeof(GjsAutoTestObject), ==, sizeof(GjsTestObject*)); @@ -68,7 +70,7 @@ static void test_gjs_autopointer_ctor_basic(Fixture* fx, const void*) { } static void test_gjs_autopointer_ctor_take_ownership(Fixture* fx, const void*) { - GjsAutoTestObject autoptr(fx->ptr, GjsAutoTakeOwnership()); + GjsAutoTestObject autoptr{fx->ptr, Gjs::TakeOwnership{}}; g_assert_true(autoptr == fx->ptr); g_assert_true(autoptr.get() == fx->ptr); g_object_unref(fx->ptr); @@ -118,7 +120,7 @@ static void test_gjs_autopointer_dtor_cpp() { { auto* ptr = new TestStruct(dtor_callback); - GjsAutoCppPointer autoptr(ptr); + Gjs::AutoCppPointer autoptr{ptr}; g_assert_true(ptr == autoptr); } @@ -141,12 +143,12 @@ static void test_gjs_autopointer_dtor_cpp_array() { g_assert_cmpint(deleted, ==, 0); { - // using GjsAutoCppPointer1 = GjsAutoPointer>; + // using GjsAutoCppPointer1 = Gjs::AutoPointer>; TestStruct* ptrs = new TestStruct[3]{dtor_callback, dtor_callback, dtor_callback}; - GjsAutoCppPointer autoptr(ptrs); + Gjs::AutoCppPointer autoptr{ptrs}; g_assert_cmpint(autoptr[0].val, ==, 5); g_assert_cmpint(autoptr[1].val, ==, 5); g_assert_cmpint(autoptr[2].val, ==, 5); @@ -162,7 +164,7 @@ static void test_gjs_autopointer_dtor_cpp_array() { g_assert_cmpint(test_struct_1.val, ==, 3); int* int_ptrs = new int[3]{5, 6, 7}; - GjsAutoCppPointer int_autoptr(int_ptrs); + Gjs::AutoCppPointer 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); @@ -173,7 +175,7 @@ static void test_gjs_autopointer_dtor_cpp_array() { static void test_gjs_autopointer_dtor_take_ownership(Fixture* fx, const void*) { { - GjsAutoTestObject autoptr(fx->ptr, GjsAutoTakeOwnership()); + GjsAutoTestObject autoptr{fx->ptr, Gjs::TakeOwnership{}}; g_assert_true(autoptr == fx->ptr); g_assert_true(autoptr.get() == fx->ptr); } @@ -183,13 +185,13 @@ static void test_gjs_autopointer_dtor_take_ownership(Fixture* fx, const void*) { } static void test_gjs_autopointer_dtor_default_free() { - GjsAutoPointer autoptr(g_strdup("Please, FREE ME!")); + Gjs::AutoPointer autoptr{g_strdup("Please, FREE ME!")}; g_assert_cmpstr(autoptr, ==, "Please, FREE ME!"); } static void test_gjs_autopointer_dtor_no_free_pointer() { const char* str = "DO NOT FREE ME"; - GjsAutoPointer autoptr(const_cast(str)); + Gjs::AutoPointer autoptr{const_cast(str)}; g_assert_cmpstr(autoptr, ==, "DO NOT FREE ME"); } @@ -201,7 +203,7 @@ static GObject* gobject_copy(GObject* p) { static void test_gjs_autopointer_cast_free_func_type() { // No assertions; this test fails to compile if the casts are wrong using TypedAutoPointer = - GjsAutoPointer; + Gjs::AutoPointer; TypedAutoPointer autoptr{gjs_test_object_new()}; TypedAutoPointer copy{autoptr.copy()}; } @@ -365,7 +367,7 @@ static void test_gjs_autopointer_assign_operator_bool(Fixture* fx, static void test_gjs_autopointer_assign_operator_array() { auto* ptrs = g_new0(GjsTestObject, 5); - GjsAutoPointer autopointers(ptrs); + Gjs::AutoPointer autopointers{ptrs}; for (int i = 0; i < 5; i++) { autopointers[i].stuff = i; @@ -502,7 +504,7 @@ static void test_gjs_autopointer_as() { static void test_gjs_autochar_init() { char* str = g_strdup("FoooBar"); - GjsAutoChar autoptr = str; + Gjs::AutoChar autoptr = str; g_assert_cmpstr(autoptr, ==, "FoooBar"); g_assert_cmpuint(autoptr[4], ==, 'B'); @@ -511,7 +513,7 @@ static void test_gjs_autochar_init() { static void test_gjs_autochar_init_take_ownership() { const char* str = "FoooBarConst"; - GjsAutoChar autoptr(str, GjsAutoTakeOwnership()); + Gjs::AutoChar autoptr{str, Gjs::TakeOwnership{}}; g_assert_cmpstr(autoptr, ==, str); g_assert_cmpuint(autoptr[4], ==, 'B'); @@ -519,7 +521,7 @@ static void test_gjs_autochar_init_take_ownership() { } static void test_gjs_autochar_copy() { - GjsAutoChar autoptr = g_strdup("FoooBar"); + Gjs::AutoChar autoptr{g_strdup("FoooBar")}; char* copy = autoptr.copy(); g_assert_cmpstr(autoptr, ==, copy); @@ -530,7 +532,7 @@ static void test_gjs_autochar_copy() { static void test_gjs_autostrv_init() { const char* strv[] = {"FOO", "Bar", "BAZ", nullptr}; - GjsAutoStrv autoptr = g_strdupv(const_cast(strv)); + Gjs::AutoStrv autoptr{g_strdupv(const_cast(strv))}; g_assert_true(g_strv_equal(strv, autoptr)); @@ -540,7 +542,7 @@ static void test_gjs_autostrv_init() { static void test_gjs_autostrv_init_take_ownership() { const char* strv[] = {"FOO", "Bar", "BAZ", nullptr}; - GjsAutoStrv autoptr(const_cast(strv), GjsAutoTakeOwnership()); + Gjs::AutoStrv autoptr{const_cast(strv), Gjs::TakeOwnership{}}; for (int i = g_strv_length(const_cast(strv)); i >= 0; i--) g_assert_cmpstr(autoptr[i], ==, strv[i]); @@ -549,7 +551,7 @@ static void test_gjs_autostrv_init_take_ownership() { static void test_gjs_autostrv_copy() { const char* strv[] = {"FOO", "Bar", "BAZ", nullptr}; - GjsAutoStrv autoptr = g_strdupv(const_cast(strv)); + Gjs::AutoStrv autoptr{g_strdupv(const_cast(strv))}; char** copy = autoptr.copy(); for (int i = g_strv_length(const_cast(strv)); i >= 0; i--) @@ -560,7 +562,7 @@ static void test_gjs_autostrv_copy() { } static void test_gjs_autotypeclass_init() { - GjsAutoTypeClass autoclass(gjs_test_object_get_type()); + Gjs::AutoTypeClass autoclass{gjs_test_object_get_type()}; g_assert_nonnull(autoclass); g_assert_cmpint(autoclass->g_type_class.g_type, ==, @@ -568,8 +570,8 @@ static void test_gjs_autotypeclass_init() { } static void test_gjs_error_init() { - GjsAutoError error = - g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_EXIST, "Message"); + Gjs::AutoError 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); @@ -582,8 +584,8 @@ static void test_gjs_error_init() { } static void test_gjs_error_out() { - GjsAutoError error( - g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_EXIST, "Message")); + Gjs::AutoError error{ + g_error_new_literal(G_FILE_ERROR, G_FILE_ERROR_EXIST, "Message")}; g_clear_error(&error); g_assert_null(error); } @@ -592,108 +594,108 @@ static void test_gjs_error_out() { g_test_add(path, Fixture, nullptr, setup, func, teardown); void gjs_test_add_tests_for_jsapi_utils(void) { - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/size", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/size", test_gjs_autopointer_size); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/constructor/empty", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/constructor/empty", test_gjs_autopointer_ctor_empty); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/constructor/basic", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/constructor/basic", test_gjs_autopointer_ctor_basic); ADD_AUTOPTRTEST( - "/cjs/jsapi-utils/gjs-autopointer/constructor/take_ownership", + "/gjs/jsapi-utils/gjs-autopointer/constructor/take_ownership", test_gjs_autopointer_ctor_take_ownership); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/constructor/assignment", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/constructor/assignment", test_gjs_autopointer_ctor_assign); ADD_AUTOPTRTEST( - "/cjs/jsapi-utils/gjs-autopointer/constructor/assignment/other", + "/gjs/jsapi-utils/gjs-autopointer/constructor/assignment/other", test_gjs_autopointer_ctor_assign_other); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/destructor", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/destructor", test_gjs_autopointer_dtor); ADD_AUTOPTRTEST( - "/cjs/jsapi-utils/gjs-autopointer/destructor/take_ownership", + "/gjs/jsapi-utils/gjs-autopointer/destructor/take_ownership", test_gjs_autopointer_dtor_take_ownership); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/destructor/default_free", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/destructor/default_free", test_gjs_autopointer_dtor_default_free); g_test_add_func( - "/cjs/jsapi-utils/gjs-autopointer/destructor/no_free_pointer", + "/gjs/jsapi-utils/gjs-autopointer/destructor/no_free_pointer", test_gjs_autopointer_dtor_no_free_pointer); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/free_and_ref_funcs", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/free_and_ref_funcs", test_gjs_autopointer_cast_free_func_type); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/destructor/c++", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/destructor/c++", test_gjs_autopointer_dtor_cpp); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/destructor/c++-array", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/destructor/c++-array", test_gjs_autopointer_dtor_cpp_array); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/operator/assign", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/operator/assign", test_gjs_autopointer_assign_operator); g_test_add_func( - "/cjs/jsapi-utils/gjs-autopointer/operator/assign/other_ptr", + "/gjs/jsapi-utils/gjs-autopointer/operator/assign/other_ptr", test_gjs_autopointer_assign_operator_other_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/assign/self_ptr", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/assign/self_ptr", test_gjs_autopointer_assign_operator_self_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/assign/object", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/assign/object", test_gjs_autopointer_assign_operator_object); g_test_add_func( - "/cjs/jsapi-utils/gjs-autopointer/operator/assign/other_object", + "/gjs/jsapi-utils/gjs-autopointer/operator/assign/other_object", test_gjs_autopointer_assign_operator_other_object); ADD_AUTOPTRTEST( - "/cjs/jsapi-utils/gjs-autopointer/operator/assign/self_object", + "/gjs/jsapi-utils/gjs-autopointer/operator/assign/self_object", test_gjs_autopointer_assign_operator_self_object); ADD_AUTOPTRTEST( - "/cjs/jsapi-utils/gjs-autopointer/operator/assign/copy_and_swap", + "/gjs/jsapi-utils/gjs-autopointer/operator/assign/copy_and_swap", test_gjs_autopointer_assign_operator_copy_and_swap); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/move", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/move", test_gjs_autopointer_operator_move); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/swap", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/swap", test_gjs_autopointer_operator_swap); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/arrow", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/arrow", test_gjs_autopointer_assign_operator_arrow); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/deference", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/deference", test_gjs_autopointer_assign_operator_deference); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/operator/bool", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/operator/bool", test_gjs_autopointer_assign_operator_bool); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/operator/array", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/operator/array", test_gjs_autopointer_assign_operator_array); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/get", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/get", test_gjs_autopointer_get); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/out", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/out", test_gjs_autopointer_out); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/release", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/release", test_gjs_autopointer_release); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/reset/nullptr", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/reset/nullptr", test_gjs_autopointer_reset_nullptr); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/method/reset/other_ptr", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/method/reset/other_ptr", test_gjs_autopointer_reset_other_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/reset/self_ptr", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/reset/self_ptr", test_gjs_autopointer_reset_self_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/swap/other_ptr", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/swap/other_ptr", test_gjs_autopointer_swap_other_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/swap/self_ptr", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/swap/self_ptr", test_gjs_autopointer_swap_self_ptr); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/swap/empty", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/swap/empty", test_gjs_autopointer_swap_empty); - ADD_AUTOPTRTEST("/cjs/jsapi-utils/gjs-autopointer/method/copy", + ADD_AUTOPTRTEST("/gjs/jsapi-utils/gjs-autopointer/method/copy", test_gjs_autopointer_copy); - g_test_add_func("/cjs/jsapi-utils/gjs-autopointer/method/as", + g_test_add_func("/gjs/jsapi-utils/gjs-autopointer/method/as", test_gjs_autopointer_as); // Other implementations - g_test_add_func("/cjs/jsapi-utils/gjs-autochar/init", + g_test_add_func("/gjs/jsapi-utils/gjs-autochar/init", test_gjs_autochar_init); - g_test_add_func("/cjs/jsapi-utils/gjs-autochar/init/take_ownership", + g_test_add_func("/gjs/jsapi-utils/gjs-autochar/init/take_ownership", test_gjs_autochar_init_take_ownership); - g_test_add_func("/cjs/jsapi-utils/gjs-autochar/copy", + g_test_add_func("/gjs/jsapi-utils/gjs-autochar/copy", test_gjs_autochar_copy); - g_test_add_func("/cjs/jsapi-utils/gjs-autostrv/init", + g_test_add_func("/gjs/jsapi-utils/gjs-autostrv/init", test_gjs_autostrv_init); - g_test_add_func("/cjs/jsapi-utils/gjs-autostrv/init/take_ownership", + g_test_add_func("/gjs/jsapi-utils/gjs-autostrv/init/take_ownership", test_gjs_autostrv_init_take_ownership); - g_test_add_func("/cjs/jsapi-utils/gjs-autostrv/copy", + g_test_add_func("/gjs/jsapi-utils/gjs-autostrv/copy", test_gjs_autostrv_copy); - g_test_add_func("/cjs/jsapi-utils/gjs-autotypeclass/init", + g_test_add_func("/gjs/jsapi-utils/gjs-autotypeclass/init", test_gjs_autotypeclass_init); - g_test_add_func("/cjs/jsapi-utils/gjs-autoerror/init", test_gjs_error_init); - g_test_add_func("/cjs/jsapi-utils/gjs-autoerror/as-out-value", + 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-rooting.cpp b/test/gjs-test-rooting.cpp index 0eab6b3c4..94dc3c46d 100644 --- a/test/gjs-test-rooting.cpp +++ b/test/gjs-test-rooting.cpp @@ -3,16 +3,12 @@ #include -#include // for size_t - #include -#include #include // for JS_GC, JS_SetGCCallback, JSGCStatus -#include +#include #include -#include -#include // for JS_NewObject +#include #include "cjs/context-private.h" #include "cjs/jsapi-util-root.h" @@ -20,19 +16,10 @@ class JSTracer; -// COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 -#ifdef __clang_analyzer__ -void g_assertion_message(const char*, const char*, int, const char*, - const char*) __attribute__((analyzer_noreturn)); -#endif - static GMutex gc_lock; static GCond gc_finished; static int gc_counter; -// TestObj reserved slots -static const size_t POINTER = 0; - #define PARENT(fx) ((GjsUnitTestFixture *)fx) struct GjsRootingFixture { GjsUnitTestFixture parent; @@ -43,31 +30,14 @@ struct GjsRootingFixture { GjsMaybeOwned* obj; // only used in callback test cases }; -static void test_obj_finalize(JS::GCContext*, JSObject* obj) { - bool* finalized_p = JS::GetMaybePtrFromReservedSlot(obj, POINTER); - g_assert_false(*finalized_p); - *finalized_p = true; -} - -static const JSClassOps test_obj_class_ops = { - nullptr, // addProperty - nullptr, // deleteProperty - nullptr, // enumerate - nullptr, // newEnumerate - nullptr, // resolve - nullptr, // mayResolve - test_obj_finalize}; - -static JSClass test_obj_class = { - "TestObj", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, - &test_obj_class_ops}; - static JSObject * test_obj_new(GjsRootingFixture *fx) { - JSObject *retval = JS_NewObject(PARENT(fx)->cx, &test_obj_class); - JS::SetReservedSlot(retval, POINTER, JS::PrivateValue(&fx->finalized)); - return retval; + return JS::NewObjectWithStashedPointer(PARENT(fx)->cx, fx, + [](GjsRootingFixture* data) { + g_assert_false(data->finalized); + data->finalized = true; + }); } static void on_gc(JSContext*, JSGCStatus status, JS::GCReason, void*) { diff --git a/test/gjs-test-toggle-queue.cpp b/test/gjs-test-toggle-queue.cpp index 66655a371..699df4a65 100644 --- a/test/gjs-test-toggle-queue.cpp +++ b/test/gjs-test-toggle-queue.cpp @@ -13,7 +13,7 @@ #include // for tie #include // for pair -#include +#include #include #include @@ -22,8 +22,9 @@ #include "gi/object.h" #include "gi/toggle.h" +#include "cjs/auto.h" #include "cjs/context.h" -#include "cjs/jsapi-util.h" +#include "cjs/gerror-result.h" #include "installed-tests/js/libgjstesttools/gjs-test-tools.h" #include "test/gjs-test-utils.h" @@ -71,12 +72,13 @@ static void on_gc(JSContext*, JSGCStatus status, JS::GCReason, void*) { } static void setup(GjsUnitTestFixture* fx, const void*) { - g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR")); gjs_test_tools_init(); gjs_unit_test_fixture_setup(fx, nullptr); + AutoUnref repo = gi_repository_dup_default(); + gi_repository_prepend_search_path(repo, g_getenv("TOP_BUILDDIR")); JS_SetGCCallback(fx->cx, on_gc, fx); - GjsAutoError error; + AutoError error; int code; const char* gi_initializer = "imports.gi;"; @@ -112,16 +114,15 @@ static void teardown(GjsUnitTestFixture* fx, const void*) { } // namespace TQ static ::ObjectInstance* new_test_gobject(GjsUnitTestFixture* fx) { - GjsAutoUnref gobject( - G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr))); + AutoUnref gobject{G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr))}; auto* object = ObjectInstance::new_for_gobject(fx->cx, gobject); static_cast(object)->ensure_uses_toggle_ref(fx->cx); return object; } static void wait_for(int interval) { - GjsAutoPointer loop( - g_main_loop_new(nullptr, false)); + AutoPointer loop{ + g_main_loop_new(nullptr, false)}; g_timeout_add_full( G_PRIORITY_LOW, interval, [](void* data) { @@ -391,7 +392,7 @@ static void test_toggle_queue_object_from_main_thread(GjsUnitTestFixture* fx, auto* instance = new_test_gobject(fx); auto tq = ToggleQueue::get_default(); - GjsAutoUnref reffed(instance->ptr(), GjsAutoTakeOwnership()); + AutoUnref reffed{instance->ptr(), TakeOwnership{}}; bool toggle_down_queued, toggle_up_queued; std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance); @@ -405,8 +406,8 @@ static void test_toggle_queue_object_from_main_thread(GjsUnitTestFixture* fx, static void test_toggle_queue_object_from_main_thread_already_enqueued( GjsUnitTestFixture* fx, const void*) { auto* instance = new_test_gobject(fx); - GjsAutoUnref reffed; - GjsAutoError error; + AutoUnref reffed; + AutoError error; reffed = instance->ptr(); gjs_test_tools_ref_other_thread(reffed, &error); @@ -430,8 +431,8 @@ static void test_toggle_queue_object_from_main_thread_already_enqueued( static void test_toggle_queue_object_from_main_thread_unref_already_enqueued( GjsUnitTestFixture* fx, const void*) { auto* instance = new_test_gobject(fx); - GjsAutoUnref reffed; - GjsAutoError error; + AutoUnref reffed; + AutoError error; reffed = instance->ptr(); gjs_test_tools_ref_other_thread(reffed, &error); @@ -457,7 +458,7 @@ static void test_toggle_queue_object_from_other_thread_ref_unref( GjsUnitTestFixture* fx, const void*) { auto* instance = new_test_gobject(fx); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -483,10 +484,10 @@ static void test_toggle_queue_object_handle_up(GjsUnitTestFixture* fx, auto* instance = new_test_gobject(fx); auto* instance_test = reinterpret_cast(instance); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); - GjsAutoUnref reffed(instance->ptr()); + AutoUnref reffed{instance->ptr()}; assert_equal(ToggleQueue::queue().size(), 1LU); assert_equal(ToggleQueue::queue().at(0).direction, ::ToggleQueue::Direction::UP); @@ -502,7 +503,7 @@ static void test_toggle_queue_object_handle_up_down(GjsUnitTestFixture* fx, auto* instance = new_test_gobject(fx); auto* instance_test = reinterpret_cast(instance); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -524,7 +525,7 @@ static void test_toggle_queue_object_handle_up_down_delayed( auto* instance = new_test_gobject(fx); auto* instance_test = reinterpret_cast(instance); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -552,7 +553,7 @@ static void test_toggle_queue_object_handle_up_down_on_gc( GjsUnitTestFixture* fx, const void*) { auto* instance = new_test_gobject(fx); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); assert_equal(ToggleQueue::queue().size(), 1LU); @@ -578,10 +579,10 @@ static void test_toggle_queue_object_handle_many_up(GjsUnitTestFixture* fx, auto* instance = new_test_gobject(fx); auto* instance_test = reinterpret_cast(instance); - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); - GjsAutoUnref reffed(instance->ptr()); + AutoUnref reffed{instance->ptr()}; // Simulating the case where late threads are causing this... ToggleQueue::get_default()->enqueue(instance, ::ToggleQueue::Direction::UP, ToggleQueue().handler()); @@ -604,7 +605,7 @@ static void test_toggle_queue_object_handle_many_up_and_down( auto* instance_test = reinterpret_cast(instance); // This is something similar to what is happening on #297 - GjsAutoError error; + AutoError error; gjs_test_tools_ref_other_thread(instance->ptr(), &error); g_assert_no_error(error); ToggleQueue::get_default()->enqueue(instance, ::ToggleQueue::Direction::UP, diff --git a/test/gjs-test-utils.cpp b/test/gjs-test-utils.cpp index 3c14821b3..e99210b01 100644 --- a/test/gjs-test-utils.cpp +++ b/test/gjs-test-utils.cpp @@ -12,9 +12,9 @@ #include #include +#include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" -#include "cjs/jsapi-util.h" #include "test/gjs-test-common.h" #include "test/gjs-test-utils.h" @@ -29,7 +29,7 @@ void gjs_unit_test_fixture_setup(GjsUnitTestFixture* fx, const void*) { void gjs_unit_test_destroy_context(GjsUnitTestFixture *fx) { - GjsAutoChar message = gjs_test_get_exception_message(fx->cx); + Gjs::AutoChar message = gjs_test_get_exception_message(fx->cx); if (message) g_printerr("**\n%s\n", message.get()); diff --git a/test/gjs-test-utils.h b/test/gjs-test-utils.h index e3f8bdd40..db8c8f611 100644 --- a/test/gjs-test-utils.h +++ b/test/gjs-test-utils.h @@ -10,7 +10,6 @@ #include // for uintptr_t -#include // for pair #include // for numeric_limits #include #include // for is_same diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp index 6a2bba81a..53e081e3c 100644 --- a/test/gjs-tests.cpp +++ b/test/gjs-tests.cpp @@ -12,7 +12,7 @@ #include // for u16string, u32string #include -#include +#include #include #include #include // for g_unlink @@ -37,8 +37,10 @@ #include "gi/arg-inl.h" #include "gi/js-value-inl.h" +#include "cjs/auto.h" #include "cjs/context.h" #include "cjs/error-types.h" +#include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/profiler.h" #include "test/gjs-test-no-introspection-object.h" @@ -49,11 +51,14 @@ 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*, - const char*) __attribute__((analyzer_noreturn)); -#endif +namespace Gjs { +namespace Tag { +struct Enum; +struct GBoolean; +struct GType; +struct UnsignedEnum; +} // namespace Tag +} // namespace Gjs #define VALID_UTF8_STRING "\303\211\303\226 foobar \343\203\237" @@ -125,7 +130,7 @@ gjstest_test_func_gjs_context_construct_eval(void) { GjsContext *context; int estatus; - GjsAutoError error; + AutoError error; context = gjs_context_new (); if (!gjs_context_eval (context, "1+1", -1, "", &estatus, &error)) @@ -134,8 +139,8 @@ gjstest_test_func_gjs_context_construct_eval(void) } static void gjstest_test_func_gjs_context_eval_dynamic_import() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; int status; bool ok = gjs_context_eval(gjs, R"js( @@ -151,8 +156,8 @@ 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(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; int status; bool ok = g_file_set_contents("num.js", "export default 77;", -1, &error); @@ -179,12 +184,12 @@ 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(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; int status; - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, - "*Unknown module: 'badmodule'*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, + "*ImportError*badmodule*"); bool ok = gjs_context_eval(gjs, R"js( let isBad = false; @@ -207,8 +212,8 @@ static void gjstest_test_func_gjs_context_eval_dynamic_import_bad() { } static void gjstest_test_func_gjs_context_eval_non_zero_terminated(void) { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; int status; // This string is invalid JS if it is treated as zero-terminated @@ -223,7 +228,7 @@ static void gjstest_test_func_gjs_context_exit(void) { GjsContext *context = gjs_context_new(); - GjsAutoError error; + AutoError error; int status; bool ok = gjs_context_eval(context, "imports.system.exit(0);", -1, @@ -244,9 +249,9 @@ gjstest_test_func_gjs_context_exit(void) } static void gjstest_test_func_gjs_context_eval_module_file() { - GjsAutoUnref gjs = gjs_context_new(); + AutoUnref gjs{gjs_context_new()}; uint8_t exit_status; - GjsAutoError error; + AutoError error; bool ok = gjs_context_eval_module_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/default.js", @@ -259,11 +264,11 @@ 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(); + AutoUnref gjs{gjs_context_new()}; uint8_t exit_status; - GjsAutoError error; + AutoError error; - g_test_expect_message("Cjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); bool ok = gjs_context_eval_module_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/throws.js", @@ -277,8 +282,8 @@ static void gjstest_test_func_gjs_context_eval_module_file_throw() { } static void gjstest_test_func_gjs_context_eval_module_file_exit() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; uint8_t exit_status; bool ok = gjs_context_eval_module_file( @@ -301,11 +306,11 @@ static void gjstest_test_func_gjs_context_eval_module_file_exit() { } static void gjstest_test_func_gjs_context_eval_module_file_fail_instantiate() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; uint8_t exit_status; - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, "*foo*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "*foo*"); // evaluating this module without registering 'foo' first should make it // fail ModuleLink @@ -321,10 +326,10 @@ static void gjstest_test_func_gjs_context_eval_module_file_fail_instantiate() { } static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_warning() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, "*foo*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "*foo*"); bool ok = gjs_context_eval_module_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/import.js", nullptr, @@ -338,8 +343,8 @@ static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_war static void gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_no_warning() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_eval_module_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/default.js", nullptr, @@ -350,10 +355,10 @@ gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_no_warning() { } static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_throw() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; - g_test_expect_message("Cjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*bad module*"); bool ok = gjs_context_eval_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/throws.js", nullptr, @@ -366,8 +371,8 @@ static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_throw() { } static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_no_throw() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_eval_file( gjs, "resource:///org/cinnamon/cjs/mock/test/modules/nothrows.js", nullptr, @@ -378,8 +383,8 @@ static void gjstest_test_func_gjs_context_eval_file_exit_code_omitted_no_throw() } static void gjstest_test_func_gjs_context_register_module_eval_module() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_register_module( gjs, "foo", "resource:///org/cinnamon/cjs/mock/test/modules/default.js", @@ -397,8 +402,8 @@ 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(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_register_module( gjs, "foo", "resource:///org/cinnamon/cjs/mock/test/modules/default.js", @@ -419,7 +424,7 @@ static void gjstest_test_func_gjs_context_register_module_eval_module_file() { static void gjstest_test_func_gjs_context_register_module_eval_jsapi( GjsUnitTestFixture* fx, const void*) { - GjsAutoError error; + AutoError error; bool ok = gjs_context_register_module( fx->gjs_context, "foo", @@ -475,15 +480,15 @@ static void gjstest_test_func_gjs_context_register_module_eval_jsapi_rel( JS::RootedValue unused{fx->cx}; ok = JS::Evaluate(fx->cx, options, source, &unused); g_assert_false(ok); - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, + 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(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_register_module(gjs, "foo", "nonexist.js", &error); @@ -492,8 +497,8 @@ static void gjstest_test_func_gjs_context_register_module_non_existent() { } static void gjstest_test_func_gjs_context_eval_module_unregistered() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; uint8_t exit_status; bool ok = gjs_context_eval_module(gjs, "foo", &exit_status, &error); @@ -504,8 +509,8 @@ static void gjstest_test_func_gjs_context_eval_module_unregistered() { } static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_throw() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_eval_module(gjs, "foo", nullptr, &error); @@ -514,8 +519,8 @@ static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_throw() } static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; bool ok = gjs_context_register_module( gjs, "lies", "resource:///org/cinnamon/cjs/mock/test/modules/nothrows.js", @@ -532,7 +537,7 @@ static void gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw static void gjstest_test_func_gjs_context_module_eval_jsapi_throws( GjsUnitTestFixture* fx, const void*) { - GjsAutoError error; + AutoError error; bool ok = gjs_context_register_module( fx->gjs_context, "foo", @@ -574,7 +579,7 @@ static void gjstest_test_func_gjs_context_module_eval_jsapi_throws( } static void gjstest_test_func_gjs_context_run_in_realm() { - GjsAutoUnref gjs = gjs_context_new(); + AutoUnref gjs{gjs_context_new()}; auto* cx = static_cast(gjs_context_get_native_context(gjs)); g_assert_null(JS::GetCurrentRealmOrNull(cx)); @@ -612,7 +617,7 @@ static void gjstest_test_func_gjs_gobject_js_defined_type(void) { GjsContext *context = gjs_context_new(); - GjsAutoError error; + AutoError error; int status; bool ok = gjs_context_eval(context, JS_CLASS, -1, "", &status, &error); g_assert_no_error(error); @@ -629,8 +634,8 @@ gjstest_test_func_gjs_gobject_js_defined_type(void) } static void gjstest_test_func_gjs_gobject_without_introspection(void) { - GjsAutoUnref context = gjs_context_new(); - GjsAutoError error; + AutoUnref context{gjs_context_new()}; + AutoError error; int status; /* Ensure class */ @@ -657,10 +662,10 @@ 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(); - GjsAutoError error; + AutoUnref context{gjs_context_new()}; + AutoError error; - g_test_expect_message("Cjs", G_LOG_LEVEL_CRITICAL, "*wrong code*"); + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, "*wrong code*"); const char bad_js[] = "throw new Error('wrong code');"; @@ -673,8 +678,8 @@ static void gjstest_test_func_gjs_context_eval_exit_code_omitted_throw() { } static void gjstest_test_func_gjs_context_eval_exit_code_omitted_no_throw() { - GjsAutoUnref context = gjs_context_new(); - GjsAutoError error; + AutoUnref context{gjs_context_new()}; + AutoError error; const char good_js[] = "let num = 77;"; @@ -742,7 +747,7 @@ static void gjstest_test_func_gjs_jsapi_util_error_throw(GjsUnitTestFixture* fx, static void test_jsapi_util_error_throw_cause(GjsUnitTestFixture* fx, const void*) { - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "JS ERROR: Error: Exception 1\n" "Caused by: Error: Exception 2"); @@ -750,7 +755,7 @@ static void test_jsapi_util_error_throw_cause(GjsUnitTestFixture* fx, gjs_throw(fx->cx, "Exception 2"); gjs_log_exception(fx->cx); - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "JS ERROR: Error: Exception 1\n" "Caused by: Error: Exception 2\n" "Caused by: Error: Exception 3"); @@ -760,7 +765,7 @@ static void test_jsapi_util_error_throw_cause(GjsUnitTestFixture* fx, gjs_throw(fx->cx, "Exception 3"); gjs_log_exception(fx->cx); - g_test_expect_message("Cjs", G_LOG_LEVEL_WARNING, "JS ERROR: 42"); + g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING, "JS ERROR: 42"); JS::RootedValue non_object(fx->cx, JS::Int32Value(42)); JS_SetPendingException(fx->cx, non_object); @@ -931,15 +936,15 @@ gjstest_test_func_util_misc_strv_concat_pointers(void) static void gjstest_test_profiler_start_stop(void) { - GjsAutoUnref context = GJS_CONTEXT( - g_object_new(GJS_TYPE_CONTEXT, "profiler-enabled", TRUE, nullptr)); + AutoUnref context{GJS_CONTEXT( + g_object_new(GJS_TYPE_CONTEXT, "profiler-enabled", TRUE, nullptr))}; GjsProfiler *profiler = gjs_context_get_profiler(context); gjs_profiler_set_filename(profiler, "dont-conflict-with-other-test.syscap"); gjs_profiler_start(profiler); for (size_t ix = 0; ix < 100; ix++) { - GjsAutoError error; + AutoError error; int estatus; #define TESTJS "[1,5,7,1,2,3,67,8].sort()" @@ -993,7 +998,7 @@ static void gjstest_test_args_set_get_unset() { gjs_arg_set(&arg, true); g_assert_true(arg.v_boolean); - gjs_arg_unset(&arg); + gjs_arg_unset(&arg); g_assert_false(arg.v_boolean); int8_t random_int8 = get_random_number(); @@ -1056,43 +1061,42 @@ static void gjstest_test_args_set_get_unset() { assert_equal(arg.v_pointer, random_ptr); assert_equal(gjs_arg_get(&arg), random_ptr); - GjsAutoChar cstr = g_strdup("Gjs argument string"); + AutoChar cstr{g_strdup("Gjs argument string")}; gjs_arg_set(&arg, cstr.get()); assert_equal(arg.v_string, const_cast("Gjs argument string")); assert_equal(static_cast(arg.v_string), static_cast(cstr.get())); - gjs_arg_set(&arg, TRUE); + gjs_arg_set(&arg, TRUE); g_assert_true(arg.v_boolean); - g_assert_true((gjs_arg_get(&arg))); + g_assert_true((gjs_arg_get(&arg))); - gjs_arg_set(&arg, FALSE); + gjs_arg_set(&arg, FALSE); g_assert_false(arg.v_boolean); - g_assert_false((gjs_arg_get(&arg))); + g_assert_false((gjs_arg_get(&arg))); - gjs_arg_set(&arg, TRUE); + gjs_arg_set(&arg, TRUE); g_assert_true(arg.v_boolean); - gjs_arg_unset(&arg); + gjs_arg_unset(&arg); g_assert_false(arg.v_boolean); GType random_gtype = get_random_number(); - gjs_arg_set(&arg, random_gtype); + gjs_arg_set(&arg, random_gtype); if constexpr (std::is_same_v) assert_equal(static_cast(arg.v_size), random_gtype); else if constexpr (std::is_same_v) assert_equal(static_cast(arg.v_ulong), random_gtype); - assert_equal(gjs_arg_get(&arg), random_gtype); + assert_equal(gjs_arg_get(&arg), random_gtype); int random_signed_iface = get_random_number(); - gjs_arg_set(&arg, random_signed_iface); + gjs_arg_set(&arg, random_signed_iface); assert_equal(arg.v_int, random_signed_iface); - assert_equal(gjs_arg_get(&arg), - random_signed_iface); + assert_equal(gjs_arg_get(&arg), random_signed_iface); unsigned random_unsigned_iface = get_random_number(); - gjs_arg_set(&arg, random_unsigned_iface); + gjs_arg_set(&arg, random_unsigned_iface); assert_equal(arg.v_uint, random_unsigned_iface); - assert_equal(gjs_arg_get(&arg), + assert_equal(gjs_arg_get(&arg), random_unsigned_iface); } @@ -1128,8 +1132,8 @@ static void gjstest_test_args_rounded_values() { } static void gjstest_test_func_gjs_context_argv_array() { - GjsAutoUnref gjs = gjs_context_new(); - GjsAutoError error; + AutoUnref gjs{gjs_context_new()}; + AutoError error; int status; const char* argv[1] = {"test"}; @@ -1148,6 +1152,84 @@ static void gjstest_test_func_gjs_context_argv_array() { g_assert_false(ok); } +static void gjstest_test_func_gjs_context_eval_module_source_map() { + AutoUnref gjs = gjs_context_new(); + uint8_t exit_status; + AutoError error; + const char* pattern = + "*get2ndNumber*number.js:2:5 -> number.ts:6:5*numberWork.js:2:13 -> " + "numberWork.ts:3:13*"; + + // separate source map + bool ok = gjs_context_register_module(gjs, + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/separate/numberWork.js", + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/separate/numberWork.js", + &error); + g_assert_true(ok); + + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, pattern); + ok = gjs_context_eval_module(gjs, + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/separate/numberWork.js", + &exit_status, &error); + + g_assert_false(ok); + g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); + g_assert_cmpuint(exit_status, ==, 1); + g_test_assert_expected_messages(); + + // inlined source maps + error = nullptr; + ok = gjs_context_register_module(gjs, + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/inlined/numberWork.js", + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/inlined/numberWork.js", + &error); + g_assert_true(ok); + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, pattern); + ok = gjs_context_eval_module(gjs, + "resource:///org/cinnamon/cjs/mock/test/" + "source-maps/inlined/numberWork.js", + &exit_status, &error); + + g_assert_false(ok); + g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); + g_assert_cmpuint(exit_status, ==, 1); + g_test_assert_expected_messages(); +} + +static void gjstest_test_func_gjs_context_eval_file_source_map() { + AutoUnref gjs = gjs_context_new(); + int exit_status; + AutoError error; + const char* pattern = "*noModule.js:2:9 -> noModule.ts:6:11*"; + const char* separate_test_file = + "resource:///org/cinnamon/cjs/mock/test/source-maps/separate/noModule.js"; + const char* inlined_test_file = + "resource:///org/cinnamon/cjs/mock/test/source-maps/inlined/noModule.js"; + + // separate source map + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, pattern); + bool ok = + gjs_context_eval_file(gjs, separate_test_file, &exit_status, &error); + + g_assert_false(ok); + g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); + g_assert_cmpuint(exit_status, ==, 1); + g_test_assert_expected_messages(); + + // inlined source map + error = nullptr; + g_test_expect_message("Gjs", G_LOG_LEVEL_CRITICAL, pattern); + ok = gjs_context_eval_file(gjs, inlined_test_file, &exit_status, &error); + g_assert_false(ok); + g_assert_error(error, GJS_ERROR, GJS_ERROR_FAILED); + g_assert_cmpuint(exit_status, ==, 1); + g_test_assert_expected_messages(); +} } // namespace Test } // namespace Gjs @@ -1180,49 +1262,49 @@ main(int argc, g_message("Using C++ random seed %u\n", cpp_random_seed); - g_test_add_func("/cjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy); - g_test_add_func("/cjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval); - g_test_add_func("/cjs/context/argv", + g_test_add_func("/gjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy); + g_test_add_func("/gjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval); + g_test_add_func("/gjs/context/argv", gjstest_test_func_gjs_context_argv_array); - g_test_add_func("/cjs/context/eval/dynamic-import", + g_test_add_func("/gjs/context/eval/dynamic-import", gjstest_test_func_gjs_context_eval_dynamic_import); - g_test_add_func("/cjs/context/eval/dynamic-import/relative", + g_test_add_func("/gjs/context/eval/dynamic-import/relative", gjstest_test_func_gjs_context_eval_dynamic_import_relative); - g_test_add_func("/cjs/context/eval/dynamic-import/bad", + g_test_add_func("/gjs/context/eval/dynamic-import/bad", gjstest_test_func_gjs_context_eval_dynamic_import_bad); - g_test_add_func("/cjs/context/eval/non-zero-terminated", + g_test_add_func("/gjs/context/eval/non-zero-terminated", gjstest_test_func_gjs_context_eval_non_zero_terminated); - g_test_add_func("/cjs/context/exit", gjstest_test_func_gjs_context_exit); - g_test_add_func("/cjs/context/eval-module-file", + g_test_add_func("/gjs/context/exit", gjstest_test_func_gjs_context_exit); + g_test_add_func("/gjs/context/eval-module-file", gjstest_test_func_gjs_context_eval_module_file); - g_test_add_func("/cjs/context/eval-module-file/throw", + g_test_add_func("/gjs/context/eval-module-file/throw", gjstest_test_func_gjs_context_eval_module_file_throw); - g_test_add_func("/cjs/context/eval-module-file/exit", + g_test_add_func("/gjs/context/eval-module-file/exit", gjstest_test_func_gjs_context_eval_module_file_exit); g_test_add_func( - "/cjs/context/eval-module-file/fail-instantiate", + "/gjs/context/eval-module-file/fail-instantiate", gjstest_test_func_gjs_context_eval_module_file_fail_instantiate); - g_test_add_func("/cjs/context/register-module/eval-module", + g_test_add_func("/gjs/context/register-module/eval-module", gjstest_test_func_gjs_context_register_module_eval_module); g_test_add_func( - "/cjs/context/register-module/eval-module-file", + "/gjs/context/register-module/eval-module-file", gjstest_test_func_gjs_context_register_module_eval_module_file); - g_test_add("/cjs/context/register-module/eval-jsapi", GjsUnitTestFixture, + 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("/cjs/context/register-module/eval-jsapi-relative", + 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("/cjs/context/register-module/non-existent", + g_test_add_func("/gjs/context/register-module/non-existent", gjstest_test_func_gjs_context_register_module_non_existent); - g_test_add_func("/cjs/context/eval-module/unregistered", + g_test_add_func("/gjs/context/eval-module/unregistered", gjstest_test_func_gjs_context_eval_module_unregistered); - g_test_add_func("/cjs/gobject/js_defined_type", gjstest_test_func_gjs_gobject_js_defined_type); - g_test_add_func("/cjs/gobject/without_introspection", + g_test_add_func("/gjs/gobject/js_defined_type", gjstest_test_func_gjs_gobject_js_defined_type); + g_test_add_func("/gjs/gobject/without_introspection", gjstest_test_func_gjs_gobject_without_introspection); - g_test_add_func("/cjs/profiler/start_stop", gjstest_test_profiler_start_stop); + g_test_add_func("/gjs/profiler/start_stop", gjstest_test_profiler_start_stop); g_test_add_func("/util/misc/strv/concat/null", gjstest_test_func_util_misc_strv_concat_null); g_test_add_func("/util/misc/strv/concat/pointers", @@ -1233,33 +1315,38 @@ main(int argc, gjstest_test_args_rounded_values); g_test_add_func( - "/cjs/context/eval-module-file/exit-code-omitted-warning", + "/gjs/context/eval-module-file/exit-code-omitted-warning", gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_warning); g_test_add_func( - "/cjs/context/eval-module-file/exit-code-omitted-no-warning", + "/gjs/context/eval-module-file/exit-code-omitted-no-warning", gjstest_test_func_gjs_context_eval_module_file_exit_code_omitted_no_warning); - g_test_add_func("/cjs/context/eval-file/exit-code-omitted-no-throw", + g_test_add_func("/gjs/context/eval-file/exit-code-omitted-no-throw", gjstest_test_func_gjs_context_eval_file_exit_code_omitted_no_throw); - g_test_add_func("/cjs/context/eval-file/exit-code-omitted-throw", + g_test_add_func("/gjs/context/eval-file/exit-code-omitted-throw", gjstest_test_func_gjs_context_eval_file_exit_code_omitted_throw); - g_test_add_func("/cjs/context/eval/exit-code-omitted-throw", + g_test_add_func("/gjs/context/eval/exit-code-omitted-throw", gjstest_test_func_gjs_context_eval_exit_code_omitted_throw); - g_test_add_func("/cjs/context/eval/exit-code-omitted-no-throw", + g_test_add_func("/gjs/context/eval/exit-code-omitted-no-throw", gjstest_test_func_gjs_context_eval_exit_code_omitted_no_throw); - g_test_add_func("/cjs/context/eval-module/exit-code-omitted-throw", + g_test_add_func("/gjs/context/eval-module/exit-code-omitted-throw", gjstest_test_func_gjs_context_eval_module_exit_code_omitted_throw); g_test_add_func( - "/cjs/context/eval-module/exit-code-omitted-no-throw", + "/gjs/context/eval-module/exit-code-omitted-no-throw", gjstest_test_func_gjs_context_eval_module_exit_code_omitted_no_throw); - g_test_add("/cjs/context/eval-module/jsapi-throw", GjsUnitTestFixture, + 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("/cjs/context/run-in-realm", + g_test_add_func("/gjs/context/run-in-realm", gjstest_test_func_gjs_context_run_in_realm); + g_test_add_func("/gjs/context/eval-module/source-map", + gjstest_test_func_gjs_context_eval_module_source_map); + g_test_add_func("/gjs/context/eval-file/source-map", + gjstest_test_func_gjs_context_eval_file_source_map); + #define ADD_JSAPI_UTIL_TEST(path, func) \ - g_test_add("/cjs/jsapi/util/" path, GjsUnitTestFixture, NULL, \ + g_test_add("/gjs/jsapi/util/" path, GjsUnitTestFixture, NULL, \ gjs_unit_test_fixture_setup, func, \ gjs_unit_test_fixture_teardown) diff --git a/test/meson.build b/test/meson.build index 391663966..174ecb296 100644 --- a/test/meson.build +++ b/test/meson.build @@ -7,13 +7,13 @@ mock_js_resources_files = gnome.compile_resources('mock-js-resources', 'mock-js-resources.gresource.xml', c_name: 'mock_js_resources', source_dir: '..') -libcjs_tests_common = static_library('gjs-tests-common', +libgjs_tests_common = static_library('gjs-tests-common', sources: [ 'gjs-test-utils.cpp', 'gjs-test-utils.h', 'gjs-test-common.cpp', 'gjs-test-common.h', ], - cpp_args: libcjs_cpp_args, - include_directories: top_include, dependencies: libcjs_dependencies, + cpp_args: libgjs_cpp_args, + include_directories: top_include, dependencies: libgjs_dependencies, ) gjs_tests_sources = [ @@ -23,11 +23,12 @@ gjs_tests_sources = [ ] gjs_tests = executable('gjs-tests', gjs_tests_sources, mock_js_resources_files, - include_directories: top_include, dependencies: libcjs_dep, - link_with: libcjs_tests_common) + include_directories: top_include, dependencies: libgjs_dep, + link_whole: [module_resource_lib], + link_with: [libgjs_tests_common, libgjs_internal]) test('API tests', gjs_tests, args: ['--tap', '--keep-going', '--verbose'], - depends: cjs_private_typelib, env: tests_environment, protocol: 'tap', + depends: gjs_private_typelib, env: tests_environment, protocol: 'tap', suite: 'C', timeout: 60, priority: 10) gjs_tests_internal = executable('gjs-tests-internal', @@ -40,9 +41,9 @@ gjs_tests_internal = executable('gjs-tests-internal', module_resource_srcs, ], include_directories: top_include, - cpp_args: libcjs_cpp_args, - dependencies: [libcjs_dependencies, libgjstesttools_dep], - link_with: [libcjs_tests_common, libcjs_internal]) + cpp_args: libgjs_cpp_args, + dependencies: [libgjs_dependencies, libgjstesttools_dep], + link_with: [libgjs_tests_common, libgjs_internal]) test('Internal API tests', gjs_tests_internal, args: ['--tap', '--keep-going', '--verbose'], diff --git a/test/mock-js-resources.gresource.xml b/test/mock-js-resources.gresource.xml index 718137b33..e90626b76 100644 --- a/test/mock-js-resources.gresource.xml +++ b/test/mock-js-resources.gresource.xml @@ -10,5 +10,16 @@ test/modules/import.js test/modules/throws.js test/modules/nothrows.js + + test/source-maps/separate/number.js + test/source-maps/separate/number.js.map + test/source-maps/separate/numberWork.js + test/source-maps/separate/numberWork.js.map + test/source-maps/separate/noModule.js + test/source-maps/separate/noModule.js.map + + test/source-maps/inlined/number.js + test/source-maps/inlined/numberWork.js + test/source-maps/inlined/noModule.js diff --git a/test/modules/.eslintrc.yml b/test/modules/.eslintrc.yml deleted file mode 100644 index b2391da9e..000000000 --- a/test/modules/.eslintrc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -# SPDX-FileCopyrightText: 2021 Philip Chimento -parserOptions: - sourceType: module diff --git a/test/source-maps/inlined/noModule.js b/test/source-maps/inlined/noModule.js new file mode 100644 index 000000000..de26b87aa --- /dev/null +++ b/test/source-maps/inlined/noModule.js @@ -0,0 +1,3 @@ +var a = { n: null }; +var b = a.n.toString(42); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9Nb2R1bGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub01vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxJQUFJLENBQUMsR0FBZ0IsRUFBQyxDQUFDLEVBQUUsSUFBSSxFQUFDLENBQUM7QUFDL0IsSUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/test/source-maps/inlined/noModule.js.license b/test/source-maps/inlined/noModule.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/inlined/noModule.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/inlined/number.js b/test/source-maps/inlined/number.js new file mode 100644 index 000000000..9250a905f --- /dev/null +++ b/test/source-maps/inlined/number.js @@ -0,0 +1,4 @@ +export var get2ndNumber = function (num) { + return num[1].n.toFixed(1); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVtYmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibnVtYmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLE1BQU0sQ0FBQyxJQUFNLFlBQVksR0FBRyxVQUFDLEdBQXVCO0lBQ2hELE9BQU8sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDL0IsQ0FBQyxDQUFBIn0= \ No newline at end of file diff --git a/test/source-maps/inlined/number.js.license b/test/source-maps/inlined/number.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/inlined/number.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/inlined/numberWork.js b/test/source-maps/inlined/numberWork.js new file mode 100644 index 000000000..487d2db27 --- /dev/null +++ b/test/source-maps/inlined/numberWork.js @@ -0,0 +1,3 @@ +import { get2ndNumber } from "./number.js"; +get2ndNumber([{ n: 1 }, { n: null }]); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVtYmVyV29yay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm51bWJlcldvcmsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUUzQyxZQUFZLENBQUMsQ0FBQyxFQUFDLENBQUMsRUFBQyxDQUFDLEVBQUMsRUFBRSxFQUFDLENBQUMsRUFBRSxJQUFJLEVBQUMsQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/test/source-maps/inlined/numberWork.js.license b/test/source-maps/inlined/numberWork.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/inlined/numberWork.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/noModule.js b/test/source-maps/separate/noModule.js new file mode 100644 index 000000000..a094e2142 --- /dev/null +++ b/test/source-maps/separate/noModule.js @@ -0,0 +1,3 @@ +var a = { n: null }; +var b = a.n.toString(42); +//# sourceMappingURL=noModule.js.map \ No newline at end of file diff --git a/test/source-maps/separate/noModule.js.license b/test/source-maps/separate/noModule.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/noModule.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/noModule.js.map b/test/source-maps/separate/noModule.js.map new file mode 100644 index 000000000..447ddc3fe --- /dev/null +++ b/test/source-maps/separate/noModule.js.map @@ -0,0 +1 @@ +{"version":3,"file":"noModule.js","sourceRoot":"","sources":["noModule.ts"],"names":[],"mappings":"AAIA,IAAI,CAAC,GAAgB,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC;AAC/B,IAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/test/source-maps/separate/noModule.js.map.license b/test/source-maps/separate/noModule.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/noModule.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/number.js b/test/source-maps/separate/number.js new file mode 100644 index 000000000..d9c8f3444 --- /dev/null +++ b/test/source-maps/separate/number.js @@ -0,0 +1,4 @@ +export var get2ndNumber = function (num) { + return num[1].n.toFixed(1); +}; +//# sourceMappingURL=number.js.map \ No newline at end of file diff --git a/test/source-maps/separate/number.js.license b/test/source-maps/separate/number.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/number.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/number.js.map b/test/source-maps/separate/number.js.map new file mode 100644 index 000000000..e3c061f01 --- /dev/null +++ b/test/source-maps/separate/number.js.map @@ -0,0 +1 @@ +{"version":3,"file":"number.js","sourceRoot":"","sources":["number.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,IAAM,YAAY,GAAG,UAAC,GAAuB;IAChD,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAA"} \ No newline at end of file diff --git a/test/source-maps/separate/number.js.map.license b/test/source-maps/separate/number.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/number.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/numberWork.js b/test/source-maps/separate/numberWork.js new file mode 100644 index 000000000..bce5a9874 --- /dev/null +++ b/test/source-maps/separate/numberWork.js @@ -0,0 +1,3 @@ +import { get2ndNumber } from "./number.js"; +get2ndNumber([{ n: 1 }, { n: null }]); +//# sourceMappingURL=numberWork.js.map \ No newline at end of file diff --git a/test/source-maps/separate/numberWork.js.license b/test/source-maps/separate/numberWork.js.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/numberWork.js.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/source-maps/separate/numberWork.js.map b/test/source-maps/separate/numberWork.js.map new file mode 100644 index 000000000..6d3e15f13 --- /dev/null +++ b/test/source-maps/separate/numberWork.js.map @@ -0,0 +1 @@ +{"version":3,"file":"numberWork.js","sourceRoot":"","sources":["numberWork.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,EAAE,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/test/source-maps/separate/numberWork.js.map.license b/test/source-maps/separate/numberWork.js.map.license new file mode 100644 index 000000000..184e9b607 --- /dev/null +++ b/test/source-maps/separate/numberWork.js.map.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +SPDX-FileCopyrightText: 2024 Gary Li \ No newline at end of file diff --git a/test/test-ci.sh b/test/test-ci.sh old mode 100644 new mode 100755 index 9e33946d8..52cd2a73a --- a/test/test-ci.sh +++ b/test/test-ci.sh @@ -129,6 +129,8 @@ do_Create_Artifacts_Folder "$1" # Ignore extra git security checks as we don't care in CI. git config --global --add safe.directory "${PWD}" +git config --global --add safe.directory \ + "${PWD}/subprojects/gobject-introspection-tests" if test "$1" = "SETUP"; then do_Show_Info @@ -139,12 +141,12 @@ elif test "$1" = "BUILD"; then do_Set_Env DEFAULT_CONFIG_OPTS="-Dreadline=enabled -Dprofiler=enabled -Ddtrace=false \ - -Dsystemtap=false -Dverbose_logs=false --werror" + -Dsystemtap=false -Dverbose_logs=false --werror -Dglib:werror=false" meson setup _build $DEFAULT_CONFIG_OPTS $CONFIG_OPTS ninja -C _build if test "$TEST" != "skip"; then - xvfb-run -a meson test -C _build $TEST_OPTS + xvfb-run -a meson test -C _build --suite=gjs $TEST_OPTS fi elif test "$1" = "SH_CHECKS"; then @@ -164,7 +166,8 @@ elif test "$1" = "SH_CHECKS"; then elif test "$1" = "CPPLINT"; then do_Print_Labels 'C/C++ Linter report ' - cpplint --quiet $(find . -name \*.cpp -or -name \*.h | sort) 2>&1 >/dev/null | \ + cpplint --quiet --filter=-build/include_what_you_use \ + $(find . -name \*.cpp -or -name \*.h | sort) 2>&1 >/dev/null | \ tee "$save_dir"/analysis/head-report.txt | \ sed -E -e 's/:[0-9]+:/:LINE:/' -e 's/ +/ /g' \ > /cwd/head-report.txt @@ -180,7 +183,8 @@ elif test "$1" = "CPPLINT"; then exit 0 fi git checkout ci-upstream-base - cpplint --quiet $(find . -name \*.cpp -or -name \*.h | sort) 2>&1 >/dev/null | \ + cpplint --quiet --filter=-build/include_what_you_use \ + $(find . -name \*.cpp -or -name \*.h | sort) 2>&1 >/dev/null | \ tee "$save_dir"/analysis/base-report.txt | \ sed -E -e 's/:[0-9]+:/:LINE:/' -e 's/ +/ /g' \ > /cwd/base-report.txt diff --git a/tools/apply-format b/tools/apply-format old mode 100644 new mode 100755 diff --git a/tools/eslint.config.js b/tools/eslint.config.js new file mode 100644 index 000000000..0d278c65c --- /dev/null +++ b/tools/eslint.config.js @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2025 Florian Müllner +// SPDX-License-Identifier: MIT OR LGPL-2.1-or-later + +import {defineConfig} from '@eslint/config-helpers'; +import globals from 'globals'; +import gnome from 'eslint-config-gnome'; + +export default defineConfig([ + gnome.configs.recommended, + gnome.configs.jsdoc, + { + ignores: [ + 'installed-tests/js/jasmine.js', + 'installed-tests/js/modules/badOverrides/WarnLib.js', + 'installed-tests/js/modules/subBadInit/__init__.js', + 'modules/script/jsUnit.js', + '_*/', + 'builddir', + 'modules/internal/source-map/', + 'installed-tests/debugger/sourcemap*', + 'test/source-maps/', + ], + }, + { + languageOptions: { + sourceType: 'script', + }, + }, + { + files: [ + '**/eslint.config.js', + 'examples/**', + 'installed-tests/js/matchers.js', + 'installed-tests/js/minijasmine-executor.js', + 'installed-tests/js/minijasmine.js', + 'installed-tests/js/modules/exports.js', + 'installed-tests/js/modules/greet.js', + 'installed-tests/js/modules/importmeta.js', + 'installed-tests/js/modules/networkURI.js', + 'installed-tests/js/modules/say.js', + 'installed-tests/js/modules/scaryURI.js', + 'installed-tests/js/modules/sideEffect4.js', + 'installed-tests/js/testAsync.js', + 'installed-tests/js/testAsyncMainloop.js', + 'installed-tests/js/testCairoModule.js', + 'installed-tests/js/testConsole.js', + 'installed-tests/js/testEncoding.js', + 'installed-tests/js/testESModules.js', + 'installed-tests/js/testGLibLogWriter.js', + 'installed-tests/js/testTimers.js', + 'installed-tests/js/testUtility.js', + 'installed-tests/js/testWeakRef.js', + 'modules/esm/**', + 'modules/internal/**', + 'test/modules/**', + ], + languageOptions: { + sourceType: 'module', + }, + }, + { + files: [ + 'modules/internal/**', + ], + languageOptions: { + globals: { + ARGV: 'off', + GIRepositoryGType: 'off', + imports: 'off', + log: 'off', + logError: 'off', + print: 'off', + printerr: 'off', + + atob: 'readonly', + compileInternalModule: 'readonly', + compileModule: 'readonly', + getRegistry: 'readonly', + getSourceMapRegistry: 'readonly', + loadResourceOrFile: 'readonly', + loadResourceOrFileAsync: 'readonly', + moduleGlobalThis: 'readonly', + parseURI: 'readonly', + resolveRelativeResourceOrFile: 'readonly', + setGlobalModuleLoader: 'readonly', + setModulePrivate: 'readonly', + uriExists: 'readonly', + }, + }, + }, + { + files: [ + 'installed-tests/**', + 'modules/core/**', + 'modules/script/**', + 'examples/**', + ], + rules: { + 'jsdoc/require-jsdoc': 'off', + }, + }, + { + files: [ + 'examples/**', + 'modules/script/tweener/**', + ], + rules: { + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/check-tag-names': 'off', + 'jsdoc/check-param-names': 'off', + }, + }, + { + files: [ + 'modules/script/_bootstrap/**', + ], + languageOptions: { + globals: { + log: 'off', + logError: 'off', + print: 'off', + printerr: 'off', + }, + }, + }, + { + files: [ + 'installed-tests/js/**', + ], + languageOptions: { + globals: { + ...globals.jasmine, + }, + }, + rules: { + 'no-restricted-globals': [ + 'error', + { + name: 'fdescribe', + message: 'Do not commit fdescribe(). Use describe() instead.', + }, + { + name: 'fit', + message: 'Do not commit fit(). Use it() instead.', + }, + ], + 'no-restricted-syntax': [ + 'error', + { + selector: 'CallExpression[callee.name="it"] > ArrowFunctionExpression', + message: 'Arrow functions can mess up some Jasmine APIs. Use function () instead', + }, + { + selector: 'CallExpression[callee.name="beforeEach"] > ArrowFunctionExpression', + message: 'Arrow functions can mess up some Jasmine APIs. Use function () instead', + }, + { + selector: 'CallExpression[callee.name="afterEach"] > ArrowFunctionExpression', + message: 'Arrow functions can mess up some Jasmine APIs. Use function () instead', + }, + { + selector: 'CallExpression[callee.name="beforeAll"] > ArrowFunctionExpression', + message: 'Arrow functions can mess up some Jasmine APIs. Use function () instead', + }, + { + selector: 'CallExpression[callee.name="afterAll"] > ArrowFunctionExpression', + message: 'Arrow functions can mess up some Jasmine APIs. Use function () instead', + }, + ], + }, + }, + { + files: [ + 'installed-tests/js/modules/overrides/**', + 'modules/core/overrides/**', + ], + rules: { + 'no-unused-vars': ['error', { + varsIgnorePattern: '^_init$', + }], + }, + }, + { + files: [ + 'installed-tests/js/modules/badOverrides/**', + 'installed-tests/js/modules/badOverrides2/**', + ], + rules: { + 'no-throw-literal': 'off', // these are intended to be bad code + 'no-unused-vars': ['error', { + varsIgnorePattern: '^_init$', + }], + }, + }, + { + files: [ + 'installed-tests/debugger/**', + ], + rules: { + 'no-debugger': 'off', + }, + }, + { + files: [ + 'installed-tests/debugger/keys.debugger.js', + 'installed-tests/debugger/print.debugger.js', + ], + rules: { + 'no-unused-private-class-members': 'off', + }, + }, +]); diff --git a/tools/git-pre-commit-format b/tools/git-pre-commit-format old mode 100644 new mode 100755 diff --git a/tools/gjs-private-iwyu.imp b/tools/gjs-private-iwyu.imp index 6b6bcf4ec..baf829015 100644 --- a/tools/gjs-private-iwyu.imp +++ b/tools/gjs-private-iwyu.imp @@ -9,6 +9,7 @@ {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, + {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, @@ -16,12 +17,14 @@ {"include": ["<__stdarg_va_arg.h>", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, + {"include": ["\"gdbusinterfaceskeleton.h\"", "private", "", "public"]}, + {"include": ["\"gclosure.h\"", "private", "", "public"]}, {"include": ["@\"gio/.*\"", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["\"gitypes.h\"", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["\"giversion.h\"", "private", "", "public"]}, - {"include": ["@\"gi.*info.h\"", "private", "", "public"]}, + {"include": ["@\"gi[a-z]*info.h\"", "private", "", "public"]}, {"include": ["\"glib/galloca.h\"", "private", "", "public"]}, {"include": ["\"glib/garray.h\"", "private", "", "public"]}, {"include": ["\"glib/gbacktrace.h\"", "private", "", "public"]}, diff --git a/tools/gjs-public-iwyu.imp b/tools/gjs-public-iwyu.imp index 714f683da..2d3fe0a12 100644 --- a/tools/gjs-public-iwyu.imp +++ b/tools/gjs-public-iwyu.imp @@ -5,5 +5,5 @@ [ {"ref": "gjs-private-iwyu.imp"}, - {"include": ["\"gjs/macros.h\"", "private", "", "public"]} + {"include": ["\"cjs/macros.h\"", "private", "", "public"]} ] \ No newline at end of file diff --git a/tools/heapdot.py b/tools/heapdot.py index 2d56fb619..4d15c77f2 100644 --- a/tools/heapdot.py +++ b/tools/heapdot.py @@ -9,7 +9,7 @@ import re -func_regex = re.compile('Function(?: ([^/]+)(?:/([<|\w]+))?)?') +func_regex = re.compile(r'Function(?: ([^/]+)(?:/([<|\w]+))?)?') priv_regex = re.compile(r'([^ ]+) (\(nil\)|0x[a-fA-F0-9]+$)') @@ -90,7 +90,7 @@ def output_dot_file(args, graph, targs, fname): if elabel in ['prototype', 'group_proto']: style += ',dashed' # Another object native to Gjs - elif label.startswith('Cjs') or label.startswith('GIR'): + elif label.startswith('Gjs') or label.startswith('GIR'): shape = 'octagon' elif label.startswith('Function'): fm = func_regex.match(label) diff --git a/tools/heapgraph.md b/tools/heapgraph.md index ba96d400d..e4bd0ffa5 100644 --- a/tools/heapgraph.md +++ b/tools/heapgraph.md @@ -137,11 +137,12 @@ label, e.g. GObject_Object "My object". > `./heapgraph.py --help` before running. ``` -usage: heapgraph.py [-h] [--edge | --function | --string] [--count] - [--dot-graph] [--no-addr] [--diff-heap FILE] - [--no-gray-roots] [--no-weak-maps] [--show-global] - [--show-imports] [--hide-addr ADDR] [--hide-node LABEL] - [--hide-edge LABEL] FILE TARGET +usage: heapgraph.py [-h] [--edge EDGE_TARGETS] [--function FUNC_TARGETS] + [--string STRING_TARGETS] [--annotation ANNOTATION_TARGETS] + [--count] [--dot-graph] [--no-addr] [--diff-heap FILE] + [--no-gray-roots] [--show-unreachable] [--no-weak-maps] + [--show-global] [--show-imports] [--hide-addr ADDR] + [--hide-node LABEL] [--hide-edge LABEL] FILE [TARGET ...] Find what is rooting or preventing an object from being collected in a GJS heap using a shortest-path breadth-first algorithm. @@ -151,11 +152,18 @@ positional arguments: TARGET Heap address (eg. 0x7fa814054d00) or type prefix (eg. Array, Object, GObject, Function...) -optional arguments: +options: -h, --help show this help message and exit - --edge, -e Treat TARGET as a function name - --function, -f Treat TARGET as a function name - --string, -s Treat TARGET as a string literal or String() + +Target options: + --edge, -e EDGE_TARGETS + Add an edge label to the list of targets + --function, -f FUNC_TARGETS + Add a function name to the list of targets + --string, -s STRING_TARGETS + Add a string literal or String() to the list of targets + --annotation, -a ANNOTATION_TARGETS + Add a __heapgraph_name annotation to the list of targets Output Options: --count, -c Only count the matches for TARGET @@ -163,18 +171,20 @@ Output Options: --no-addr, -na Don't show addresses Node/Root Filtering: - --diff-heap FILE, -dh FILE + --diff-heap, -dh FILE Don't show roots common to the heap FILE --no-gray-roots, -ng Don't show gray roots (marked to be collected) + --show-unreachable, -u + Show objects that have no path to a root but are not collected yet --no-weak-maps, -nwm Don't show WeakMaps --show-global, -g Show the global object (eg. globalThis/GjsGlobal) --show-imports, -i Show import and module nodes (eg. imports.foo) - --hide-addr ADDR, -ha ADDR + --hide-addr, -ha ADDR Don't show roots with the heap address ADDR - --hide-node LABEL, -hn LABEL + --hide-node, -hn LABEL Don't show nodes with labels containing LABEL - --hide-edge LABEL, -he LABEL - Don't show edges labelled LABEL + --hide-edge, -he LABEL + Don't show edges labeled LABEL ``` ## See Also diff --git a/tools/heapgraph.py b/tools/heapgraph.py old mode 100644 new mode 100755 index 3399f537d..7d48130bc --- a/tools/heapgraph.py +++ b/tools/heapgraph.py @@ -225,8 +225,10 @@ def addEdge(source, target, edge_label): break else: addNode(node_addr, node_label) - # Skip comments, arenas, realms and zones - elif line[0] == '#': + # Skip comments, arenas, realms, zones, and weak map entries + elif (line[0] == '#' or + wme_regex.match(line) is not None or + line[:10] == '=========='): continue else: sys.stderr.write('Error: Unknown line: {}\n'.format(line[:-1])) diff --git a/tools/lcovrc b/tools/lcovrc index 9e57d09a4..644168e66 100644 --- a/tools/lcovrc +++ b/tools/lcovrc @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2018 Endless Mobile, Inc. # lcov and genhtml configuration -# See http://ltp.sourceforge.net/coverage/lcov/lcovrc.5.php +# See https://github.com/linux-test-project/lcov/blob/HEAD/lcovrc # Adapted from GLib's lcovrc # Always enable branch coverage @@ -15,3 +15,6 @@ lcov_excl_br_line = LCOV_EXCL_BR_LINE|g_return_if_fail|g_return_val_if_fail|g_as # Similarly for unreachable assertions. lcov_excl_line = LCOV_EXCL_LINE|g_return_if_reached|g_return_val_if_reached|g_assert_not_reached + +# Temporary, ignore error coming from GLib built as a subproject +ignore_errors = negative diff --git a/tools/package-lock.json b/tools/package-lock.json index 27e486c11..5f539e281 100644 --- a/tools/package-lock.json +++ b/tools/package-lock.json @@ -10,7 +10,9 @@ "license": "MIT OR LGPL-2.0-or-later", "devDependencies": { "eslint": "^8.57.0", - "eslint-plugin-jsdoc": "^48.2.1" + "eslint-config-gnome": "git+https://gitlab.gnome.org/World/javascript/eslint-config-gnome.git", + "eslint-plugin-jsdoc": "^48.2.1", + "typescript": "^5.7" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -23,13 +25,14 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", - "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", "dev": true, + "license": "MIT", "dependencies": { "comment-parser": "1.4.1", - "esquery": "^1.5.0", + "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { @@ -60,6 +63,16 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", @@ -160,6 +173,19 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", + "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -167,10 +193,11 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -258,18 +285,6 @@ "concat-map": "0.0.1" } }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -318,6 +333,7 @@ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.0.0" } @@ -329,10 +345,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -343,12 +360,13 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -377,6 +395,13 @@ "node": ">=6.0.0" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -444,21 +469,48 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-gnome": { + "version": "1.0.0", + "resolved": "git+https://gitlab.gnome.org/World/javascript/eslint-config-gnome.git#498b208be64773c69b1d513bbd095fe3eb002735", + "dev": true, + "license": "MIT OR LGPL-2.1-or-later", + "dependencies": { + "@eslint/config-helpers": "^0.2.2", + "@eslint/js": "^9.28.0", + "eslint-plugin-jsdoc": "^48.11.0" + } + }, + "node_modules/eslint-config-gnome/node_modules/@eslint/js": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.1.tgz", - "integrity": "sha512-iUvbcyDZSO/9xSuRv2HQBw++8VkV/pt3UWtX9cpPH0l7GKPq78QC/6+PmyQHHvNZaTjAce6QVciEbnc6J/zH5g==", + "version": "48.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.11.0.tgz", + "integrity": "sha512-d12JHJDPNo7IFwTOAItCeJY1hcqoIxE0lHA8infQByLilQ9xkqrRa6laWCnsuCrf+8rUnvxXY1XuTbibRBNylA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.42.0", + "@es-joy/jsdoccomment": "~0.46.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.5", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.6.0", - "spdx-expression-parse": "^4.0.0" + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" }, "engines": { "node": ">=18" @@ -467,6 +519,37 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -513,10 +596,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -747,21 +831,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -815,6 +884,7 @@ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.0.0" } @@ -880,18 +950,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -905,10 +963,11 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -984,6 +1043,20 @@ "node": ">=6" } }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1107,13 +1180,11 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1142,6 +1213,13 @@ "node": ">=8" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -1200,12 +1278,36 @@ "node": ">=8" } }, + "node_modules/synckit": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", + "integrity": "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1230,6 +1332,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1260,12 +1376,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/tools/package.json b/tools/package.json index 0713d818d..34467d6d9 100644 --- a/tools/package.json +++ b/tools/package.json @@ -4,12 +4,16 @@ "repository": "https://gitlab.gnome.org/GNOME/gjs", "license": "MIT OR LGPL-2.0-or-later", "private": true, + "type": "module", "scripts": { - "lint": "cd .. && eslint . --format unix --resolve-plugins-relative-to tools", - "fix": "npm run lint --fix" + "lint": "cd .. && eslint . --format unix", + "fix": "npm run lint --fix", + "typecheck": "tsc" }, "devDependencies": { "eslint": "^8.57.0", - "eslint-plugin-jsdoc": "^48.2.1" + "eslint-config-gnome": "git+https://gitlab.gnome.org/World/javascript/eslint-config-gnome.git#c479d059e8d9ea99c3b53c2ea43abf7fdb05eb51", + "eslint-plugin-jsdoc": "^48.2.1", + "typescript": "^5.7" } } diff --git a/tools/process_iwyu.py b/tools/process_iwyu.py old mode 100644 new mode 100755 index f659817b4..e249f6eba --- a/tools/process_iwyu.py +++ b/tools/process_iwyu.py @@ -58,30 +58,17 @@ class Colors: ) add_fwd_header = False -CSTDINT = '#include ' -STDINTH = '#include ' - FALSE_POSITIVES = ( - # The bodies of these structs already come before their usage, - # we don't need to have forward declarations of them as well - ('gjs/atoms.h', 'class GjsAtoms;', ''), - ('gjs/atoms.h', 'struct GjsSymbolAtom;', ''), - ('gjs/mem-private.h', 'namespace Gjs { namespace Memory { struct Counter; } }', ''), - - # False positive when constructing JS::GCHashMap - ('gi/boxed.h', '#include ', 'for move'), - ('gi/object.h', '#include ', 'for move'), - ('gjs/jsapi-util-error.cpp', '#include ', 'for move'), - - # 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 ', ''), + # glib.h is already included, we don't need to additionally forward-declare + ('cjs/auto.h', 'struct _GVariant;', ''), + + # IWYU is not sure whether or is for pair + ('test/gjs-test-utils.h', '#include ', 'for pair'), + ('test/gjs-test-toggle-queue.cpp', '#include ', 'for pair'), + + # C++20 false positive + # https://github.com/include-what-you-use/include-what-you-use/issues/1791 + ('cjs/jsapi-util-root.h', '#include ', 'for nullptr_t'), ) diff --git a/tools/run_coverage.sh b/tools/run_coverage.sh old mode 100644 new mode 100755 index a8961f22b..6b834e0bb --- a/tools/run_coverage.sh +++ b/tools/run_coverage.sh @@ -6,7 +6,7 @@ SOURCEDIR=$(pwd) BUILDDIR="$(pwd)/_coverage_build" LCOV_ARGS="--config-file $SOURCEDIR/tools/lcovrc" GENHTML_ARGS='--legend --show-details --branch-coverage' -IGNORE="*/gjs/test/* *-resources.c *minijasmine.cpp */gjs/subprojects/glib/* */gjs/subprojects/gobject-introspection/*" +IGNORE="*/gjs/test/* *-resources.c *minijasmine.cpp */gjs/installed-tests/js/libgjstesttools/* */gjs/subprojects/glib/* */gjs/_coverage_build/subprojects/glib/* */gjs/subprojects/gobject-introspection/*" rm -rf "$BUILDDIR" meson setup "$BUILDDIR" -Db_coverage=true @@ -14,11 +14,11 @@ meson setup "$BUILDDIR" -Db_coverage=true VERSION=$(meson introspect "$BUILDDIR" --projectinfo | python -c 'import json, sys; print(json.load(sys.stdin)["version"])') mkdir -p _coverage -meson test -C "$BUILDDIR" --num-processes 1 +meson test -C "$BUILDDIR" --num-processes 1 --suite=gjs lcov --directory "$BUILDDIR" --capture --output-file _coverage/gjs.lcov.run --no-checksum $LCOV_ARGS lcov --extract _coverage/gjs.lcov.run "$SOURCEDIR/*" $LCOV_ARGS -o _coverage/gjs.lcov.sources lcov --remove _coverage/gjs.lcov.sources $IGNORE $LCOV_ARGS -o _coverage/gjs.lcov -genhtml --prefix "$BUILDDIR/lcov/org/cinnamon/cjs" --prefix "$BUILDDIR" --prefix "$SOURCEDIR" \ +genhtml --prefix "$BUILDDIR/lcov/org/gnome/gjs" --prefix "$BUILDDIR" --prefix "$SOURCEDIR" \ --output-directory _coverage/html \ --title "gjs-$VERSION Code Coverage" \ $GENHTML_ARGS _coverage/gjs.lcov "$BUILDDIR"/lcov/coverage.lcov diff --git a/tools/run_cppcheck.sh b/tools/run_cppcheck.sh old mode 100644 new mode 100755 diff --git a/tools/run_eslint.sh b/tools/run_eslint.sh old mode 100644 new mode 100755 diff --git a/tools/run_iwyu.sh b/tools/run_iwyu.sh old mode 100644 new mode 100755 index b0292293a..06d4bb223 --- a/tools/run_iwyu.sh +++ b/tools/run_iwyu.sh @@ -43,11 +43,11 @@ fi echo "files: $files" -IWYU="python3 $(which iwyu_tool || which iwyu-tool || which iwyu_tool.py) -p ." +IWYU="python3 $(which iwyu_tool iwyu-tool iwyu_tool.py 2>/dev/null) -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-128)" +IWYU_RAW_INC="-I. -I.. $(pkg-config --cflags girepository-2.0 mozjs-140)" 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" @@ -56,27 +56,28 @@ EXIT=0 # inline-only files with no implementation file don't appear in the compilation # database, so iwyu_tool cannot process them for FILE in $SRCDIR/gi/arg-types-inl.h $SRCDIR/gi/js-value-inl.h \ - $SRCDIR/gi/utils-inl.h $SRCDIR/gjs/enum-utils.h \ - $SRCDIR/gjs/jsapi-util-args.h $SRCDIR/gjs/jsapi-util-root.h \ + $SRCDIR/gi/info.h $SRCDIR/gi/utils-inl.h $SRCDIR/cjs/auto.h \ + $SRCDIR/cjs/enum-utils.h $SRCDIR/cjs/gerror-result.h \ + $SRCDIR/cjs/jsapi-util-args.h $SRCDIR/cjs/jsapi-util-root.h \ $SRCDIR/modules/cairo-module.h do if should_analyze $FILE; then - if ! $IWYU_RAW $(realpath --relative-to=. $FILE) $IWYU_RAW_INC 2>&1 \ - | $POSTPROCESS; then + if ! $IWYU_RAW $PRIVATE_MAPPING $(realpath --relative-to=. $FILE) \ + $IWYU_RAW_INC 2>&1 | $POSTPROCESS; then EXIT=1 fi fi done -for FILE in $SRCDIR/gi/*.cpp $SRCDIR/gjs/atoms.cpp $SRCDIR/gjs/byteArray.cpp \ - $SRCDIR/gjs/coverage.cpp $SRCDIR/gjs/debugger.cpp \ - $SRCDIR/gjs/deprecation.cpp $SRCDIR/gjs/engine.cpp \ - $SRCDIR/gjs/error-types.cpp $SRCDIR/gjs/global.cpp \ - $SRCDIR/gjs/internal.cpp $SRCDIR/gjs/importer.cpp \ - $SRCDIR/gjs/jsapi-util*.cpp $SRCDIR/gjs/mainloop.cpp \ - $SRCDIR/gjs/module.cpp $SRCDIR/gjs/native.cpp \ - $SRCDIR/gjs/objectbox.cpp $SRCDIR/gjs/promise.cpp $SRCDIR/gjs/stack.cpp \ - $SRCDIR/gjs/text-encoding.cpp $SRCDIR/modules/cairo-*.cpp \ +for FILE in $SRCDIR/gi/*.cpp $SRCDIR/cjs/atoms.cpp $SRCDIR/cjs/byteArray.cpp \ + $SRCDIR/cjs/coverage.cpp $SRCDIR/cjs/debugger.cpp \ + $SRCDIR/cjs/deprecation.cpp $SRCDIR/cjs/engine.cpp \ + $SRCDIR/cjs/error-types.cpp $SRCDIR/cjs/global.cpp \ + $SRCDIR/cjs/internal.cpp $SRCDIR/cjs/importer.cpp \ + $SRCDIR/cjs/jsapi-util*.cpp $SRCDIR/cjs/mainloop.cpp \ + $SRCDIR/cjs/module.cpp $SRCDIR/cjs/native.cpp \ + $SRCDIR/cjs/objectbox.cpp $SRCDIR/cjs/promise.cpp $SRCDIR/cjs/stack.cpp \ + $SRCDIR/cjs/text-encoding.cpp $SRCDIR/modules/cairo-*.cpp \ $SRCDIR/modules/console.cpp $SRCDIR/modules/print.cpp \ $SRCDIR/modules/system.cpp $SRCDIR/test/*.cpp $SRCDIR/util/*.cpp \ $SRCDIR/libgjs-private/*.c @@ -89,17 +90,17 @@ do done # this header file is named differently from its corresponding implementation -if ( should_analyze $SRCDIR/gjs/jsapi-dynamic-class.cpp || \ - should_analyze $SRCDIR/gjs/jsapi-class.h ); then - if ! $IWYU $SRCDIR/gjs/jsapi-dynamic-class.cpp -- $PRIVATE_MAPPING \ +if ( should_analyze $SRCDIR/cjs/jsapi-dynamic-class.cpp || \ + should_analyze $SRCDIR/cjs/jsapi-class.h ); then + if ! $IWYU $SRCDIR/cjs/jsapi-dynamic-class.cpp -- $PRIVATE_MAPPING \ $IWYU_TOOL_ARGS \ - -Xiwyu --check_also=*/gjs/jsapi-class.h | $POSTPROCESS; then + -Xiwyu --check_also=*/cjs/jsapi-class.h | $POSTPROCESS; then EXIT=1 fi fi # include header files with private implementation along with their main files -for STEM in gjs/context gjs/mem gjs/profiler modules/cairo; do +for STEM in cjs/context cjs/mem cjs/profiler modules/cairo; do if should_analyze $SRCDIR/$STEM.cpp; then if ! $IWYU $SRCDIR/$STEM.cpp -- $PRIVATE_MAPPING $IWYU_TOOL_ARGS \ -Xiwyu --check_also=*/$STEM-private.h | $POSTPROCESS; then @@ -108,7 +109,7 @@ for STEM in gjs/context gjs/mem gjs/profiler modules/cairo; do fi done -for FILE in $SRCDIR/gjs/console.cpp $SRCDIR/installed-tests/minijasmine.cpp +for FILE in $SRCDIR/cjs/console.cpp $SRCDIR/installed-tests/minijasmine.cpp do if should_analyze $FILE; then if ! $IWYU $FILE -- $PUBLIC_MAPPING $IWYU_TOOL_ARGS | $POSTPROCESS; then diff --git a/tools/run_typecheck.sh b/tools/run_typecheck.sh new file mode 100755 index 000000000..27fe66063 --- /dev/null +++ b/tools/run_typecheck.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2020 Philip Chimento + +export NODE_OPTIONS=--dns-result-order=ipv4first + +cd $(dirname -- "$0") +npm ci +npm run typecheck -- "$@" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..f99c505fe --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Philip Chimento +{ + "files": [ + "modules/internal/internalLoader.js", + "modules/internal/loader.js", + ], + "compilerOptions": { + "noEmit": true, // don't generate files, just typecheck them + "allowJs": true, + "checkJs": true, + "skipLibCheck": true, // don't check environment.d.ts + + "target": "es2023", + "lib": ["es2023"], + + // Type checking options + "strict": true + } +} diff --git a/util/console.cpp b/util/console.cpp index 5489ab6be..b3ceff660 100644 --- a/util/console.cpp +++ b/util/console.cpp @@ -11,8 +11,13 @@ # include #endif +#ifdef HAVE_READLINE_READLINE_H +# include +#endif + #include +#include "cjs/auto.h" #include "util/console.h" /** @@ -62,3 +67,28 @@ bool gjs_console_clear() { return fputs(ANSICode::CLEAR_SCREEN, stdout) > 0 && fflush(stdout) > 0; } + +#ifdef HAVE_READLINE_READLINE_H +char* gjs_console_get_repl_history_path() { + const char* user_history_path = g_getenv("GJS_REPL_HISTORY"); + Gjs::AutoChar default_history_path = + g_build_filename(g_get_user_cache_dir(), "gjs_repl_history", nullptr); + bool is_write_history_disabled = + user_history_path && user_history_path[0] == '\0'; + if (is_write_history_disabled) + return nullptr; + + if (user_history_path) + return g_strdup(user_history_path); + return default_history_path.release(); +} + +void gjs_console_write_repl_history(const char* path) { + if (path) { + int err = write_history(path); + if (err != 0) + g_warning("Could not persist history to defined file %s: %s", path, + g_strerror(err)); + } +} +#endif diff --git a/util/console.h b/util/console.h index b11d867f6..3fa4c667f 100644 --- a/util/console.h +++ b/util/console.h @@ -28,6 +28,11 @@ bool gjs_console_is_tty(int fd); bool gjs_console_clear(void); +#ifdef HAVE_READLINE_READLINE_H +char* gjs_console_get_repl_history_path(); +void gjs_console_write_repl_history(const char*); +#endif + G_END_DECLS #endif // UTIL_CONSOLE_H_ diff --git a/util/log.cpp b/util/log.cpp index 607cd3735..eb4105f19 100644 --- a/util/log.cpp +++ b/util/log.cpp @@ -26,7 +26,7 @@ #include -#include "cjs/jsapi-util.h" +#include "cjs/auto.h" #include "util/log.h" #include "util/misc.h" @@ -34,7 +34,7 @@ static std::atomic_bool s_initialized = ATOMIC_VAR_INIT(false); static bool s_debug_log_enabled = false; static bool s_print_thread = false; static std::unique_ptr s_log_file; -static GjsAutoPointer s_timer; +static Gjs::AutoPointer s_timer; static std::array s_enabled_topics; static const char* topic_to_prefix(GjsDebugTopic topic) { @@ -119,7 +119,7 @@ void gjs_log_init() { */ c = strchr(const_cast(debug_output), '%'); if (c && c[1] == 'u' && !strchr(c + 1, '%')) { - GjsAutoChar file_name; + Gjs::AutoChar file_name; #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") @@ -150,7 +150,7 @@ void gjs_log_init() { auto* topics = g_getenv("GJS_DEBUG_TOPICS"); s_enabled_topics.fill(topics == nullptr); if (topics) { - GjsAutoStrv prefixes(g_strsplit(topics, ";", -1)); + Gjs::AutoStrv prefixes{g_strsplit(topics, ";", -1)}; for (unsigned i = 0; prefixes[i] != NULL; i++) { GjsDebugTopic topic = prefix_to_topic(prefixes[i]); if (topic != GJS_DEBUG_LAST) diff --git a/util/misc.h b/util/misc.h index 5d495fcd7..2beb94e93 100644 --- a/util/misc.h +++ b/util/misc.h @@ -11,8 +11,6 @@ #include // for FILE, stdout #include // for memcpy -#include // for g_malloc - #ifdef G_DISABLE_ASSERT # define GJS_USED_ASSERT [[maybe_unused]] #else @@ -23,34 +21,6 @@ bool gjs_environment_variable_is_set (const char *env_variable_name); char** gjs_g_strv_concat(char*** strv_array, int len); -/* - * _gjs_memdup2: - * @mem: (nullable): the memory to copy. - * @byte_size: the number of bytes to copy. - * - * Allocates @byte_size bytes of memory, and copies @byte_size bytes into it - * from @mem. If @mem is null or @byte_size is 0 it returns null. - * - * This replaces g_memdup(), which was prone to integer overflows when - * converting the argument from a gsize to a guint. - * - * This static inline version is a backport of the new public g_memdup2() API - * from GLib 2.68. - * See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1927. - * It should be replaced when GLib 2.68 becomes the stable branch. - * - * Returns: (nullable): a pointer to the newly-allocated copy of the memory, - * or null if @mem is null. - */ -static inline void* _gjs_memdup2(const void* mem, size_t byte_size) { - if (!mem || byte_size == 0) - return nullptr; - - void* new_mem = g_malloc(byte_size); - memcpy(new_mem, mem, byte_size); - return new_mem; -} - /* * LogFile: * RAII class encapsulating access to a FILE* pointer that must be closed,