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

Generated by: LCOV version 1.14