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

Generated by: LCOV version 2.0-1