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