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

           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                 :        379 : GjsAutoChar gjs_hyphen_to_underscore(const char* str) {
      44                 :        379 :     char *s = g_strdup(str);
      45                 :        379 :     char *retval = s;
      46         [ +  + ]:       4405 :     while (*(s++) != '\0') {
      47         [ +  + ]:       4026 :         if (*s == '-')
      48                 :        222 :             *s = '_';
      49                 :            :     }
      50                 :        379 :     return retval;
      51                 :            : }
      52                 :            : 
      53                 :        162 : GjsAutoChar gjs_hyphen_to_camel(const char* str) {
      54                 :        162 :     GjsAutoChar retval = static_cast<char*>(g_malloc(strlen(str) + 1));
      55                 :        162 :     const char* input_iter = str;
      56                 :        162 :     char* output_iter = retval.get();
      57                 :        162 :     bool uppercase_next = false;
      58         [ +  + ]:       2292 :     while (*input_iter != '\0') {
      59         [ +  + ]:       2130 :         if (*input_iter == '-') {
      60                 :        126 :             uppercase_next = true;
      61         [ +  + ]:       2004 :         } else if (uppercase_next) {
      62                 :        126 :             *output_iter++ = g_ascii_toupper(*input_iter);
      63                 :        126 :             uppercase_next = false;
      64                 :            :         } else {
      65                 :       1878 :             *output_iter++ = *input_iter;
      66                 :            :         }
      67                 :       2130 :         input_iter++;
      68                 :            :     }
      69                 :        162 :     *output_iter = '\0';
      70                 :        162 :     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                 :      22590 : JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) {
      88         [ +  + ]:      22590 :     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                 :      22589 :     JS::RootedString str(cx, value.toString());
      94                 :      22589 :     return JS_EncodeStringToUTF8(cx, str);
      95                 :      22589 : }
      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                 :         41 : bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output,
     113                 :            :                           size_t* output_len) {
     114                 :         41 :     JSLinearString* linear = JS_EnsureLinearString(cx, str);
     115         [ -  + ]:         41 :     if (!linear)
     116                 :          0 :         return false;
     117                 :            : 
     118                 :         41 :     size_t length = JS::GetDeflatedUTF8StringLength(linear);
     119                 :         41 :     char* bytes = js_pod_arena_malloc<char>(js::StringBufferArena, length + 1);
     120         [ -  + ]:         41 :     if (!bytes)
     121                 :          0 :         return false;
     122                 :            : 
     123                 :            :     // Append a zero-terminator to the string.
     124                 :         41 :     bytes[length] = '\0';
     125                 :            : 
     126                 :            :     size_t deflated_length [[maybe_unused]] =
     127                 :         41 :         JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length));
     128                 :         41 :     g_assert(deflated_length == length);
     129                 :            : 
     130                 :         41 :     *output_len = length;
     131                 :         41 :     *output = JS::UniqueChars(bytes);
     132                 :         41 :     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                 :        162 : JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string,
     166                 :            :                                        size_t len) {
     167                 :        162 :     JS::UTF8Chars chars(utf8_string, len);
     168                 :            :     size_t outlen;
     169                 :            :     JS::UniqueTwoByteChars twobyte_chars(
     170                 :        162 :         JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen,
     171                 :            :                                              js::MallocArena)
     172                 :        162 :             .get());
     173         [ -  + ]:        162 :     if (!twobyte_chars)
     174                 :          0 :         return nullptr;
     175                 :            : 
     176                 :        162 :     return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen);
     177                 :        162 : }
     178                 :            : 
     179                 :            : bool
     180                 :      10170 : gjs_string_from_utf8(JSContext             *context,
     181                 :            :                      const char            *utf8_string,
     182                 :            :                      JS::MutableHandleValue value_p)
     183                 :            : {
     184                 :      10170 :     JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string));
     185                 :      10170 :     JS::RootedString str(context, JS_NewStringCopyUTF8Z(context, chars));
     186         [ -  + ]:      10170 :     if (!str)
     187                 :          0 :         return false;
     188                 :            : 
     189                 :      10170 :     value_p.setString(str);
     190                 :      10170 :     return true;
     191                 :      10170 : }
     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                 :      24023 : bool gjs_get_string_id(JSContext* cx, jsid id, JS::UniqueChars* name_p) {
     432         [ +  + ]:      24023 :     if (!id.isString()) {
     433                 :       3781 :         name_p->reset();
     434                 :       3781 :         return true;
     435                 :            :     }
     436                 :            : 
     437                 :      20242 :     JSLinearString* lstr = id.toLinearString();
     438                 :      20242 :     JS::RootedString s(cx, JS_FORGET_STRING_LINEARNESS(lstr));
     439                 :      20242 :     *name_p = JS_EncodeStringToUTF8(cx, s);
     440                 :      20242 :     return !!*name_p;
     441                 :      20242 : }
     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                 :      36825 : gjs_intern_string_to_id(JSContext  *cx,
     470                 :            :                         const char *string)
     471                 :            : {
     472                 :      36825 :     JS::RootedString str(cx, JS_AtomizeAndPinString(cx, string));
     473         [ -  + ]:      36825 :     if (!str)
     474                 :          0 :         return JS::PropertyKey::Void();
     475                 :      36825 :     return JS::PropertyKey::fromPinnedString(str);
     476                 :      36825 : }
     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                 :       4497 : [[nodiscard]] static std::string gjs_debug_linear_string(JSLinearString* str,
     496                 :            :                                                          Quotes quotes) {
     497                 :       4497 :     size_t len = JS::GetLinearStringLength(str);
     498                 :            : 
     499                 :       4497 :     std::ostringstream out;
     500         [ +  + ]:       4497 :     if (quotes == DoubleQuotes)
     501                 :       4410 :         out << '"';
     502                 :            : 
     503                 :       4497 :     JS::AutoCheckCannotGC nogc;
     504         [ +  - ]:       4497 :     if (JS::LinearStringHasLatin1Chars(str)) {
     505                 :       4497 :         const JS::Latin1Char* chars = JS::GetLatin1LinearStringChars(nogc, str);
     506                 :       4497 :         out << std::string(reinterpret_cast<const char*>(chars), len);
     507         [ +  + ]:       4497 :         if (quotes == DoubleQuotes)
     508                 :       4410 :             out << '"';
     509                 :       4497 :         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                 :       4497 : }
     530                 :            : 
     531                 :            : std::string
     532                 :       4410 : gjs_debug_string(JSString *str)
     533                 :            : {
     534         [ -  + ]:       4410 :     if (!str)
     535                 :          0 :         return "<null string>";
     536         [ -  + ]:       4410 :     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                 :       4410 :                                    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                 :       7917 : gjs_debug_object(JSObject * const obj)
     585                 :            : {
     586         [ +  + ]:       7917 :     if (!obj)
     587                 :       3224 :         return "<null object>";
     588                 :            : 
     589                 :       6305 :     std::ostringstream out;
     590                 :            : 
     591         [ +  + ]:       6305 :     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                 :       4300 :     JS::HandleObject handle = JS::HandleObject::fromMarkedLocation(&obj);
     604         [ +  + ]:       4300 :     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                 :       4127 :     const JSClass* clasp = JS::GetClass(obj);
     621                 :       4127 :     out << "<object " << clasp->name << " at " << obj <<  '>';
     622                 :       4127 :     return out.str();
     623                 :       6305 : }
     624                 :            : 
     625                 :            : std::string
     626                 :       3963 : gjs_debug_value(JS::Value v)
     627                 :            : {
     628         [ +  + ]:       3963 :     if (v.isNull())
     629                 :          2 :         return "null";
     630         [ +  + ]:       3962 :     if (v.isUndefined())
     631                 :          4 :         return "undefined";
     632         [ +  + ]:       3960 :     if (v.isInt32()) {
     633                 :          1 :         std::ostringstream out;
     634                 :          1 :         out << v.toInt32();
     635                 :          1 :         return out.str();
     636                 :          1 :     }
     637         [ -  + ]:       3959 :     if (v.isDouble()) {
     638                 :          0 :         std::ostringstream out;
     639                 :          0 :         out << v.toDouble();
     640                 :          0 :         return out.str();
     641                 :          0 :     }
     642         [ +  + ]:       3959 :     if (v.isBigInt())
     643                 :          1 :         return gjs_debug_bigint(v.toBigInt());
     644         [ +  + ]:       3958 :     if (v.isString())
     645                 :          2 :         return gjs_debug_string(v.toString());
     646         [ +  + ]:       3956 :     if (v.isSymbol())
     647                 :          1 :         return gjs_debug_symbol(v.toSymbol());
     648         [ +  + ]:       3955 :     if (v.isObject())
     649                 :       3954 :         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 1.14