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

Generated by: LCOV version 2.0-1