LCOV - code coverage report
Current view: top level - gjs - jsapi-util-string.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 74.1 % 316 234
Test Date: 2025-07-02 05:04:07 Functions: 96.2 % 26 25
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 58.2 % 146 85

             Branch data     Line data    Source code
       1                 :             : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
       2                 :             : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       3                 :             : // SPDX-FileCopyrightText: 2008 litl, LLC
       4                 :             : 
       5                 :             : #include <config.h>
       6                 :             : 
       7                 :             : #include <stdint.h>
       8                 :             : #include <string.h>     // for size_t, strlen
       9                 :             : #include <sys/types.h>  // for ssize_t
      10                 :             : 
      11                 :             : #include <algorithm>  // for copy
      12                 :             : #include <iomanip>    // for operator<<, setfill, setw
      13                 :             : #include <sstream>    // for operator<<, basic_ostream, ostring...
      14                 :             : #include <string>     // for allocator, char_traits
      15                 :             : 
      16                 :             : #include <glib.h>
      17                 :             : 
      18                 :             : #include <js/BigInt.h>
      19                 :             : #include <js/CharacterEncoding.h>
      20                 :             : #include <js/Class.h>
      21                 :             : #include <js/ErrorReport.h>
      22                 :             : #include <js/GCAPI.h>  // for AutoCheckCannotGC
      23                 :             : #include <js/Id.h>
      24                 :             : #include <js/Object.h>  // for GetClass
      25                 :             : #include <js/Promise.h>
      26                 :             : #include <js/RootingAPI.h>
      27                 :             : #include <js/String.h>
      28                 :             : #include <js/Symbol.h>
      29                 :             : #include <js/TypeDecls.h>
      30                 :             : #include <js/Utility.h>  // for UniqueChars
      31                 :             : #include <js/Value.h>
      32                 :             : #include <jsapi.h>        // for JS_GetFunctionDisplayId
      33                 :             : #include <jsfriendapi.h>  // for IdToValue, IsFunctionObject, ...
      34                 :             : #include <mozilla/CheckedInt.h>
      35                 :             : #include <mozilla/Span.h>
      36                 :             : 
      37                 :             : #include "gjs/auto.h"
      38                 :             : #include "gjs/gerror-result.h"
      39                 :             : #include "gjs/jsapi-util.h"
      40                 :             : #include "gjs/macros.h"
      41                 :             : 
      42                 :             : class JSLinearString;
      43                 :             : 
      44                 :         386 : Gjs::AutoChar gjs_hyphen_to_underscore(const char* str) {
      45                 :         386 :     char *s = g_strdup(str);
      46                 :         386 :     char *retval = s;
      47         [ +  + ]:        4477 :     while (*(s++) != '\0') {
      48         [ +  + ]:        4091 :         if (*s == '-')
      49                 :         226 :             *s = '_';
      50                 :             :     }
      51                 :         386 :     return retval;
      52                 :             : }
      53                 :             : 
      54                 :         162 : Gjs::AutoChar gjs_hyphen_to_camel(const char* str) {
      55                 :         162 :     Gjs::AutoChar retval{static_cast<char*>(g_malloc(strlen(str) + 1))};
      56                 :         162 :     const char* input_iter = str;
      57                 :         162 :     char* output_iter = retval.get();
      58                 :         162 :     bool uppercase_next = false;
      59         [ +  + ]:        2292 :     while (*input_iter != '\0') {
      60         [ +  + ]:        2130 :         if (*input_iter == '-') {
      61                 :         126 :             uppercase_next = true;
      62         [ +  + ]:        2004 :         } else if (uppercase_next) {
      63                 :         126 :             *output_iter++ = g_ascii_toupper(*input_iter);
      64                 :         126 :             uppercase_next = false;
      65                 :             :         } else {
      66                 :        1878 :             *output_iter++ = *input_iter;
      67                 :             :         }
      68                 :        2130 :         input_iter++;
      69                 :             :     }
      70                 :         162 :     *output_iter = '\0';
      71                 :         162 :     return retval;
      72                 :             : }
      73                 :             : 
      74                 :             : /**
      75                 :             :  * gjs_string_to_utf8:
      76                 :             :  * @cx: JSContext
      77                 :             :  * @value: a JS::Value containing a string
      78                 :             :  *
      79                 :             :  * Converts the JSString in @value to UTF-8 and puts it in @utf8_string_p.
      80                 :             :  *
      81                 :             :  * This function is a convenience wrapper around JS_EncodeStringToUTF8() that
      82                 :             :  * typechecks the JS::Value and throws an exception if it's the wrong type.
      83                 :             :  * Don't use this function if you already have a JS::RootedString, or if you
      84                 :             :  * know the value already holds a string; use JS_EncodeStringToUTF8() instead.
      85                 :             :  *
      86                 :             :  * Returns: Unique UTF8 chars, empty on exception throw.
      87                 :             :  */
      88                 :       45069 : JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) {
      89         [ -  + ]:       45069 :     if (!value.isString()) {
      90                 :           0 :         gjs_throw(cx, "Value is not a string, cannot convert to UTF-8");
      91                 :           0 :         return nullptr;
      92                 :             :     }
      93                 :             : 
      94                 :       45069 :     JS::RootedString str(cx, value.toString());
      95                 :       45069 :     return JS_EncodeStringToUTF8(cx, str);
      96                 :       45069 : }
      97                 :             : 
      98                 :             : /**
      99                 :             :  * gjs_string_to_utf8_n:
     100                 :             :  * @cx: the current #JSContext
     101                 :             :  * @str: string to encode in UTF-8
     102                 :             :  * @output: (out): return location for the UTF-8-encoded string
     103                 :             :  * @output_len: (out): return location for the length of @output
     104                 :             :  *
     105                 :             :  * Converts a JSString to UTF-8 and puts the char array in @output and its
     106                 :             :  * length in @output_len.
     107                 :             :  *
     108                 :             :  * This function handles the boilerplate for unpacking @str, determining its
     109                 :             :  * length, and returning the appropriate JS::UniqueChars. This function should
     110                 :             :  * generally be preferred over using JS::DeflateStringToUTF8Buffer() directly as
     111                 :             :  * it correctly handles allocation in a JS_free compatible manner.
     112                 :             :  *
     113                 :             :  * Returns: false if an exception is pending, otherwise true.
     114                 :             :  */
     115                 :          42 : bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output,
     116                 :             :                           size_t* output_len) {
     117                 :          42 :     JSLinearString* linear = JS_EnsureLinearString(cx, str);
     118         [ -  + ]:          42 :     if (!linear)
     119                 :           0 :         return false;
     120                 :             : 
     121                 :          42 :     size_t length = JS::GetDeflatedUTF8StringLength(linear);
     122                 :          42 :     char* bytes = js_pod_malloc<char>(length + 1);
     123         [ -  + ]:          42 :     if (!bytes)
     124                 :           0 :         return false;
     125                 :             : 
     126                 :             :     // Append a zero-terminator to the string.
     127                 :          42 :     bytes[length] = '\0';
     128                 :             : 
     129                 :             :     size_t deflated_length [[maybe_unused]] =
     130                 :          42 :         JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length));
     131                 :          42 :     g_assert(deflated_length == length);
     132                 :             : 
     133                 :          42 :     *output_len = length;
     134                 :          42 :     *output = JS::UniqueChars(bytes);
     135                 :          42 :     return true;
     136                 :             : }
     137                 :             : 
     138                 :             : /**
     139                 :             :  * gjs_lossy_string_from_utf8:
     140                 :             :  * @cx: the current #JSContext
     141                 :             :  * @utf8_string: a zero-terminated array of UTF-8 characters to decode
     142                 :             :  *
     143                 :             :  * Converts @utf8_string to a JS string. Instead of throwing, any invalid
     144                 :             :  * characters will be converted to the UTF-8 invalid character fallback.
     145                 :             :  *
     146                 :             :  * Returns: The decoded string.
     147                 :             :  */
     148                 :           0 : JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string) {
     149                 :           0 :     JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string));
     150                 :             :     size_t outlen;
     151                 :             :     JS::UniqueTwoByteChars twobyte_chars(
     152                 :           0 :         JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen,
     153                 :             :                                              js::MallocArena)
     154                 :           0 :             .get());
     155         [ #  # ]:           0 :     if (!twobyte_chars)
     156                 :           0 :         return nullptr;
     157                 :             : 
     158                 :           0 :     return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen);
     159                 :           0 : }
     160                 :             : 
     161                 :             : /**
     162                 :             :  * gjs_lossy_string_from_utf8_n:
     163                 :             :  * @cx: the current #JSContext
     164                 :             :  * @utf8_string: an array of UTF-8 characters to decode
     165                 :             :  * @len: length of @utf8_string
     166                 :             :  *
     167                 :             :  * Provides the same conversion behavior as gjs_lossy_string_from_utf8
     168                 :             :  * with a fixed length. See gjs_lossy_string_from_utf8().
     169                 :             :  *
     170                 :             :  * Returns: The decoded string.
     171                 :             :  */
     172                 :         163 : JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string,
     173                 :             :                                        size_t len) {
     174                 :         163 :     JS::UTF8Chars chars(utf8_string, len);
     175                 :             :     size_t outlen;
     176                 :             :     JS::UniqueTwoByteChars twobyte_chars(
     177                 :         163 :         JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen,
     178                 :             :                                              js::MallocArena)
     179                 :         163 :             .get());
     180         [ -  + ]:         163 :     if (!twobyte_chars)
     181                 :           0 :         return nullptr;
     182                 :             : 
     183                 :         163 :     return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen);
     184                 :         163 : }
     185                 :             : 
     186                 :             : bool
     187                 :       43334 : gjs_string_from_utf8(JSContext             *context,
     188                 :             :                      const char            *utf8_string,
     189                 :             :                      JS::MutableHandleValue value_p)
     190                 :             : {
     191                 :       43334 :     JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string));
     192                 :       43334 :     JS::RootedString str(context, JS_NewStringCopyUTF8Z(context, chars));
     193         [ -  + ]:       43334 :     if (!str)
     194                 :           0 :         return false;
     195                 :             : 
     196                 :       43334 :     value_p.setString(str);
     197                 :       43334 :     return true;
     198                 :       43334 : }
     199                 :             : 
     200                 :             : bool
     201                 :          34 : gjs_string_from_utf8_n(JSContext             *cx,
     202                 :             :                        const char            *utf8_chars,
     203                 :             :                        size_t                 len,
     204                 :             :                        JS::MutableHandleValue out)
     205                 :             : {
     206                 :          34 :     JS::UTF8Chars chars(utf8_chars, len);
     207                 :          34 :     JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
     208         [ +  - ]:          34 :     if (str)
     209                 :          34 :         out.setString(str);
     210                 :             : 
     211                 :          34 :     return !!str;
     212                 :          34 : }
     213                 :             : 
     214                 :         181 : bool gjs_string_to_filename(JSContext* context, const JS::Value filename_val,
     215                 :             :                             Gjs::AutoChar* filename_string) {
     216                 :         181 :     Gjs::AutoError error;
     217                 :             : 
     218                 :             :     /* gjs_string_to_filename verifies that filename_val is a string */
     219                 :             : 
     220                 :         181 :     JS::UniqueChars tmp = gjs_string_to_utf8(context, filename_val);
     221         [ -  + ]:         181 :     if (!tmp)
     222                 :           0 :         return false;
     223                 :             : 
     224                 :         181 :     error = NULL;
     225                 :             :     *filename_string =
     226                 :         181 :         g_filename_from_utf8(tmp.get(), -1, nullptr, nullptr, &error);
     227         [ -  + ]:         181 :     if (!*filename_string)
     228                 :           0 :         return gjs_throw_gerror_message(context, error);
     229                 :             : 
     230                 :         181 :     return true;
     231                 :         181 : }
     232                 :             : 
     233                 :             : bool
     234                 :          12 : gjs_string_from_filename(JSContext             *context,
     235                 :             :                          const char            *filename_string,
     236                 :             :                          ssize_t                n_bytes,
     237                 :             :                          JS::MutableHandleValue value_p)
     238                 :             : {
     239                 :             :     gsize written;
     240                 :          12 :     Gjs::AutoError error;
     241                 :             : 
     242                 :          12 :     error = NULL;
     243                 :             :     Gjs::AutoChar utf8_string{g_filename_to_utf8(filename_string, n_bytes,
     244                 :          12 :                                                  nullptr, &written, &error)};
     245         [ -  + ]:          12 :     if (error) {
     246                 :           0 :         gjs_throw(context,
     247                 :             :                   "Could not convert UTF-8 string '%s' to a filename: '%s'",
     248                 :           0 :                   filename_string, error->message);
     249                 :           0 :         return false;
     250                 :             :     }
     251                 :             : 
     252                 :          12 :     return gjs_string_from_utf8_n(context, utf8_string, written, value_p);
     253                 :          12 : }
     254                 :             : 
     255                 :             : /* Converts a JSString's array of Latin-1 chars to an array of a wider integer
     256                 :             :  * type, by what the compiler believes is the most efficient method possible */
     257                 :             : template <typename T>
     258                 :        3610 : GJS_JSAPI_RETURN_CONVENTION static bool from_latin1(JSContext* cx,
     259                 :             :                                                     JSString* str, T** data_p,
     260                 :             :                                                     size_t* len_p) {
     261                 :             :     /* No garbage collection should be triggered while we are using the string's
     262                 :             :      * chars. Crash if that happens. */
     263                 :        3610 :     JS::AutoCheckCannotGC nogc;
     264                 :             : 
     265                 :             :     const JS::Latin1Char *js_data =
     266                 :        3610 :         JS_GetLatin1StringCharsAndLength(cx, nogc, str, len_p);
     267         [ -  + ]:        3610 :     if (js_data == NULL)
     268                 :           0 :         return false;
     269                 :             : 
     270                 :             :     /* Unicode codepoints 0x00-0xFF are the same as Latin-1
     271                 :             :      * codepoints, so we can preserve the string length and simply
     272                 :             :      * copy the codepoints to an array of different-sized ints */
     273                 :             : 
     274                 :        3610 :     *data_p = g_new(T, *len_p);
     275                 :             : 
     276                 :             :     /* This will probably use a loop, unfortunately */
     277                 :        3610 :     std::copy(js_data, js_data + *len_p, *data_p);
     278                 :        3610 :     return true;
     279                 :        3610 : }
     280                 :             : 
     281                 :             : /**
     282                 :             :  * gjs_string_get_char16_data:
     283                 :             :  * @context: js context
     284                 :             :  * @str: a rooted JSString
     285                 :             :  * @data_p: address to return allocated data buffer
     286                 :             :  * @len_p: address to return length of data (number of 16-bit characters)
     287                 :             :  *
     288                 :             :  * Get the binary data (as a sequence of 16-bit characters) in @str.
     289                 :             :  *
     290                 :             :  * Returns: false if exception thrown
     291                 :             :  **/
     292                 :             : bool
     293                 :        3917 : gjs_string_get_char16_data(JSContext       *context,
     294                 :             :                            JS::HandleString str,
     295                 :             :                            char16_t       **data_p,
     296                 :             :                            size_t          *len_p)
     297                 :             : {
     298         [ +  + ]:        3917 :     if (JS::StringHasLatin1Chars(str))
     299                 :        3609 :         return from_latin1(context, str, data_p, len_p);
     300                 :             : 
     301                 :             :     /* From this point on, crash if a GC is triggered while we are using
     302                 :             :      * the string's chars */
     303                 :         308 :     JS::AutoCheckCannotGC nogc;
     304                 :             : 
     305                 :             :     const char16_t *js_data =
     306                 :         308 :         JS_GetTwoByteStringCharsAndLength(context, nogc, str, len_p);
     307                 :             : 
     308         [ -  + ]:         308 :     if (js_data == NULL)
     309                 :           0 :         return false;
     310                 :             : 
     311                 :             :     mozilla::CheckedInt<size_t> len_bytes =
     312                 :         308 :         mozilla::CheckedInt<size_t>(*len_p) * sizeof(*js_data);
     313         [ -  + ]:         308 :     if (!len_bytes.isValid()) {
     314                 :           0 :         JS_ReportOutOfMemory(context);  // cannot call gjs_throw, it may GC
     315                 :           0 :         return false;
     316                 :             :     }
     317                 :             : 
     318                 :         308 :     *data_p = static_cast<char16_t*>(g_memdup2(js_data, len_bytes.value()));
     319                 :             : 
     320                 :         308 :     return true;
     321                 :         308 : }
     322                 :             : 
     323                 :             : /**
     324                 :             :  * gjs_string_to_ucs4:
     325                 :             :  * @cx: a #JSContext
     326                 :             :  * @str: rooted JSString
     327                 :             :  * @ucs4_string_p: return location for a #gunichar array
     328                 :             :  * @len_p: return location for @ucs4_string_p length
     329                 :             :  *
     330                 :             :  * Returns: true on success, false otherwise in which case a JS error is thrown
     331                 :             :  */
     332                 :             : bool
     333                 :           4 : gjs_string_to_ucs4(JSContext       *cx,
     334                 :             :                    JS::HandleString str,
     335                 :             :                    gunichar       **ucs4_string_p,
     336                 :             :                    size_t          *len_p)
     337                 :             : {
     338         [ -  + ]:           4 :     if (ucs4_string_p == NULL)
     339                 :           0 :         return true;
     340                 :             : 
     341                 :             :     size_t len;
     342                 :           4 :     Gjs::AutoError error;
     343                 :             : 
     344         [ +  + ]:           4 :     if (JS::StringHasLatin1Chars(str))
     345                 :           1 :         return from_latin1(cx, str, ucs4_string_p, len_p);
     346                 :             : 
     347                 :             :     /* From this point on, crash if a GC is triggered while we are using
     348                 :             :      * the string's chars */
     349                 :           3 :     JS::AutoCheckCannotGC nogc;
     350                 :             : 
     351                 :             :     const char16_t *utf16 =
     352                 :           3 :         JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
     353                 :             : 
     354         [ -  + ]:           3 :     if (utf16 == NULL) {
     355                 :           0 :         gjs_throw(cx, "Failed to get UTF-16 string data");
     356                 :           0 :         return false;
     357                 :             :     }
     358                 :             : 
     359         [ +  - ]:           3 :     if (ucs4_string_p != NULL) {
     360                 :             :         long length;
     361                 :           3 :         *ucs4_string_p = g_utf16_to_ucs4(reinterpret_cast<const gunichar2 *>(utf16),
     362                 :             :                                          len, NULL, &length, &error);
     363         [ -  + ]:           3 :         if (*ucs4_string_p == NULL) {
     364                 :           0 :             gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s",
     365                 :           0 :                       error->message);
     366                 :           0 :             return false;
     367                 :             :         }
     368         [ +  - ]:           3 :         if (len_p != NULL)
     369                 :           3 :             *len_p = (size_t) length;
     370                 :             :     }
     371                 :             : 
     372                 :           3 :     return true;
     373                 :           4 : }
     374                 :             : 
     375                 :             : /**
     376                 :             :  * gjs_string_from_ucs4:
     377                 :             :  * @cx: a #JSContext
     378                 :             :  * @ucs4_string: string of #gunichar
     379                 :             :  * @n_chars: number of characters in @ucs4_string or -1 for zero-terminated
     380                 :             :  * @value_p: JS::Value that will be filled with a string
     381                 :             :  *
     382                 :             :  * Returns: true on success, false otherwise in which case a JS error is thrown
     383                 :             :  */
     384                 :             : bool
     385                 :           2 : gjs_string_from_ucs4(JSContext             *cx,
     386                 :             :                      const gunichar        *ucs4_string,
     387                 :             :                      ssize_t                n_chars,
     388                 :             :                      JS::MutableHandleValue value_p)
     389                 :             : {
     390                 :             :     // a null array pointer takes precedence over whatever `n_chars` says
     391         [ -  + ]:           2 :     if (!ucs4_string) {
     392                 :           0 :         value_p.setString(JS_GetEmptyString(cx));
     393                 :           0 :         return true;
     394                 :             :     }
     395                 :             : 
     396                 :             :     long u16_string_length;
     397                 :           2 :     Gjs::AutoError error;
     398                 :             : 
     399                 :           2 :     gunichar2* u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, nullptr,
     400                 :             :                                             &u16_string_length, &error);
     401         [ -  + ]:           2 :     if (!u16_string) {
     402                 :           0 :         gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16: %s",
     403                 :           0 :                   error->message);
     404                 :           0 :         return false;
     405                 :             :     }
     406                 :             : 
     407                 :             :     // Sadly, must copy, because js::UniquePtr forces that chars passed to
     408                 :             :     // JS_NewUCString() must have been allocated by the JS engine.
     409                 :             :     JS::RootedString str(
     410                 :           2 :         cx, JS_NewUCStringCopyN(cx, reinterpret_cast<char16_t*>(u16_string),
     411                 :           2 :                                 u16_string_length));
     412                 :             : 
     413                 :           2 :     g_free(u16_string);
     414                 :             : 
     415         [ -  + ]:           2 :     if (!str) {
     416                 :           0 :         gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16");
     417                 :           0 :         return false;
     418                 :             :     }
     419                 :             : 
     420                 :           2 :     value_p.setString(str);
     421                 :           2 :     return true;
     422                 :           2 : }
     423                 :             : 
     424                 :             : /**
     425                 :             :  * gjs_get_string_id:
     426                 :             :  * @cx: a #JSContext
     427                 :             :  * @id: a jsid that is an object hash key (could be an int or string)
     428                 :             :  * @name_p place to store ASCII string version of key
     429                 :             :  *
     430                 :             :  * If the id is not a string ID, return true and set *name_p to nullptr.
     431                 :             :  * Otherwise, return true and fill in *name_p with ASCII name of id.
     432                 :             :  *
     433                 :             :  * Returns: false on error, otherwise true
     434                 :             :  **/
     435                 :       32778 : bool gjs_get_string_id(JSContext* cx, jsid id, JS::UniqueChars* name_p) {
     436         [ +  + ]:       32778 :     if (!id.isString()) {
     437                 :        4365 :         name_p->reset();
     438                 :        4365 :         return true;
     439                 :             :     }
     440                 :             : 
     441                 :       28413 :     JSLinearString* lstr = id.toLinearString();
     442                 :       28413 :     JS::RootedString s(cx, JS_FORGET_STRING_LINEARNESS(lstr));
     443                 :       28413 :     *name_p = JS_EncodeStringToUTF8(cx, s);
     444                 :       28413 :     return !!*name_p;
     445                 :       28413 : }
     446                 :             : 
     447                 :             : /**
     448                 :             :  * gjs_unichar_from_string:
     449                 :             :  * @string: A string
     450                 :             :  * @result: (out): A unicode character
     451                 :             :  *
     452                 :             :  * If successful, @result is assigned the Unicode codepoint
     453                 :             :  * corresponding to the first full character in @string.  This
     454                 :             :  * function handles characters outside the BMP.
     455                 :             :  *
     456                 :             :  * If @string is empty, @result will be 0.  An exception will
     457                 :             :  * be thrown if @string can not be represented as UTF-8.
     458                 :             :  */
     459                 :             : bool
     460                 :           3 : gjs_unichar_from_string (JSContext *context,
     461                 :             :                          JS::Value  value,
     462                 :             :                          gunichar  *result)
     463                 :             : {
     464                 :           3 :     JS::UniqueChars utf8_str = gjs_string_to_utf8(context, value);
     465         [ +  - ]:           3 :     if (utf8_str) {
     466                 :           3 :         *result = g_utf8_get_char(utf8_str.get());
     467                 :           3 :         return true;
     468                 :             :     }
     469                 :           0 :     return false;
     470                 :           3 : }
     471                 :             : 
     472                 :             : jsid
     473                 :       47670 : gjs_intern_string_to_id(JSContext  *cx,
     474                 :             :                         const char *string)
     475                 :             : {
     476                 :       47670 :     JS::RootedString str(cx, JS_AtomizeAndPinString(cx, string));
     477         [ -  + ]:       47670 :     if (!str)
     478                 :           0 :         return JS::PropertyKey::Void();
     479                 :       47670 :     return JS::PropertyKey::fromPinnedString(str);
     480                 :       47670 : }
     481                 :             : 
     482                 :           6 : std::string gjs_debug_bigint(JS::BigInt* bi) {
     483                 :             :     // technically this prints the value % INT64_MAX, cast into an int64_t if
     484                 :             :     // the value is negative, otherwise cast into uint64_t
     485                 :           6 :     std::ostringstream out;
     486         [ +  + ]:           6 :     if (JS::BigIntIsNegative(bi))
     487                 :           2 :         out << JS::ToBigInt64(bi);
     488                 :             :     else
     489                 :           4 :         out << JS::ToBigUint64(bi);
     490                 :           6 :     out << "n (modulo 2^64)";
     491                 :           6 :     return out.str();
     492                 :           6 : }
     493                 :             : 
     494                 :             : enum Quotes {
     495                 :             :     DoubleQuotes,
     496                 :             :     NoQuotes,
     497                 :             : };
     498                 :             : 
     499                 :       12470 : [[nodiscard]] static std::string gjs_debug_linear_string(JSLinearString* str,
     500                 :             :                                                          Quotes quotes) {
     501                 :       12470 :     size_t len = JS::GetLinearStringLength(str);
     502                 :             : 
     503                 :       12470 :     std::ostringstream out;
     504         [ +  + ]:       12470 :     if (quotes == DoubleQuotes)
     505                 :       12368 :         out << '"';
     506                 :             : 
     507                 :       12470 :     JS::AutoCheckCannotGC nogc;
     508         [ +  - ]:       12470 :     if (JS::LinearStringHasLatin1Chars(str)) {
     509                 :       12470 :         const JS::Latin1Char* chars = JS::GetLatin1LinearStringChars(nogc, str);
     510                 :       12470 :         out << std::string(reinterpret_cast<const char*>(chars), len);
     511         [ +  + ]:       12470 :         if (quotes == DoubleQuotes)
     512                 :       12368 :             out << '"';
     513                 :       12470 :         return out.str();
     514                 :             :     }
     515                 :             : 
     516                 :           0 :     const char16_t* chars = JS::GetTwoByteLinearStringChars(nogc, str);
     517         [ #  # ]:           0 :     for (size_t ix = 0; ix < len; ix++) {
     518                 :           0 :         char16_t c = chars[ix];
     519         [ #  # ]:           0 :         if (c == '\n')
     520                 :           0 :             out << "\\n";
     521         [ #  # ]:           0 :         else if (c == '\t')
     522                 :           0 :             out << "\\t";
     523   [ #  #  #  # ]:           0 :         else if (c >= 32 && c < 127)
     524                 :           0 :             out << c;
     525         [ #  # ]:           0 :         else if (c <= 255)
     526                 :           0 :             out << "\\x" << std::setfill('0') << std::setw(2) << unsigned(c);
     527                 :             :         else
     528                 :           0 :             out << "\\x" << std::setfill('0') << std::setw(4) << unsigned(c);
     529                 :             :     }
     530         [ #  # ]:           0 :     if (quotes == DoubleQuotes)
     531                 :           0 :         out << '"';
     532                 :           0 :     return out.str();
     533                 :       12470 : }
     534                 :             : 
     535                 :             : std::string
     536                 :       12368 : gjs_debug_string(JSString *str)
     537                 :             : {
     538         [ -  + ]:       12368 :     if (!str)
     539                 :           0 :         return "<null string>";
     540         [ -  + ]:       12368 :     if (!JS_StringIsLinear(str)) {
     541                 :             :         std::ostringstream out("<non-flat string of length ",
     542                 :           0 :                                std::ios_base::ate);
     543                 :           0 :         out << JS_GetStringLength(str) << '>';
     544                 :           0 :         return out.str();
     545                 :           0 :     }
     546                 :             :     return gjs_debug_linear_string(JS_ASSERT_STRING_IS_LINEAR(str),
     547                 :       12368 :                                    DoubleQuotes);
     548                 :             : }
     549                 :             : 
     550                 :             : std::string
     551                 :           1 : gjs_debug_symbol(JS::Symbol * const sym)
     552                 :             : {
     553         [ -  + ]:           1 :     if (!sym)
     554                 :           0 :         return "<null symbol>";
     555                 :             : 
     556                 :             :     /* This is OK because JS::GetSymbolCode() and JS::GetSymbolDescription()
     557                 :             :      * can't cause a garbage collection */
     558                 :           1 :     JS::HandleSymbol handle = JS::HandleSymbol::fromMarkedLocation(&sym);
     559                 :           1 :     JS::SymbolCode code = JS::GetSymbolCode(handle);
     560                 :           1 :     JSString *descr = JS::GetSymbolDescription(handle);
     561                 :             : 
     562         [ -  + ]:           1 :     if (size_t(code) < JS::WellKnownSymbolLimit)
     563                 :           0 :         return gjs_debug_string(descr);
     564                 :             : 
     565                 :           1 :     std::ostringstream out;
     566         [ -  + ]:           1 :     if (code == JS::SymbolCode::InSymbolRegistry) {
     567                 :           0 :         out << "Symbol.for(";
     568         [ #  # ]:           0 :         if (descr)
     569                 :           0 :             out << gjs_debug_string(descr);
     570                 :             :         else
     571                 :           0 :             out << "undefined";
     572                 :           0 :         out << ")";
     573                 :           0 :         return out.str();
     574                 :             :     }
     575         [ +  - ]:           1 :     if (code == JS::SymbolCode::UniqueSymbol) {
     576         [ +  - ]:           1 :         if (descr)
     577                 :           1 :             out << "Symbol(" << gjs_debug_string(descr) << ")";
     578                 :             :         else
     579                 :           0 :             out << "<Symbol at " << sym << ">";
     580                 :           1 :         return out.str();
     581                 :             :     }
     582                 :             : 
     583                 :           0 :     out << "<unexpected symbol code " << uint32_t(code) << ">";
     584                 :           0 :     return out.str();
     585                 :           1 : }
     586                 :             : 
     587                 :             : std::string
     588                 :       16933 : gjs_debug_object(JSObject * const obj)
     589                 :             : {
     590         [ +  + ]:       16933 :     if (!obj)
     591                 :        4488 :         return "<null object>";
     592                 :             : 
     593                 :       14689 :     std::ostringstream out;
     594                 :             : 
     595         [ +  + ]:       14689 :     if (js::IsFunctionObject(obj)) {
     596                 :        2671 :         JSFunction* fun = JS_GetObjectFunction(obj);
     597                 :        2671 :         JSString* display_name = JS_GetMaybePartialFunctionDisplayId(fun);
     598   [ +  -  +  +  :        2671 :         if (display_name && JS_GetStringLength(display_name))
                   +  + ]
     599                 :          50 :             out << "<function " << gjs_debug_string(display_name);
     600                 :             :         else
     601                 :        2621 :             out << "<anonymous function";
     602                 :        2671 :         out << " at " << fun << '>';
     603                 :        2671 :         return out.str();
     604                 :             :     }
     605                 :             : 
     606                 :             :     // This is OK because the promise methods can't cause a garbage collection
     607                 :       12018 :     JS::HandleObject handle = JS::HandleObject::fromMarkedLocation(&obj);
     608         [ +  + ]:       12018 :     if (JS::IsPromiseObject(handle)) {
     609                 :         191 :         out << '<';
     610                 :         191 :         JS::PromiseState state = JS::GetPromiseState(handle);
     611         [ +  - ]:         191 :         if (state == JS::PromiseState::Pending)
     612                 :         191 :             out << "pending ";
     613                 :         191 :         out << "promise " << JS::GetPromiseID(handle) << " at " << obj;
     614         [ -  + ]:         191 :         if (state != JS::PromiseState::Pending) {
     615                 :           0 :             out << ' ';
     616                 :             :             out << (state == JS::PromiseState::Rejected ? "rejected"
     617         [ #  # ]:           0 :                                                         : "resolved");
     618                 :           0 :             out << " with " << gjs_debug_value(JS::GetPromiseResult(handle));
     619                 :             :         }
     620                 :         191 :         out << '>';
     621                 :         191 :         return out.str();
     622                 :             :     }
     623                 :             : 
     624                 :       11827 :     const JSClass* clasp = JS::GetClass(obj);
     625                 :       11827 :     out << "<object " << clasp->name << " at " << obj <<  '>';
     626                 :       11827 :     return out.str();
     627                 :       14689 : }
     628                 :             : 
     629                 :         632 : std::string gjs_debug_callable(JSObject* callable) {
     630         [ +  - ]:         632 :     if (JSFunction* fn = JS_GetObjectFunction(callable)) {
     631         [ +  - ]:         632 :         if (JSString* display_id = JS_GetMaybePartialFunctionDisplayId(fn))
     632                 :         632 :             return {"function " + gjs_debug_string(display_id)};
     633                 :           0 :         return {"unnamed function"};
     634                 :             :     }
     635                 :           0 :     return {"callable object " + gjs_debug_object(callable)};
     636                 :             : }
     637                 :             : 
     638                 :             : std::string
     639                 :       11645 : gjs_debug_value(JS::Value v)
     640                 :             : {
     641         [ +  + ]:       11645 :     if (v.isNull())
     642                 :           2 :         return "null";
     643         [ +  + ]:       11644 :     if (v.isUndefined())
     644                 :           4 :         return "undefined";
     645         [ +  + ]:       11642 :     if (v.isInt32()) {
     646                 :           1 :         std::ostringstream out;
     647                 :           1 :         out << v.toInt32();
     648                 :           1 :         return out.str();
     649                 :           1 :     }
     650         [ -  + ]:       11641 :     if (v.isDouble()) {
     651                 :           0 :         std::ostringstream out;
     652                 :           0 :         out << v.toDouble();
     653                 :           0 :         return out.str();
     654                 :           0 :     }
     655         [ +  + ]:       11641 :     if (v.isBigInt())
     656                 :           1 :         return gjs_debug_bigint(v.toBigInt());
     657         [ +  + ]:       11640 :     if (v.isString())
     658                 :           2 :         return gjs_debug_string(v.toString());
     659         [ +  + ]:       11638 :     if (v.isSymbol())
     660                 :           1 :         return gjs_debug_symbol(v.toSymbol());
     661         [ +  + ]:       11637 :     if (v.isObject())
     662                 :       11636 :         return gjs_debug_object(&v.toObject());
     663         [ +  - ]:           1 :     if (v.isBoolean())
     664         [ +  - ]:           2 :         return (v.toBoolean() ? "true" : "false");
     665         [ #  # ]:           0 :     if (v.isMagic())
     666                 :           0 :         return "<magic>";
     667                 :           0 :     return "unexpected value";
     668                 :             : }
     669                 :             : 
     670                 :             : std::string
     671                 :         102 : gjs_debug_id(jsid id)
     672                 :             : {
     673         [ +  - ]:         102 :     if (id.isString())
     674                 :         102 :         return gjs_debug_linear_string(id.toLinearString(), NoQuotes);
     675                 :           0 :     return gjs_debug_value(js::IdToValue(id));
     676                 :             : }
        

Generated by: LCOV version 2.0-1