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