LCOV - code coverage report
Current view: top level - gjs - jsapi-util-string.cpp (source / functions) Coverage Total Hit
Test: gjs-1.86.0 Code Coverage Lines: 73.3 % 318 233
Test Date: 2025-11-16 18:28:38 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                 :         388 : Gjs::AutoChar gjs_hyphen_to_underscore(const char* str) {
      45                 :         388 :     char *s = g_strdup(str);
      46                 :         388 :     char *retval = s;
      47         [ +  + ]:        4497 :     while (*(s++) != '\0') {
      48         [ +  + ]:        4109 :         if (*s == '-')
      49                 :         226 :             *s = '_';
      50                 :             :     }
      51                 :         388 :     return retval;
      52                 :             : }
      53                 :             : 
      54                 :         164 : Gjs::AutoChar gjs_hyphen_to_camel(const char* str) {
      55                 :         164 :     Gjs::AutoChar retval{static_cast<char*>(g_malloc(strlen(str) + 1))};
      56                 :         164 :     const char* input_iter = str;
      57                 :         164 :     char* output_iter = retval.get();
      58                 :         164 :     bool uppercase_next = false;
      59         [ +  + ]:        2312 :     while (*input_iter != '\0') {
      60         [ +  + ]:        2148 :         if (*input_iter == '-') {
      61                 :         126 :             uppercase_next = true;
      62         [ +  + ]:        2022 :         } else if (uppercase_next) {
      63                 :         126 :             *output_iter++ = g_ascii_toupper(*input_iter);
      64                 :         126 :             uppercase_next = false;
      65                 :             :         } else {
      66                 :        1896 :             *output_iter++ = *input_iter;
      67                 :             :         }
      68                 :        2148 :         input_iter++;
      69                 :             :     }
      70                 :         164 :     *output_iter = '\0';
      71                 :         164 :     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                 :       46070 : JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) {
      89         [ -  + ]:       46070 :     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                 :       46070 :     JS::RootedString str(cx, value.toString());
      95                 :       46070 :     return JS_EncodeStringToUTF8(cx, str);
      96                 :       46070 : }
      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                 :          41 : bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output,
     116                 :             :                           size_t* output_len) {
     117                 :          41 :     JSLinearString* linear = JS_EnsureLinearString(cx, str);
     118         [ -  + ]:          41 :     if (!linear)
     119                 :           0 :         return false;
     120                 :             : 
     121                 :          41 :     size_t length = JS::GetDeflatedUTF8StringLength(linear);
     122                 :          41 :     char* bytes = js_pod_malloc<char>(length + 1);
     123         [ -  + ]:          41 :     if (!bytes)
     124                 :           0 :         return false;
     125                 :             : 
     126                 :             :     // Append a zero-terminator to the string.
     127                 :          41 :     bytes[length] = '\0';
     128                 :             : 
     129                 :             :     size_t deflated_length [[maybe_unused]] =
     130                 :          41 :         JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length));
     131                 :          41 :     g_assert(deflated_length == length);
     132                 :             : 
     133                 :          41 :     *output_len = length;
     134                 :          41 :     *output = JS::UniqueChars(bytes);
     135                 :          41 :     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::UTF8Chars 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                 :         165 : JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string,
     173                 :             :                                        size_t len) {
     174                 :         165 :     JS::UTF8Chars chars(utf8_string, len);
     175                 :             :     size_t outlen;
     176                 :             :     JS::UniqueTwoByteChars twobyte_chars(
     177                 :         165 :         JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen,
     178                 :             :                                              js::MallocArena)
     179                 :         165 :             .get());
     180         [ -  + ]:         165 :     if (!twobyte_chars)
     181                 :           0 :         return nullptr;
     182                 :             : 
     183                 :         165 :     return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen);
     184                 :         165 : }
     185                 :             : 
     186                 :       45879 : bool gjs_string_from_utf8(JSContext* cx, const char* utf8_string,
     187                 :             :                           JS::MutableHandleValue value_p) {
     188                 :       45879 :     JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string));
     189                 :       45879 :     JS::RootedString str{cx, JS_NewStringCopyUTF8Z(cx, chars)};
     190         [ -  + ]:       45879 :     if (!str)
     191                 :           0 :         return false;
     192                 :             : 
     193                 :       45879 :     value_p.setString(str);
     194                 :       45879 :     return true;
     195                 :       45879 : }
     196                 :             : 
     197                 :             : bool
     198                 :          36 : gjs_string_from_utf8_n(JSContext             *cx,
     199                 :             :                        const char            *utf8_chars,
     200                 :             :                        size_t                 len,
     201                 :             :                        JS::MutableHandleValue out)
     202                 :             : {
     203                 :          36 :     JS::UTF8Chars chars(utf8_chars, len);
     204                 :          36 :     JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
     205         [ +  - ]:          36 :     if (str)
     206                 :          36 :         out.setString(str);
     207                 :             : 
     208                 :          36 :     return !!str;
     209                 :          36 : }
     210                 :             : 
     211                 :         187 : bool gjs_string_to_filename(JSContext* cx, const JS::Value filename_val,
     212                 :             :                             Gjs::AutoChar* filename_string) {
     213                 :             :     // gjs_string_to_utf8() verifies that filename_val is a string
     214                 :         187 :     JS::UniqueChars tmp{gjs_string_to_utf8(cx, filename_val)};
     215         [ -  + ]:         187 :     if (!tmp)
     216                 :           0 :         return false;
     217                 :             : 
     218                 :         187 :     Gjs::AutoError error;
     219                 :             :     *filename_string =
     220                 :         187 :         g_filename_from_utf8(tmp.get(), -1, nullptr, nullptr, &error);
     221         [ -  + ]:         187 :     if (!*filename_string)
     222                 :           0 :         return gjs_throw_gerror_message(cx, error);
     223                 :             : 
     224                 :         187 :     return true;
     225                 :         187 : }
     226                 :             : 
     227                 :          14 : bool gjs_string_from_filename(JSContext* cx, const char* filename_string,
     228                 :             :                               ssize_t n_bytes, JS::MutableHandleValue value_p) {
     229                 :             :     gsize written;  // Do not use size_t, may be different width
     230                 :          14 :     Gjs::AutoError error;
     231                 :             : 
     232                 :             :     Gjs::AutoChar utf8_string{g_filename_to_utf8(filename_string, n_bytes,
     233                 :          14 :                                                  nullptr, &written, &error)};
     234         [ -  + ]:          14 :     if (error) {
     235                 :           0 :         Gjs::AutoChar escaped_char{g_strescape(filename_string, nullptr)};
     236                 :           0 :         gjs_throw(
     237                 :             :             cx,
     238                 :             :             "Could not convert filename string to UTF-8 for string: %s. If "
     239                 :             :             "string is invalid UTF-8 and used for display purposes, try GLib "
     240                 :             :             "attribute standard::display-name. The reason is: %s. ",
     241                 :           0 :             escaped_char.get(), error->message);
     242                 :           0 :         return false;
     243                 :           0 :     }
     244                 :             : 
     245                 :          14 :     return gjs_string_from_utf8_n(cx, utf8_string, written, value_p);
     246                 :          14 : }
     247                 :             : 
     248                 :             : /* Converts a JSString's array of Latin-1 chars to an array of a wider integer
     249                 :             :  * type, by what the compiler believes is the most efficient method possible */
     250                 :             : template <typename T>
     251                 :        3673 : GJS_JSAPI_RETURN_CONVENTION static bool from_latin1(JSContext* cx,
     252                 :             :                                                     JSString* str, T** data_p,
     253                 :             :                                                     size_t* len_p) {
     254                 :             :     /* No garbage collection should be triggered while we are using the string's
     255                 :             :      * chars. Crash if that happens. */
     256                 :        3673 :     JS::AutoCheckCannotGC nogc;
     257                 :             : 
     258                 :             :     const JS::Latin1Char *js_data =
     259                 :        3673 :         JS_GetLatin1StringCharsAndLength(cx, nogc, str, len_p);
     260         [ -  + ]:        3673 :     if (js_data == nullptr)
     261                 :           0 :         return false;
     262                 :             : 
     263                 :             :     /* Unicode codepoints 0x00-0xFF are the same as Latin-1
     264                 :             :      * codepoints, so we can preserve the string length and simply
     265                 :             :      * copy the codepoints to an array of different-sized ints */
     266                 :             : 
     267                 :        3673 :     *data_p = g_new(T, *len_p);
     268                 :             : 
     269                 :             :     // This will probably use a loop, unfortunately
     270                 :        3673 :     std::copy(js_data, js_data + *len_p, *data_p);
     271                 :        3673 :     return true;
     272                 :        3673 : }
     273                 :             : 
     274                 :             : /**
     275                 :             :  * gjs_string_get_char16_data:
     276                 :             :  * @cx: js context
     277                 :             :  * @str: a rooted JSString
     278                 :             :  * @data_p: address to return allocated data buffer
     279                 :             :  * @len_p: address to return length of data (number of 16-bit characters)
     280                 :             :  *
     281                 :             :  * Get the binary data (as a sequence of 16-bit characters) in @str.
     282                 :             :  *
     283                 :             :  * Returns: false if exception thrown
     284                 :             :  */
     285                 :        3983 : bool gjs_string_get_char16_data(JSContext* cx, JS::HandleString str,
     286                 :             :                                 char16_t** data_p, size_t* len_p) {
     287         [ +  + ]:        3983 :     if (JS::StringHasLatin1Chars(str))
     288                 :        3672 :         return from_latin1(cx, str, data_p, len_p);
     289                 :             : 
     290                 :             :     /* From this point on, crash if a GC is triggered while we are using
     291                 :             :      * the string's chars */
     292                 :         311 :     JS::AutoCheckCannotGC nogc;
     293                 :             : 
     294                 :             :     const char16_t* js_data =
     295                 :         311 :         JS_GetTwoByteStringCharsAndLength(cx, nogc, str, len_p);
     296                 :             : 
     297         [ -  + ]:         311 :     if (js_data == nullptr)
     298                 :           0 :         return false;
     299                 :             : 
     300                 :             :     mozilla::CheckedInt<size_t> len_bytes =
     301                 :         311 :         mozilla::CheckedInt<size_t>(*len_p) * sizeof(*js_data);
     302         [ -  + ]:         311 :     if (!len_bytes.isValid()) {
     303                 :           0 :         JS_ReportOutOfMemory(cx);  // cannot call gjs_throw, it may GC
     304                 :           0 :         return false;
     305                 :             :     }
     306                 :             : 
     307                 :         311 :     *data_p = static_cast<char16_t*>(g_memdup2(js_data, len_bytes.value()));
     308                 :             : 
     309                 :         311 :     return true;
     310                 :         311 : }
     311                 :             : 
     312                 :             : /**
     313                 :             :  * gjs_string_to_ucs4:
     314                 :             :  * @cx: a #JSContext
     315                 :             :  * @str: rooted JSString
     316                 :             :  * @ucs4_string_p: return location for a #gunichar array
     317                 :             :  * @len_p: return location for @ucs4_string_p length
     318                 :             :  *
     319                 :             :  * Returns: true on success, false otherwise in which case a JS error is thrown
     320                 :             :  */
     321                 :             : bool
     322                 :           4 : gjs_string_to_ucs4(JSContext       *cx,
     323                 :             :                    JS::HandleString str,
     324                 :             :                    gunichar       **ucs4_string_p,
     325                 :             :                    size_t          *len_p)
     326                 :             : {
     327         [ -  + ]:           4 :     if (ucs4_string_p == nullptr)
     328                 :           0 :         return true;
     329                 :             : 
     330                 :             :     size_t len;
     331                 :           4 :     Gjs::AutoError error;
     332                 :             : 
     333         [ +  + ]:           4 :     if (JS::StringHasLatin1Chars(str))
     334                 :           1 :         return from_latin1(cx, str, ucs4_string_p, len_p);
     335                 :             : 
     336                 :             :     /* From this point on, crash if a GC is triggered while we are using
     337                 :             :      * the string's chars */
     338                 :           3 :     JS::AutoCheckCannotGC nogc;
     339                 :             : 
     340                 :             :     const char16_t *utf16 =
     341                 :           3 :         JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
     342                 :             : 
     343         [ -  + ]:           3 :     if (utf16 == nullptr) {
     344                 :           0 :         gjs_throw(cx, "Failed to get UTF-16 string data");
     345                 :           0 :         return false;
     346                 :             :     }
     347                 :             : 
     348         [ +  - ]:           3 :     if (ucs4_string_p != nullptr) {
     349                 :             :         long length;
     350                 :           3 :         *ucs4_string_p =
     351                 :           3 :             g_utf16_to_ucs4(reinterpret_cast<const gunichar2*>(utf16), len,
     352                 :             :                             nullptr, &length, &error);
     353         [ -  + ]:           3 :         if (*ucs4_string_p == nullptr) {
     354                 :           0 :             gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s",
     355                 :           0 :                       error->message);
     356                 :           0 :             return false;
     357                 :             :         }
     358         [ +  - ]:           3 :         if (len_p != nullptr)
     359                 :           3 :             *len_p = (size_t) length;
     360                 :             :     }
     361                 :             : 
     362                 :           3 :     return true;
     363                 :           4 : }
     364                 :             : 
     365                 :             : /**
     366                 :             :  * gjs_string_from_ucs4:
     367                 :             :  * @cx: a #JSContext
     368                 :             :  * @ucs4_string: string of #gunichar
     369                 :             :  * @n_chars: number of characters in @ucs4_string or -1 for zero-terminated
     370                 :             :  * @value_p: JS::Value that will be filled with a string
     371                 :             :  *
     372                 :             :  * Returns: true on success, false otherwise in which case a JS error is thrown
     373                 :             :  */
     374                 :             : bool
     375                 :           2 : gjs_string_from_ucs4(JSContext             *cx,
     376                 :             :                      const gunichar        *ucs4_string,
     377                 :             :                      ssize_t                n_chars,
     378                 :             :                      JS::MutableHandleValue value_p)
     379                 :             : {
     380                 :             :     // a null array pointer takes precedence over whatever `n_chars` says
     381         [ -  + ]:           2 :     if (!ucs4_string) {
     382                 :           0 :         value_p.setString(JS_GetEmptyString(cx));
     383                 :           0 :         return true;
     384                 :             :     }
     385                 :             : 
     386                 :             :     long u16_string_length;
     387                 :           2 :     Gjs::AutoError error;
     388                 :             : 
     389                 :           2 :     gunichar2* u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, nullptr,
     390                 :             :                                             &u16_string_length, &error);
     391         [ -  + ]:           2 :     if (!u16_string) {
     392                 :           0 :         gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16: %s",
     393                 :           0 :                   error->message);
     394                 :           0 :         return false;
     395                 :             :     }
     396                 :             : 
     397                 :             :     // Sadly, must copy, because js::UniquePtr forces that chars passed to
     398                 :             :     // JS_NewUCString() must have been allocated by the JS engine.
     399                 :             :     JS::RootedString str(
     400                 :           2 :         cx, JS_NewUCStringCopyN(cx, reinterpret_cast<char16_t*>(u16_string),
     401                 :           2 :                                 u16_string_length));
     402                 :             : 
     403                 :           2 :     g_free(u16_string);
     404                 :             : 
     405         [ -  + ]:           2 :     if (!str) {
     406                 :           0 :         gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16");
     407                 :           0 :         return false;
     408                 :             :     }
     409                 :             : 
     410                 :           2 :     value_p.setString(str);
     411                 :           2 :     return true;
     412                 :           2 : }
     413                 :             : 
     414                 :             : /**
     415                 :             :  * gjs_get_string_id:
     416                 :             :  * @cx: a #JSContext
     417                 :             :  * @id: a jsid that is an object hash key (could be an int or string)
     418                 :             :  * @name_p place to store ASCII string version of key
     419                 :             :  *
     420                 :             :  * If the id is not a string ID, return true and set *name_p to nullptr.
     421                 :             :  * Otherwise, return true and fill in *name_p with ASCII name of id.
     422                 :             :  *
     423                 :             :  * Returns: false on error, otherwise true
     424                 :             :  */
     425                 :       36388 : bool gjs_get_string_id(JSContext* cx, jsid id, JS::UniqueChars* name_p) {
     426         [ +  + ]:       36388 :     if (!id.isString()) {
     427                 :        5272 :         name_p->reset();
     428                 :        5272 :         return true;
     429                 :             :     }
     430                 :             : 
     431                 :       31116 :     JSLinearString* lstr = id.toLinearString();
     432                 :       31116 :     JS::RootedString s(cx, JS_FORGET_STRING_LINEARNESS(lstr));
     433                 :       31116 :     *name_p = JS_EncodeStringToUTF8(cx, s);
     434                 :       31116 :     return !!*name_p;
     435                 :       31116 : }
     436                 :             : 
     437                 :             : /**
     438                 :             :  * gjs_unichar_from_string:
     439                 :             :  * @string: A string
     440                 :             :  * @result: (out): A unicode character
     441                 :             :  *
     442                 :             :  * If successful, @result is assigned the Unicode codepoint
     443                 :             :  * corresponding to the first full character in @string.  This
     444                 :             :  * function handles characters outside the BMP.
     445                 :             :  *
     446                 :             :  * If @string is empty, @result will be 0.  An exception will
     447                 :             :  * be thrown if @string can not be represented as UTF-8.
     448                 :             :  */
     449                 :           3 : bool gjs_unichar_from_string(JSContext* cx, JS::Value value, gunichar* result) {
     450                 :           3 :     JS::UniqueChars utf8_str{gjs_string_to_utf8(cx, value)};
     451         [ +  - ]:           3 :     if (utf8_str) {
     452                 :           3 :         *result = g_utf8_get_char(utf8_str.get());
     453                 :           3 :         return true;
     454                 :             :     }
     455                 :           0 :     return false;
     456                 :           3 : }
     457                 :             : 
     458                 :             : jsid
     459                 :       51853 : gjs_intern_string_to_id(JSContext  *cx,
     460                 :             :                         const char *string)
     461                 :             : {
     462                 :       51853 :     JS::RootedString str(cx, JS_AtomizeAndPinString(cx, string));
     463         [ -  + ]:       51853 :     if (!str)
     464                 :           0 :         return JS::PropertyKey::Void();
     465                 :       51853 :     return JS::PropertyKey::fromPinnedString(str);
     466                 :       51853 : }
     467                 :             : 
     468                 :           6 : std::string gjs_debug_bigint(JS::BigInt* bi) {
     469                 :             :     // technically this prints the value % INT64_MAX, cast into an int64_t if
     470                 :             :     // the value is negative, otherwise cast into uint64_t
     471                 :           6 :     std::ostringstream out;
     472         [ +  + ]:           6 :     if (JS::BigIntIsNegative(bi))
     473                 :           2 :         out << JS::ToBigInt64(bi);
     474                 :             :     else
     475                 :           4 :         out << JS::ToBigUint64(bi);
     476                 :           6 :     out << "n (modulo 2^64)";
     477                 :           6 :     return out.str();
     478                 :           6 : }
     479                 :             : 
     480                 :             : enum Quotes {
     481                 :             :     DoubleQuotes,
     482                 :             :     NoQuotes,
     483                 :             : };
     484                 :             : 
     485                 :       12906 : [[nodiscard]] static std::string gjs_debug_linear_string(JSLinearString* str,
     486                 :             :                                                          Quotes quotes) {
     487                 :       12906 :     size_t len = JS::GetLinearStringLength(str);
     488                 :             : 
     489                 :       12906 :     std::ostringstream out;
     490         [ +  + ]:       12906 :     if (quotes == DoubleQuotes)
     491                 :       12779 :         out << '"';
     492                 :             : 
     493                 :       12906 :     JS::AutoCheckCannotGC nogc;
     494         [ +  - ]:       12906 :     if (JS::LinearStringHasLatin1Chars(str)) {
     495                 :       12906 :         const JS::Latin1Char* chars = JS::GetLatin1LinearStringChars(nogc, str);
     496                 :       12906 :         out << std::string(reinterpret_cast<const char*>(chars), len);
     497         [ +  + ]:       12906 :         if (quotes == DoubleQuotes)
     498                 :       12779 :             out << '"';
     499                 :       12906 :         return out.str();
     500                 :             :     }
     501                 :             : 
     502                 :           0 :     const char16_t* chars = JS::GetTwoByteLinearStringChars(nogc, str);
     503         [ #  # ]:           0 :     for (size_t ix = 0; ix < len; ix++) {
     504                 :           0 :         char16_t c = chars[ix];
     505         [ #  # ]:           0 :         if (c == '\n')
     506                 :           0 :             out << "\\n";
     507         [ #  # ]:           0 :         else if (c == '\t')
     508                 :           0 :             out << "\\t";
     509   [ #  #  #  # ]:           0 :         else if (c >= 32 && c < 127)
     510                 :           0 :             out << c;
     511         [ #  # ]:           0 :         else if (c <= 255)
     512                 :           0 :             out << "\\x" << std::setfill('0') << std::setw(2) << unsigned(c);
     513                 :             :         else
     514                 :           0 :             out << "\\x" << std::setfill('0') << std::setw(4) << unsigned(c);
     515                 :             :     }
     516         [ #  # ]:           0 :     if (quotes == DoubleQuotes)
     517                 :           0 :         out << '"';
     518                 :           0 :     return out.str();
     519                 :       12906 : }
     520                 :             : 
     521                 :             : std::string
     522                 :       12779 : gjs_debug_string(JSString *str)
     523                 :             : {
     524         [ -  + ]:       12779 :     if (!str)
     525                 :           0 :         return "<null string>";
     526         [ -  + ]:       12779 :     if (!JS_StringIsLinear(str)) {
     527                 :           0 :         std::ostringstream out("<non-flat string of length ",
     528                 :           0 :                                std::ios_base::ate);
     529                 :           0 :         out << JS_GetStringLength(str) << '>';
     530                 :           0 :         return out.str();
     531                 :           0 :     }
     532                 :             :     return gjs_debug_linear_string(JS_ASSERT_STRING_IS_LINEAR(str),
     533                 :       12779 :                                    DoubleQuotes);
     534                 :             : }
     535                 :             : 
     536                 :             : std::string
     537                 :           1 : gjs_debug_symbol(JS::Symbol * const sym)
     538                 :             : {
     539         [ -  + ]:           1 :     if (!sym)
     540                 :           0 :         return "<null symbol>";
     541                 :             : 
     542                 :             :     /* This is OK because JS::GetSymbolCode() and JS::GetSymbolDescription()
     543                 :             :      * can't cause a garbage collection */
     544                 :           1 :     JS::HandleSymbol handle = JS::HandleSymbol::fromMarkedLocation(&sym);
     545                 :           1 :     JS::SymbolCode code = JS::GetSymbolCode(handle);
     546                 :           1 :     JSString *descr = JS::GetSymbolDescription(handle);
     547                 :             : 
     548         [ -  + ]:           1 :     if (size_t(code) < JS::WellKnownSymbolLimit)
     549                 :           0 :         return gjs_debug_string(descr);
     550                 :             : 
     551                 :           1 :     std::ostringstream out;
     552         [ -  + ]:           1 :     if (code == JS::SymbolCode::InSymbolRegistry) {
     553                 :           0 :         out << "Symbol.for(";
     554         [ #  # ]:           0 :         if (descr)
     555                 :           0 :             out << gjs_debug_string(descr);
     556                 :             :         else
     557                 :           0 :             out << "undefined";
     558                 :           0 :         out << ")";
     559                 :           0 :         return out.str();
     560                 :             :     }
     561         [ +  - ]:           1 :     if (code == JS::SymbolCode::UniqueSymbol) {
     562         [ +  - ]:           1 :         if (descr)
     563                 :           1 :             out << "Symbol(" << gjs_debug_string(descr) << ")";
     564                 :             :         else
     565                 :           0 :             out << "<Symbol at " << sym << ">";
     566                 :           1 :         return out.str();
     567                 :             :     }
     568                 :             : 
     569                 :           0 :     out << "<unexpected symbol code " << uint32_t(code) << ">";
     570                 :           0 :     return out.str();
     571                 :           1 : }
     572                 :             : 
     573                 :             : std::string
     574                 :       17672 : gjs_debug_object(JSObject * const obj)
     575                 :             : {
     576         [ +  + ]:       17672 :     if (!obj)
     577                 :        4748 :         return "<null object>";
     578                 :             : 
     579                 :       15298 :     std::ostringstream out;
     580                 :             : 
     581         [ +  + ]:       15298 :     if (js::IsFunctionObject(obj)) {
     582                 :        2850 :         JSFunction* fun = JS_GetObjectFunction(obj);
     583                 :        2850 :         JSString* display_name = JS_GetMaybePartialFunctionDisplayId(fun);
     584   [ +  -  +  +  :        2850 :         if (display_name && JS_GetStringLength(display_name))
                   +  + ]
     585                 :          51 :             out << "<function " << gjs_debug_string(display_name);
     586                 :             :         else
     587                 :        2799 :             out << "<anonymous function";
     588                 :        2850 :         out << " at " << fun << '>';
     589                 :        2850 :         return out.str();
     590                 :             :     }
     591                 :             : 
     592                 :             :     // This is OK because the promise methods can't cause a garbage collection
     593                 :       12448 :     JS::HandleObject handle = JS::HandleObject::fromMarkedLocation(&obj);
     594         [ +  + ]:       12448 :     if (JS::IsPromiseObject(handle)) {
     595                 :         215 :         out << '<';
     596                 :         215 :         JS::PromiseState state = JS::GetPromiseState(handle);
     597         [ +  - ]:         215 :         if (state == JS::PromiseState::Pending)
     598                 :         215 :             out << "pending ";
     599                 :         215 :         out << "promise " << JS::GetPromiseID(handle) << " at " << obj;
     600         [ -  + ]:         215 :         if (state != JS::PromiseState::Pending) {
     601                 :           0 :             out << ' ';
     602                 :             :             out << (state == JS::PromiseState::Rejected ? "rejected"
     603         [ #  # ]:           0 :                                                         : "resolved");
     604                 :           0 :             out << " with " << gjs_debug_value(JS::GetPromiseResult(handle));
     605                 :             :         }
     606                 :         215 :         out << '>';
     607                 :         215 :         return out.str();
     608                 :             :     }
     609                 :             : 
     610                 :       12233 :     const JSClass* clasp = JS::GetClass(obj);
     611                 :       12233 :     out << "<object " << clasp->name << " at " << obj <<  '>';
     612                 :       12233 :     return out.str();
     613                 :       15298 : }
     614                 :             : 
     615                 :         655 : std::string gjs_debug_callable(JSObject* callable) {
     616         [ +  - ]:         655 :     if (JSFunction* fn = JS_GetObjectFunction(callable)) {
     617         [ +  - ]:         655 :         if (JSString* display_id = JS_GetMaybePartialFunctionDisplayId(fn))
     618                 :         655 :             return {"function " + gjs_debug_string(display_id)};
     619                 :           0 :         return {"unnamed function"};
     620                 :             :     }
     621                 :           0 :     return {"callable object " + gjs_debug_object(callable)};
     622                 :             : }
     623                 :             : 
     624                 :             : std::string
     625                 :       12027 : gjs_debug_value(JS::Value v)
     626                 :             : {
     627         [ +  + ]:       12027 :     if (v.isNull())
     628                 :           2 :         return "null";
     629         [ +  + ]:       12026 :     if (v.isUndefined())
     630                 :           4 :         return "undefined";
     631         [ +  + ]:       12024 :     if (v.isInt32()) {
     632                 :           1 :         std::ostringstream out;
     633                 :           1 :         out << v.toInt32();
     634                 :           1 :         return out.str();
     635                 :           1 :     }
     636         [ -  + ]:       12023 :     if (v.isDouble()) {
     637                 :           0 :         std::ostringstream out;
     638                 :           0 :         out << v.toDouble();
     639                 :           0 :         return out.str();
     640                 :           0 :     }
     641         [ +  + ]:       12023 :     if (v.isBigInt())
     642                 :           1 :         return gjs_debug_bigint(v.toBigInt());
     643         [ +  + ]:       12022 :     if (v.isString())
     644                 :           2 :         return gjs_debug_string(v.toString());
     645         [ +  + ]:       12020 :     if (v.isSymbol())
     646                 :           1 :         return gjs_debug_symbol(v.toSymbol());
     647         [ +  + ]:       12019 :     if (v.isObject())
     648                 :       12018 :         return gjs_debug_object(&v.toObject());
     649         [ +  - ]:           1 :     if (v.isBoolean())
     650         [ +  - ]:           2 :         return (v.toBoolean() ? "true" : "false");
     651         [ #  # ]:           0 :     if (v.isMagic())
     652                 :           0 :         return "<magic>";
     653                 :           0 :     return "unexpected value";
     654                 :             : }
     655                 :             : 
     656                 :             : std::string
     657                 :         127 : gjs_debug_id(jsid id)
     658                 :             : {
     659         [ +  - ]:         127 :     if (id.isString())
     660                 :         127 :         return gjs_debug_linear_string(id.toLinearString(), NoQuotes);
     661                 :           0 :     return gjs_debug_value(js::IdToValue(id));
     662                 :             : }
        

Generated by: LCOV version 2.0-1