LCOV - code coverage report
Current view: top level - gjs - text-encoding.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 84.0 % 225 189
Test Date: 2025-02-04 05:33:54 Functions: 100.0 % 14 14
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 66.4 % 128 85

             Branch data     Line data    Source code
       1                 :             : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
       2                 :             : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       3                 :             : // SPDX-FileCopyrightText: 2010 litl, LLC
       4                 :             : // SPDX-FileCopyrightText: 2021 Evan Welsh
       5                 :             : 
       6                 :             : #include <config.h>
       7                 :             : 
       8                 :             : #include <limits.h>  // for SSIZE_MAX
       9                 :             : #include <stddef.h>  // for size_t
      10                 :             : #include <stdint.h>
      11                 :             : #include <string.h>  // for strcmp, memchr, strlen
      12                 :             : 
      13                 :             : #include <algorithm>
      14                 :             : #include <iosfwd>    // for nullptr_t
      15                 :             : #include <iterator>  // for distance
      16                 :             : #include <memory>    // for unique_ptr
      17                 :             : #include <string>    // for u16string
      18                 :             : #include <tuple>     // for tuple
      19                 :             : #include <utility>   // for move
      20                 :             : 
      21                 :             : #include <gio/gio.h>
      22                 :             : #include <glib-object.h>
      23                 :             : #include <glib.h>
      24                 :             : 
      25                 :             : #include <js/ArrayBuffer.h>
      26                 :             : #include <js/CallArgs.h>
      27                 :             : #include <js/CharacterEncoding.h>
      28                 :             : #include <js/ErrorReport.h>  // for JS_ReportOutOfMemory, JSEXN_TYPEERR
      29                 :             : #include <js/Exception.h>    // for JS_ClearPendingException, JS_...
      30                 :             : #include <js/GCAPI.h>  // for AutoCheckCannotGC
      31                 :             : #include <js/PropertyAndElement.h>
      32                 :             : #include <js/PropertySpec.h>
      33                 :             : #include <js/RootingAPI.h>
      34                 :             : #include <js/String.h>
      35                 :             : #include <js/TypeDecls.h>
      36                 :             : #include <js/Utility.h>  // for UniqueChars
      37                 :             : #include <js/Value.h>
      38                 :             : #include <js/experimental/TypedData.h>
      39                 :             : #include <jsapi.h>        // for JS_NewPlainObject, JS_InstanceOf
      40                 :             : #include <jsfriendapi.h>  // for ProtoKeyToClass
      41                 :             : #include <jspubtd.h>      // for JSProto_InternalError
      42                 :             : #include <mozilla/Maybe.h>
      43                 :             : #include <mozilla/Span.h>
      44                 :             : #include <mozilla/UniquePtr.h>
      45                 :             : 
      46                 :             : #include "gjs/auto.h"
      47                 :             : #include "gjs/gerror-result.h"
      48                 :             : #include "gjs/jsapi-util-args.h"
      49                 :             : #include "gjs/jsapi-util.h"
      50                 :             : #include "gjs/macros.h"
      51                 :             : #include "gjs/text-encoding.h"
      52                 :             : 
      53                 :             : // Callback to use with JS::NewExternalArrayBuffer()
      54                 :             : 
      55                 :           1 : static void gfree_arraybuffer_contents(void* contents, void*) {
      56                 :           1 :     g_free(contents);
      57                 :           1 : }
      58                 :             : 
      59                 :           1 : static std::nullptr_t gjs_throw_type_error_from_gerror(
      60                 :             :     JSContext* cx, Gjs::AutoError const& error) {
      61                 :           1 :     g_return_val_if_fail(error, nullptr);
      62                 :           1 :     gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "%s", error->message);
      63                 :           1 :     return nullptr;
      64                 :             : }
      65                 :             : 
      66                 :             : // UTF16_CODESET is used to encode and decode UTF-16 buffers with
      67                 :             : // iconv. To ensure the output of iconv is laid out in memory correctly
      68                 :             : // we have to use UTF-16LE on little endian systems and UTF-16BE on big
      69                 :             : // endian systems.
      70                 :             : //
      71                 :             : // This ensures we can simply reinterpret_cast<char16_t> iconv's output.
      72                 :             : #if G_BYTE_ORDER == G_LITTLE_ENDIAN
      73                 :             : static const char* UTF16_CODESET = "UTF-16LE";
      74                 :             : #else
      75                 :             : static const char* UTF16_CODESET = "UTF-16BE";
      76                 :             : #endif
      77                 :             : 
      78                 :             : GJS_JSAPI_RETURN_CONVENTION
      79                 :         174 : static JSString* gjs_lossy_decode_from_uint8array_slow(
      80                 :             :     JSContext* cx, const uint8_t* bytes, size_t bytes_len,
      81                 :             :     const char* from_codeset) {
      82                 :         174 :     Gjs::AutoError error;
      83                 :             :     Gjs::AutoUnref<GCharsetConverter> converter{
      84                 :         174 :         g_charset_converter_new(UTF16_CODESET, from_codeset, &error)};
      85                 :             : 
      86                 :             :     // This should only throw if an encoding is not available.
      87         [ -  + ]:         174 :     if (error)
      88                 :           0 :         return gjs_throw_type_error_from_gerror(cx, error);
      89                 :             : 
      90                 :             :     // This function converts *to* UTF-16, using a std::u16string
      91                 :             :     // as its buffer.
      92                 :             :     //
      93                 :             :     // UTF-16 represents each character with 2 bytes or
      94                 :             :     // 4 bytes, the best case scenario when converting to
      95                 :             :     // UTF-16 is that every input byte encodes to two bytes,
      96                 :             :     // this is typical for ASCII and non-supplementary characters.
      97                 :             :     // Because we are converting from an unknown encoding
      98                 :             :     // technically a single byte could be supplementary in
      99                 :             :     // Unicode (4 bytes) or even represent multiple Unicode characters.
     100                 :             :     //
     101                 :             :     // std::u16string does not care about these implementation
     102                 :             :     // details, its only concern is that is consists of byte pairs.
     103                 :             :     // Given this, a single UTF-16 character could be represented
     104                 :             :     // by one or two std::u16string characters.
     105                 :             : 
     106                 :             :     // Allocate bytes_len * 2 + 12 as our initial buffer.
     107                 :             :     // bytes_len * 2 is the "best case" for LATIN1 strings
     108                 :             :     // and strings which are in the basic multilingual plane.
     109                 :             :     // Add 12 as a slight cushion and set the minimum allocation
     110                 :             :     // at 256 to prefer running a single iteration for
     111                 :             :     // small strings with supplemental plane characters.
     112                 :             :     //
     113                 :             :     // When converting Chinese characters, for example,
     114                 :             :     // some dialectal characters are in the supplemental plane
     115                 :             :     // Adding a padding of 12 prevents a few dialectal characters
     116                 :             :     // from requiring a reallocation.
     117                 :         174 :     size_t buffer_size = std::max(bytes_len * 2 + 12, static_cast<size_t>(256u));
     118                 :             : 
     119                 :             :     // Cast data to correct input types
     120                 :         174 :     const char* input = reinterpret_cast<const char*>(bytes);
     121                 :         174 :     size_t input_len = bytes_len;
     122                 :             : 
     123                 :             :     // The base string that we'll append to.
     124                 :         348 :     std::u16string output_str = u"";
     125                 :             : 
     126                 :             :     do {
     127                 :        2375 :         Gjs::AutoError local_error;
     128                 :             : 
     129                 :             :         // Create a buffer to convert into.
     130                 :        2375 :         std::unique_ptr<char[]> buffer = std::make_unique<char[]>(buffer_size);
     131                 :        2375 :         size_t bytes_written = 0, bytes_read = 0;
     132                 :             : 
     133                 :        4750 :         g_converter_convert(G_CONVERTER(converter.get()), input, input_len,
     134                 :        2375 :                             buffer.get(), buffer_size, G_CONVERTER_INPUT_AT_END,
     135                 :             :                             &bytes_read, &bytes_written, &local_error);
     136                 :             : 
     137                 :             :         // If bytes were read, adjust input.
     138         [ +  + ]:        2375 :         if (bytes_read > 0) {
     139                 :         635 :             input += bytes_read;
     140                 :         635 :             input_len -= bytes_read;
     141                 :             :         }
     142                 :             : 
     143                 :             :         // If bytes were written append the buffer contents to our string
     144                 :             :         // accumulator
     145         [ +  + ]:        2375 :         if (bytes_written > 0) {
     146                 :         635 :             char16_t* utf16_buffer = reinterpret_cast<char16_t*>(buffer.get());
     147                 :             :             // std::u16string uses exactly 2 bytes for every character.
     148                 :         635 :             output_str.append(utf16_buffer, bytes_written / 2);
     149         [ +  - ]:        1740 :         } else if (local_error) {
     150                 :             :             // A PARTIAL_INPUT error can only occur if the user does not provide
     151                 :             :             // the full sequence for a multi-byte character, we skip over the
     152                 :             :             // next character and insert a unicode fallback.
     153                 :             : 
     154                 :             :             // An INVALID_DATA error occurs when there is no way to decode a
     155                 :             :             // given byte into UTF-16 or the given byte does not exist in the
     156                 :             :             // source encoding.
     157                 :        1740 :             if (g_error_matches(local_error, G_IO_ERROR,
     158   [ +  +  +  -  :        1742 :                                 G_IO_ERROR_INVALID_DATA) ||
                   +  - ]
     159                 :           2 :                 g_error_matches(local_error, G_IO_ERROR,
     160                 :             :                                 G_IO_ERROR_PARTIAL_INPUT)) {
     161                 :             :                 // If we're already at the end of the string, don't insert a
     162                 :             :                 // fallback.
     163         [ +  - ]:        1740 :                 if (input_len > 0) {
     164                 :             :                     // Skip the next byte and reduce length by one.
     165                 :        1740 :                     input += 1;
     166                 :        1740 :                     input_len -= 1;
     167                 :             : 
     168                 :             :                     // Append the unicode fallback character to the output
     169                 :        1740 :                     output_str.append(u"\ufffd", 1);
     170                 :             :                 }
     171         [ #  # ]:           0 :             } else if (g_error_matches(local_error, G_IO_ERROR,
     172                 :             :                                        G_IO_ERROR_NO_SPACE)) {
     173                 :             :                 // If the buffer was full increase the buffer
     174                 :             :                 // size and re-try the conversion.
     175                 :             :                 //
     176                 :             :                 // This logic allocates bytes_len * 3 first,
     177                 :             :                 // then bytes_len * 4 (the worst case scenario
     178                 :             :                 // is nearly impossible) and then continues appending
     179                 :             :                 // arbitrary padding because we'll trust Gio and give
     180                 :             :                 // it additional space.
     181         [ #  # ]:           0 :                 if (buffer_size > bytes_len * 4) {
     182                 :           0 :                     buffer_size += 256;
     183                 :             :                 } else {
     184                 :           0 :                     buffer_size += bytes_len;
     185                 :             :                 }
     186                 :             :             } else {
     187                 :             :                 // Stop decoding if an unknown error occurs.
     188                 :           0 :                 return gjs_throw_type_error_from_gerror(cx, local_error);
     189                 :             :             }
     190                 :             :         }
     191   [ +  -  +  -  :        4750 :     } while (input_len > 0);
                   +  + ]
     192                 :             : 
     193                 :             :     // Copy the accumulator's data into a JSString of Unicode (UTF-16) chars.
     194                 :         174 :     return JS_NewUCStringCopyN(cx, output_str.c_str(), output_str.size());
     195                 :         174 : }
     196                 :             : 
     197                 :             : GJS_JSAPI_RETURN_CONVENTION
     198                 :         176 : static JSString* gjs_decode_from_uint8array_slow(JSContext* cx,
     199                 :             :                                                  const uint8_t* input,
     200                 :             :                                                  size_t input_len,
     201                 :             :                                                  const char* encoding,
     202                 :             :                                                  bool fatal) {
     203                 :             :     // If the decoding is not fatal we use the lossy decoder.
     204         [ +  + ]:         176 :     if (!fatal)
     205                 :         174 :         return gjs_lossy_decode_from_uint8array_slow(cx, input, input_len,
     206                 :         174 :                                                      encoding);
     207                 :             : 
     208                 :             :     // g_convert only handles up to SSIZE_MAX bytes, but we may have SIZE_MAX
     209         [ -  + ]:           2 :     if (G_UNLIKELY(input_len > SSIZE_MAX)) {
     210                 :           0 :         gjs_throw(cx, "Array too big to decode: %zu bytes", input_len);
     211                 :           0 :         return nullptr;
     212                 :             :     }
     213                 :             : 
     214                 :             :     size_t bytes_written, bytes_read;
     215                 :           2 :     Gjs::AutoError error;
     216                 :             : 
     217                 :             :     Gjs::AutoChar bytes{g_convert(reinterpret_cast<const char*>(input),
     218                 :             :                                   input_len, UTF16_CODESET, encoding,
     219                 :           2 :                                   &bytes_read, &bytes_written, &error)};
     220                 :             : 
     221         [ +  + ]:           2 :     if (error)
     222                 :           1 :         return gjs_throw_type_error_from_gerror(cx, error);
     223                 :             : 
     224                 :             :     // bytes_written should be bytes in a UTF-16 string so should be a
     225                 :             :     // multiple of 2
     226                 :           1 :     g_assert((bytes_written % 2) == 0);
     227                 :             : 
     228                 :             :     // Cast g_convert's output to char16_t and copy the data.
     229                 :           1 :     const char16_t* unicode_bytes = reinterpret_cast<char16_t*>(bytes.get());
     230                 :           1 :     return JS_NewUCStringCopyN(cx, unicode_bytes, bytes_written / 2);
     231                 :           2 : }
     232                 :             : 
     233                 :         376 : [[nodiscard]] static bool is_utf8_label(const char* encoding) {
     234                 :             :     // We could be smarter about utf8 synonyms here.
     235                 :             :     // For now, we handle any casing and trailing/leading
     236                 :             :     // whitespace.
     237                 :             :     //
     238                 :             :     // is_utf8_label is only an optimization, so if a label
     239                 :             :     // doesn't match we just use the slower path.
     240   [ +  +  -  +  :         554 :     if (g_ascii_strcasecmp(encoding, "utf-8") == 0 ||
                   +  + ]
     241                 :         178 :         g_ascii_strcasecmp(encoding, "utf8") == 0)
     242                 :         198 :         return true;
     243                 :             : 
     244                 :         178 :     Gjs::AutoChar stripped{g_strdup(encoding)};
     245                 :         178 :     g_strstrip(stripped);  // modifies in place
     246   [ +  -  -  + ]:         356 :     return g_ascii_strcasecmp(stripped, "utf-8") == 0 ||
     247                 :         178 :            g_ascii_strcasecmp(stripped, "utf8") == 0;
     248                 :         178 : }
     249                 :             : 
     250                 :             : // Finds the length of a given data array, stopping at the first 0 byte.
     251                 :             : template <class T>
     252                 :           9 : [[nodiscard]] static size_t zero_terminated_length(const T* data, size_t len) {
     253   [ +  -  +  + ]:           9 :     if (!data || len == 0)
     254                 :           1 :         return 0;
     255                 :             : 
     256                 :           8 :     const T* start = data;
     257                 :           8 :     auto* found = static_cast<const T*>(std::memchr(start, '\0', len));
     258                 :             : 
     259                 :             :     // If a null byte was not found, return the passed length.
     260         [ +  + ]:           8 :     if (!found)
     261                 :           4 :         return len;
     262                 :             : 
     263                 :           4 :     return std::distance(start, found);
     264                 :             : }
     265                 :             : 
     266                 :             : // decode() function implementation
     267                 :         349 : JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array,
     268                 :             :                                      const char* encoding,
     269                 :             :                                      GjsStringTermination string_termination,
     270                 :             :                                      bool fatal) {
     271                 :         349 :     g_assert(encoding && "encoding must be non-null");
     272                 :             : 
     273         [ +  + ]:         349 :     if (!JS_IsUint8Array(byte_array)) {
     274                 :           1 :         gjs_throw(cx, "Argument to decode() must be a Uint8Array");
     275                 :           1 :         return nullptr;
     276                 :             :     }
     277                 :             : 
     278                 :             :     uint8_t* data;
     279                 :             :     size_t len;
     280                 :             :     bool is_shared_memory;
     281                 :         348 :     js::GetUint8ArrayLengthAndData(byte_array, &len, &is_shared_memory, &data);
     282                 :             : 
     283                 :             :     // If the desired behavior is zero-terminated, calculate the
     284                 :             :     // zero-terminated length of the given data.
     285   [ +  +  +  + ]:         348 :     if (len && string_termination == GjsStringTermination::ZERO_TERMINATED)
     286                 :           7 :         len = zero_terminated_length(data, len);
     287                 :             : 
     288                 :             :     // If the calculated length is 0 we can just return an empty string.
     289         [ +  + ]:         348 :     if (len == 0)
     290                 :           3 :         return JS_GetEmptyString(cx);
     291                 :             : 
     292                 :             :     // Optimization, only use glib's iconv-based converters if we're dealing
     293                 :             :     // with a non-UTF8 encoding. SpiderMonkey has highly optimized UTF-8 decoder
     294                 :             :     // and encoders.
     295                 :         345 :     bool encoding_is_utf8 = is_utf8_label(encoding);
     296         [ +  + ]:         345 :     if (!encoding_is_utf8)
     297                 :         176 :         return gjs_decode_from_uint8array_slow(cx, data, len, encoding, fatal);
     298                 :             : 
     299                 :         169 :     JS::RootedString decoded(cx);
     300         [ +  + ]:         169 :     if (!fatal) {
     301                 :         163 :         decoded.set(gjs_lossy_string_from_utf8_n(
     302                 :             :             cx, reinterpret_cast<char*>(data), len));
     303                 :             :     } else {
     304                 :           6 :         JS::UTF8Chars chars(reinterpret_cast<char*>(data), len);
     305                 :           6 :         JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
     306                 :             : 
     307                 :             :         // If an exception occurred, we need to check if the
     308                 :             :         // exception was an InternalError. Unfortunately,
     309                 :             :         // SpiderMonkey's decoder can throw InternalError for some
     310                 :             :         // invalid UTF-8 sources, we have to convert this into a
     311                 :             :         // TypeError to match the Encoding specification.
     312         [ +  + ]:           6 :         if (str) {
     313                 :           5 :             decoded.set(str);
     314                 :             :         } else {
     315                 :           1 :             JS::RootedValue exc(cx);
     316   [ +  -  -  +  :           1 :             if (!JS_GetPendingException(cx, &exc) || !exc.isObject())
                   -  + ]
     317                 :           0 :                 return nullptr;
     318                 :             : 
     319                 :           1 :             JS::RootedObject exc_obj(cx, &exc.toObject());
     320                 :             :             const JSClass* internal_error =
     321                 :           1 :                 js::ProtoKeyToClass(JSProto_InternalError);
     322         [ -  + ]:           1 :             if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) {
     323                 :             :                 // Clear the existing exception.
     324                 :           0 :                 JS_ClearPendingException(cx);
     325                 :           0 :                 gjs_throw_custom(
     326                 :             :                     cx, JSEXN_TYPEERR, nullptr,
     327                 :             :                     "The provided encoded data was not valid UTF-8");
     328                 :             :             }
     329                 :             : 
     330                 :           1 :             return nullptr;
     331                 :           1 :         }
     332         [ +  + ]:           6 :     }
     333                 :             : 
     334                 :             :     uint8_t* current_data;
     335                 :             :     size_t current_len;
     336                 :             :     bool ignore_val;
     337                 :             : 
     338                 :             :     // If a garbage collection occurs between when we call
     339                 :             :     // js::GetUint8ArrayLengthAndData and return from
     340                 :             :     // gjs_decode_from_uint8array, a use-after-free corruption can occur if the
     341                 :             :     // garbage collector shifts the location of the Uint8Array's private data.
     342                 :             :     // To mitigate this we call js::GetUint8ArrayLengthAndData again and then
     343                 :             :     // compare if the length and pointer are still the same. If the pointers
     344                 :             :     // differ, we use the slow path to ensure no data corruption occurred. The
     345                 :             :     // shared-ness of an array cannot change between calls, so we ignore it.
     346                 :         168 :     js::GetUint8ArrayLengthAndData(byte_array, &current_len, &ignore_val,
     347                 :             :                                    &current_data);
     348                 :             : 
     349                 :             :     // Ensure the private data hasn't changed
     350         [ +  - ]:         168 :     if (current_data == data)
     351                 :         168 :         return decoded;
     352                 :             : 
     353                 :           0 :     g_assert(current_len == len &&
     354                 :             :              "Garbage collection should not affect data length.");
     355                 :             : 
     356                 :             :     // This was the UTF-8 optimized path, so we explicitly pass the encoding
     357                 :           0 :     return gjs_decode_from_uint8array_slow(cx, current_data, current_len,
     358                 :           0 :                                            "utf-8", fatal);
     359                 :         169 : }
     360                 :             : 
     361                 :             : GJS_JSAPI_RETURN_CONVENTION
     362                 :         340 : static bool gjs_decode(JSContext* cx, unsigned argc, JS::Value* vp) {
     363                 :         340 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     364                 :             : 
     365                 :         340 :     JS::RootedObject byte_array(cx);
     366                 :         340 :     JS::UniqueChars encoding;
     367                 :         340 :     bool fatal = false;
     368         [ -  + ]:         340 :     if (!gjs_parse_call_args(cx, "decode", args, "os|b", "byteArray",
     369                 :             :                              &byte_array, "encoding", &encoding, "fatal",
     370                 :             :                              &fatal))
     371                 :           0 :         return false;
     372                 :             : 
     373                 :             :     JS::RootedString decoded(
     374                 :         340 :         cx, gjs_decode_from_uint8array(cx, byte_array, encoding.get(),
     375                 :             :                                        GjsStringTermination::EXPLICIT_LENGTH,
     376                 :         340 :                                        fatal));
     377         [ +  + ]:         340 :     if (!decoded)
     378                 :           2 :         return false;
     379                 :             : 
     380                 :         338 :     args.rval().setString(decoded);
     381                 :         338 :     return true;
     382                 :         340 : }
     383                 :             : 
     384                 :             : // encode() function implementation
     385                 :          31 : JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
     386                 :             :                                    const char* encoding,
     387                 :             :                                    GjsStringTermination string_termination) {
     388                 :          31 :     JS::RootedObject array_buffer(cx);
     389                 :             : 
     390                 :          31 :     bool encoding_is_utf8 = is_utf8_label(encoding);
     391         [ +  + ]:          31 :     if (encoding_is_utf8) {
     392                 :          29 :         JS::UniqueChars utf8;
     393                 :             :         size_t utf8_len;
     394                 :             : 
     395         [ -  + ]:          29 :         if (!gjs_string_to_utf8_n(cx, str, &utf8, &utf8_len))
     396                 :           0 :             return nullptr;
     397                 :             : 
     398         [ +  + ]:          29 :         if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
     399                 :             :             // strlen is safe because gjs_string_to_utf8_n returns
     400                 :             :             // a null-terminated string.
     401                 :           6 :             utf8_len = strlen(utf8.get());
     402                 :             :         }
     403                 :             : 
     404                 :             :         array_buffer =
     405                 :          29 :             JS::NewArrayBufferWithContents(cx, utf8_len, std::move(utf8));
     406         [ +  - ]:          29 :     } else {
     407                 :           2 :         Gjs::AutoError error;
     408                 :           2 :         Gjs::AutoChar encoded;
     409                 :             :         size_t bytes_written;
     410                 :             : 
     411                 :             :         /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
     412                 :             :          * while we are using the string's chars */
     413                 :             :         {
     414                 :           2 :             JS::AutoCheckCannotGC nogc;
     415                 :             :             size_t len;
     416                 :             : 
     417         [ +  - ]:           2 :             if (JS::StringHasLatin1Chars(str)) {
     418                 :             :                 const JS::Latin1Char* chars =
     419                 :           2 :                     JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len);
     420         [ -  + ]:           2 :                 if (!chars)
     421                 :           0 :                     return nullptr;
     422                 :             : 
     423                 :             :                 encoded = g_convert(reinterpret_cast<const char*>(chars), len,
     424                 :             :                                     encoding,  // to_encoding
     425                 :             :                                     "LATIN1",  // from_encoding
     426                 :             :                                     nullptr,   // bytes_read
     427                 :           2 :                                     &bytes_written, &error);
     428                 :             :             } else {
     429                 :             :                 const char16_t* chars =
     430                 :           0 :                     JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
     431         [ #  # ]:           0 :                 if (!chars)
     432                 :           0 :                     return nullptr;
     433                 :             : 
     434                 :             :                 encoded =
     435                 :           0 :                     g_convert(reinterpret_cast<const char*>(chars), len * 2,
     436                 :             :                               encoding,  // to_encoding
     437                 :             :                               "UTF-16",  // from_encoding
     438                 :             :                               nullptr,   // bytes_read
     439                 :           0 :                               &bytes_written, &error);
     440                 :             :             }
     441         [ +  - ]:           2 :         }
     442                 :             : 
     443         [ -  + ]:           2 :         if (!encoded)
     444                 :           0 :             return gjs_throw_type_error_from_gerror(cx, error);  // frees GError
     445                 :             : 
     446         [ +  - ]:           2 :         if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
     447                 :           2 :             bytes_written =
     448                 :           2 :                 zero_terminated_length(encoded.get(), bytes_written);
     449                 :             :         }
     450                 :             : 
     451         [ +  + ]:           2 :         if (bytes_written == 0)
     452                 :           1 :             return JS_NewUint8Array(cx, 0);
     453                 :             : 
     454                 :             :         mozilla::UniquePtr<void, JS::BufferContentsDeleter> contents{
     455                 :           1 :             encoded.release(), gfree_arraybuffer_contents};
     456                 :             :         array_buffer =
     457                 :           1 :             JS::NewExternalArrayBuffer(cx, bytes_written, std::move(contents));
     458   [ +  +  +  + ]:           3 :     }
     459                 :             : 
     460         [ -  + ]:          30 :     if (!array_buffer)
     461                 :           0 :         return nullptr;
     462                 :             : 
     463                 :          30 :     return JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1);
     464                 :          31 : }
     465                 :             : 
     466                 :             : GJS_JSAPI_RETURN_CONVENTION
     467                 :           3 : static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str,
     468                 :             :                                        JS::HandleObject uint8array,
     469                 :             :                                        JS::MutableHandleValue rval) {
     470         [ -  + ]:           3 :     if (!JS_IsUint8Array(uint8array)) {
     471                 :           0 :         gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
     472                 :             :                          "Argument to encodeInto() must be a Uint8Array");
     473                 :           0 :         return false;
     474                 :             :     }
     475                 :             : 
     476                 :           3 :     uint32_t len = JS_GetTypedArrayByteLength(uint8array);
     477                 :           3 :     bool shared = JS_GetTypedArraySharedness(uint8array);
     478                 :             : 
     479         [ -  + ]:           3 :     if (shared) {
     480                 :           0 :         gjs_throw(cx, "Cannot encode data into shared memory.");
     481                 :           0 :         return false;
     482                 :             :     }
     483                 :             : 
     484                 :           3 :     mozilla::Maybe<std::tuple<size_t, size_t>> results;
     485                 :             : 
     486                 :             :     {
     487                 :           3 :         JS::AutoCheckCannotGC nogc(cx);
     488                 :           3 :         uint8_t* data = JS_GetUint8ArrayData(uint8array, &shared, nogc);
     489                 :             : 
     490                 :             :         // We already checked for sharedness with JS_GetTypedArraySharedness
     491                 :           3 :         g_assert(!shared);
     492                 :             : 
     493                 :           3 :         results = JS_EncodeStringToUTF8BufferPartial(
     494                 :           6 :             cx, str, mozilla::AsWritableChars(mozilla::Span(data, len)));
     495                 :           3 :     }
     496                 :             : 
     497         [ -  + ]:           3 :     if (!results) {
     498                 :           0 :         JS_ReportOutOfMemory(cx);
     499                 :           0 :         return false;
     500                 :             :     }
     501                 :             : 
     502                 :             :     size_t read, written;
     503                 :           3 :     std::tie(read, written) = *results;
     504                 :             : 
     505                 :           3 :     g_assert(written <= len);
     506                 :             : 
     507                 :           3 :     JS::RootedObject result(cx, JS_NewPlainObject(cx));
     508         [ -  + ]:           3 :     if (!result)
     509                 :           0 :         return false;
     510                 :             : 
     511                 :           3 :     JS::RootedValue v_read(cx, JS::NumberValue(read)),
     512                 :           3 :         v_written(cx, JS::NumberValue(written));
     513                 :             : 
     514         [ +  - ]:           6 :     if (!JS_SetProperty(cx, result, "read", v_read) ||
     515   [ -  +  -  + ]:           6 :         !JS_SetProperty(cx, result, "written", v_written))
     516                 :           0 :         return false;
     517                 :             : 
     518                 :           3 :     rval.setObject(*result);
     519                 :           3 :     return true;
     520                 :           3 : }
     521                 :             : 
     522                 :             : GJS_JSAPI_RETURN_CONVENTION
     523                 :          23 : static bool gjs_encode(JSContext* cx, unsigned argc, JS::Value* vp) {
     524                 :          23 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     525                 :          23 :     JS::RootedString str(cx);
     526                 :          23 :     JS::UniqueChars encoding;
     527         [ -  + ]:          23 :     if (!gjs_parse_call_args(cx, "encode", args, "Ss", "string", &str,
     528                 :             :                              "encoding", &encoding))
     529                 :           0 :         return false;
     530                 :             : 
     531                 :             :     JS::RootedObject uint8array(
     532                 :          23 :         cx, gjs_encode_to_uint8array(cx, str, encoding.get(),
     533                 :          23 :                                      GjsStringTermination::EXPLICIT_LENGTH));
     534         [ -  + ]:          23 :     if (!uint8array)
     535                 :           0 :         return false;
     536                 :             : 
     537                 :          23 :     args.rval().setObject(*uint8array);
     538                 :          23 :     return true;
     539                 :          23 : }
     540                 :             : 
     541                 :             : GJS_JSAPI_RETURN_CONVENTION
     542                 :           3 : static bool gjs_encode_into(JSContext* cx, unsigned argc, JS::Value* vp) {
     543                 :           3 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     544                 :           3 :     JS::RootedString str(cx);
     545                 :           3 :     JS::RootedObject uint8array(cx);
     546         [ -  + ]:           3 :     if (!gjs_parse_call_args(cx, "encodeInto", args, "So", "string", &str,
     547                 :             :                              "byteArray", &uint8array))
     548                 :           0 :         return false;
     549                 :             : 
     550                 :           3 :     return gjs_encode_into_uint8array(cx, str, uint8array, args.rval());
     551                 :           3 : }
     552                 :             : 
     553                 :             : static JSFunctionSpec gjs_text_encoding_module_funcs[] = {
     554                 :             :     JS_FN("decode", gjs_decode, 3, 0),
     555                 :             :     JS_FN("encodeInto", gjs_encode_into, 2, 0),
     556                 :             :     JS_FN("encode", gjs_encode, 2, 0), JS_FS_END};
     557                 :             : 
     558                 :           9 : bool gjs_define_text_encoding_stuff(JSContext* cx,
     559                 :             :                                     JS::MutableHandleObject module) {
     560                 :           9 :     JSObject* new_obj = JS_NewPlainObject(cx);
     561         [ -  + ]:           9 :     if (!new_obj)
     562                 :           0 :         return false;
     563                 :           9 :     module.set(new_obj);
     564                 :             : 
     565                 :           9 :     return JS_DefineFunctions(cx, module, gjs_text_encoding_module_funcs);
     566                 :             : }
        

Generated by: LCOV version 2.0-1