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