LCOV - code coverage report
Current view: top level - gjs - jsapi-util-string.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 74.8 % 310 232
Test Date: 2024-04-16 04:37:39 Functions: 96.0 % 25 24
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 59.2 % 142 84

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

Generated by: LCOV version 2.0-1