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