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 : : // SPDX-FileCopyrightText: 2009 Red Hat, Inc.
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <stdio.h> // for sscanf
9 : : #include <string.h> // for strlen
10 : :
11 : : #ifdef _WIN32
12 : : # include <windows.h>
13 : : #endif
14 : :
15 : : #include <sstream>
16 : : #include <string>
17 : : #include <utility> // for move
18 : : #include <vector>
19 : :
20 : : #include <js/AllocPolicy.h>
21 : : #include <js/Array.h>
22 : : #include <js/CallAndConstruct.h> // for Call
23 : : #include <js/CallArgs.h>
24 : : #include <js/CharacterEncoding.h>
25 : : #include <js/Class.h>
26 : : #include <js/ColumnNumber.h> // for TaggedColumnNumberOneOrigin
27 : : #include <js/Conversions.h>
28 : : #include <js/ErrorReport.h>
29 : : #include <js/Exception.h>
30 : : #include <js/GCAPI.h> // for JS_MaybeGC, NonIncrementalGC, GCRe...
31 : : #include <js/GCHashTable.h> // for GCHashSet
32 : : #include <js/GCVector.h> // for RootedVector
33 : : #include <js/HashTable.h> // for DefaultHasher
34 : : #include <js/Object.h> // for GetClass
35 : : #include <js/PropertyAndElement.h>
36 : : #include <js/PropertyDescriptor.h> // for JSPROP_ENUMERATE
37 : : #include <js/RootingAPI.h>
38 : : #include <js/SavedFrameAPI.h>
39 : : #include <js/String.h>
40 : : #include <js/TypeDecls.h>
41 : : #include <js/Value.h>
42 : : #include <js/ValueArray.h>
43 : : #include <jsapi.h> // for JS_InstanceOf
44 : : #include <jsfriendapi.h> // for ProtoKeyToClass
45 : : #include <jspubtd.h> // for JSProto_InternalError, JSProto_SyntaxError
46 : : #include <mozilla/ScopeExit.h>
47 : :
48 : : #include "gjs/atoms.h"
49 : : #include "gjs/auto.h"
50 : : #include "gjs/context-private.h"
51 : : #include "gjs/global.h"
52 : : #include "gjs/jsapi-util.h"
53 : : #include "gjs/macros.h"
54 : : #include "gjs/module.h"
55 : :
56 : : static void
57 : 99 : throw_property_lookup_error(JSContext *cx,
58 : : JS::HandleObject obj,
59 : : const char *description,
60 : : JS::HandleId property_name,
61 : : const char *reason)
62 : : {
63 : : /* remember gjs_throw() is a no-op if JS_GetProperty()
64 : : * already set an exception
65 : : */
66 [ + - ]: 99 : if (description)
67 : 99 : gjs_throw(cx, "No property '%s' in %s (or %s)",
68 : 198 : gjs_debug_id(property_name).c_str(), description, reason);
69 : : else
70 : 0 : gjs_throw(cx, "No property '%s' in object %p (or %s)",
71 : 0 : gjs_debug_id(property_name).c_str(), obj.get(), reason);
72 : 99 : }
73 : :
74 : : /* Returns whether the object had the property; if the object did
75 : : * not have the property, always sets an exception. Treats
76 : : * "the property's value is undefined" the same as "no such property,".
77 : : * Guarantees that *value_p is set to something, if only JS::UndefinedValue(),
78 : : * even if an exception is set and false is returned.
79 : : *
80 : : * SpiderMonkey will emit a warning if the property is not present, so don't
81 : : * use this if you expect the property not to be present some of the time.
82 : : */
83 : : bool
84 : 2802 : gjs_object_require_property(JSContext *context,
85 : : JS::HandleObject obj,
86 : : const char *obj_description,
87 : : JS::HandleId property_name,
88 : : JS::MutableHandleValue value)
89 : : {
90 : 2802 : value.setUndefined();
91 : :
92 [ - + ]: 2802 : if (G_UNLIKELY(!JS_GetPropertyById(context, obj, property_name, value)))
93 : 0 : return false;
94 : :
95 [ + + ]: 2802 : if (G_LIKELY(!value.isUndefined()))
96 : 2800 : return true;
97 : :
98 : 2 : throw_property_lookup_error(context, obj, obj_description, property_name,
99 : : "its value was undefined");
100 : 2 : return false;
101 : : }
102 : :
103 : : bool
104 : 0 : gjs_object_require_property(JSContext *cx,
105 : : JS::HandleObject obj,
106 : : const char *description,
107 : : JS::HandleId property_name,
108 : : bool *value)
109 : : {
110 : 0 : JS::RootedValue prop_value(cx);
111 [ # # # # : 0 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
# # ]
112 : 0 : prop_value.isBoolean()) {
113 : 0 : *value = prop_value.toBoolean();
114 : 0 : return true;
115 : : }
116 : :
117 : 0 : throw_property_lookup_error(cx, obj, description, property_name,
118 : : "it was not a boolean");
119 : 0 : return false;
120 : 0 : }
121 : :
122 : : bool
123 : 14 : gjs_object_require_property(JSContext *cx,
124 : : JS::HandleObject obj,
125 : : const char *description,
126 : : JS::HandleId property_name,
127 : : int32_t *value)
128 : : {
129 : 14 : JS::RootedValue prop_value(cx);
130 [ + - + - : 28 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
+ - ]
131 : 14 : prop_value.isInt32()) {
132 : 14 : *value = prop_value.toInt32();
133 : 14 : return true;
134 : : }
135 : :
136 : 0 : throw_property_lookup_error(cx, obj, description, property_name,
137 : : "it was not a 32-bit integer");
138 : 0 : return false;
139 : 14 : }
140 : :
141 : : /* Converts JS string value to UTF-8 string. */
142 : 177 : bool gjs_object_require_property(JSContext* cx, JS::HandleObject obj,
143 : : const char* description,
144 : : JS::HandleId property_name,
145 : : JS::UniqueChars* value) {
146 : 177 : JS::RootedValue prop_value(cx);
147 [ + - ]: 177 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value)) {
148 : 177 : JS::UniqueChars tmp = gjs_string_to_utf8(cx, prop_value);
149 [ + - ]: 177 : if (tmp) {
150 : 177 : *value = std::move(tmp);
151 : 177 : return true;
152 : : }
153 [ - + ]: 177 : }
154 : :
155 : 0 : throw_property_lookup_error(cx, obj, description, property_name,
156 : : "it was not a valid string");
157 : 0 : return false;
158 : 177 : }
159 : :
160 : : bool
161 : 29690 : gjs_object_require_property(JSContext *cx,
162 : : JS::HandleObject obj,
163 : : const char *description,
164 : : JS::HandleId property_name,
165 : : JS::MutableHandleObject value)
166 : : {
167 : 29690 : JS::RootedValue prop_value(cx);
168 [ + + + - : 59283 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
+ + ]
169 : 29593 : prop_value.isObject()) {
170 : 29593 : value.set(&prop_value.toObject());
171 : 29593 : return true;
172 : : }
173 : :
174 : 97 : throw_property_lookup_error(cx, obj, description, property_name,
175 : : "it was not an object");
176 : 97 : return false;
177 : 29690 : }
178 : :
179 : : bool
180 : 435 : gjs_object_require_converted_property(JSContext *cx,
181 : : JS::HandleObject obj,
182 : : const char *description,
183 : : JS::HandleId property_name,
184 : : uint32_t *value)
185 : : {
186 : 435 : JS::RootedValue prop_value(cx);
187 [ + - + - ]: 870 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
188 [ + - ]: 870 : JS::ToUint32(cx, prop_value, value)) {
189 : 435 : return true;
190 : : }
191 : :
192 : 0 : throw_property_lookup_error(cx, obj, description, property_name,
193 : : "it couldn't be converted to uint32");
194 : 0 : return false;
195 : 435 : }
196 : :
197 : : void
198 : 1 : gjs_throw_constructor_error(JSContext *context)
199 : : {
200 : 1 : gjs_throw(context,
201 : : "Constructor called as normal method. Use 'new SomeObject()' not 'SomeObject()'");
202 : 1 : }
203 : :
204 : 2 : void gjs_throw_abstract_constructor_error(JSContext* context,
205 : : const JS::CallArgs& args) {
206 : : const JSClass *proto_class;
207 : 2 : const char *name = "anonymous";
208 : :
209 : 2 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
210 : 2 : JS::RootedObject callee(context, &args.callee());
211 : 2 : JS::RootedValue prototype(context);
212 [ + - ]: 2 : if (JS_GetPropertyById(context, callee, atoms.prototype(), &prototype)) {
213 : 2 : proto_class = JS::GetClass(&prototype.toObject());
214 : 2 : name = proto_class->name;
215 : : }
216 : :
217 : 2 : gjs_throw(context, "You cannot construct new instances of '%s'", name);
218 : 2 : }
219 : :
220 : 390 : JSObject* gjs_build_string_array(JSContext* context,
221 : : const std::vector<std::string>& strings) {
222 : 390 : JS::RootedValueVector elems(context);
223 [ - + ]: 390 : if (!elems.reserve(strings.size())) {
224 : 0 : JS_ReportOutOfMemory(context);
225 : 0 : return nullptr;
226 : : }
227 : :
228 [ + + ]: 1837 : for (const std::string& string : strings) {
229 : 1447 : JS::ConstUTF8CharsZ chars(string.c_str(), string.size());
230 : : JS::RootedValue element(context,
231 : 1447 : JS::StringValue(JS_NewStringCopyUTF8Z(context, chars)));
232 : 1447 : elems.infallibleAppend(element);
233 : 1447 : }
234 : :
235 : 390 : return JS::NewArrayObject(context, elems);
236 : 390 : }
237 : :
238 : 326 : JSObject* gjs_define_string_array(JSContext* context,
239 : : JS::HandleObject in_object,
240 : : const char* array_name,
241 : : const std::vector<std::string>& strings,
242 : : unsigned attrs) {
243 : 326 : JS::RootedObject array(context, gjs_build_string_array(context, strings));
244 [ - + ]: 326 : if (!array)
245 : 0 : return nullptr;
246 : :
247 [ - + ]: 326 : if (!JS_DefineProperty(context, in_object, array_name, array, attrs))
248 : 0 : return nullptr;
249 : :
250 : 326 : return array;
251 : 326 : }
252 : :
253 : : // Helper function: perform ToString on an exception (which may not even be an
254 : : // object), except if it is an InternalError, which would throw in ToString.
255 : : GJS_JSAPI_RETURN_CONVENTION
256 : 107 : static JSString* exception_to_string(JSContext* cx, JS::HandleValue exc) {
257 [ + + ]: 107 : if (exc.isObject()) {
258 : 105 : JS::RootedObject exc_obj(cx, &exc.toObject());
259 : : const JSClass* internal_error =
260 : 105 : js::ProtoKeyToClass(JSProto_InternalError);
261 [ - + ]: 105 : if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) {
262 : 0 : JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
263 [ # # ]: 0 : if (!report->message())
264 : 0 : return JS_NewStringCopyZ(cx, "(unknown internal error)");
265 : 0 : return JS_NewStringCopyUTF8Z(cx, report->message());
266 : : }
267 [ + - ]: 105 : }
268 : :
269 : 107 : return JS::ToString(cx, exc);
270 : : }
271 : :
272 : : // Helper function: log and clear the pending exception, without calling into
273 : : // any JS APIs that might cause more exceptions to be thrown.
274 : 1042 : static void log_exception_brief(JSContext* cx) {
275 : 1042 : JS::RootedValue exc{cx};
276 [ + - ]: 1042 : if (!JS_GetPendingException(cx, &exc))
277 : 1042 : return;
278 : :
279 : 0 : JS_ClearPendingException(cx);
280 : :
281 [ # # ]: 0 : if (!exc.isObject()) {
282 : 0 : g_warning("Value thrown while printing exception: %s",
283 : : gjs_debug_value(exc).c_str());
284 : 0 : return;
285 : : }
286 : :
287 : 0 : JS::RootedObject exc_obj{cx, &exc.toObject()};
288 : 0 : JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
289 [ # # ]: 0 : if (!report) {
290 : 0 : g_warning("Non-Error Object thrown while printing exception: %s",
291 : : gjs_debug_object(exc_obj).c_str());
292 : 0 : return;
293 : : }
294 : :
295 : 0 : g_warning("Exception thrown while printing exception: %s:%u:%u: %s",
296 : : report->filename.c_str(), report->lineno,
297 : : report->column.oneOriginValue(), report->message().c_str());
298 [ - - - + ]: 1042 : }
299 : :
300 : : // Helper function: format the error's stack property.
301 : 105 : static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) {
302 : 105 : auto Ok = JS::SavedFrameResult::Ok;
303 : 105 : JS::AutoSaveExceptionState saved_exc(cx);
304 : : auto restore =
305 : 210 : mozilla::MakeScopeExit([&saved_exc]() { saved_exc.restore(); });
306 : :
307 : 105 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
308 : 105 : std::ostringstream out;
309 : :
310 : : // Check both the internal SavedFrame object and the stack property.
311 : : // GErrors will not have the former, and internal errors will not
312 : : // have the latter.
313 : 105 : JS::RootedObject saved_frame{cx, JS::ExceptionStackOrNull(exc)};
314 [ + + ]: 105 : if (saved_frame) {
315 : 90 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
316 : 90 : Gjs::AutoMainRealm ar{gjs};
317 : 90 : JS::RootedObject global{cx, gjs->global()};
318 : 90 : JS::RootedObject registry{cx, gjs_get_source_map_registry(global)};
319 : :
320 : 90 : JS::UniqueChars utf8_stack{format_saved_frame(cx, saved_frame)};
321 [ - + ]: 90 : if (!utf8_stack)
322 : 0 : return {};
323 : :
324 : 90 : char* utf8_stack_str = utf8_stack.get();
325 : 90 : std::stringstream ss{utf8_stack_str};
326 : : // append source map info when available to each line
327 [ + + ]: 997 : while (saved_frame) {
328 : 909 : JS::RootedObject consumer{cx};
329 : 909 : JS::RootedString source_string{cx};
330 : : uint32_t line;
331 : 909 : JS::TaggedColumnNumberOneOrigin column;
332 : 909 : std::string stack_line;
333 : :
334 : : // print the original stack trace
335 : 909 : std::getline(ss, stack_line, '\n');
336 : 909 : out << '\n' << stack_line;
337 : :
338 : : bool success =
339 : 909 : JS::GetSavedFrameSource(cx, nullptr, saved_frame,
340 [ + - ]: 907 : &source_string) == Ok &&
341 [ + + + - ]: 1816 : JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line) == Ok &&
342 : 907 : JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &column) ==
343 : 909 : Ok;
344 : 909 : if (JS::GetSavedFrameParent(cx, nullptr, saved_frame,
345 [ + + ]: 909 : &saved_frame) != Ok) {
346 : : // If we can't iterate, bail out and don't print source map info
347 : 2 : break;
348 : : }
349 : :
350 [ - + ]: 907 : if (!success) {
351 : 0 : continue;
352 : : }
353 : :
354 : 907 : if (!gjs_global_source_map_get(cx, registry, source_string,
355 [ + - + + ]: 1814 : &consumer) ||
356 [ + + ]: 907 : !consumer) {
357 : 901 : log_exception_brief(cx);
358 : 901 : continue; // no source map for this file
359 : : }
360 : :
361 : : // build query obj for consumer
362 : 6 : JS::RootedObject input_obj{cx, JS_NewPlainObject(cx)};
363 : 6 : if (!input_obj ||
364 [ + - ]: 6 : !JS_DefineProperty(cx, input_obj, "line", line,
365 [ + - ]: 12 : JSPROP_ENUMERATE) ||
366 [ - + - + ]: 12 : !JS_DefineProperty(cx, input_obj, "column",
367 : 6 : column.oneOriginValue() - 1,
368 : : JSPROP_ENUMERATE)) {
369 : 0 : log_exception_brief(cx);
370 : 0 : continue;
371 : : }
372 : :
373 : 6 : JS::RootedValue val{cx, JS::ObjectValue(*input_obj)};
374 : 12 : if (!JS::Call(cx, consumer, "originalPositionFor",
375 [ - + ]: 12 : JS::HandleValueArray(val), &val)) {
376 : 0 : log_exception_brief(cx);
377 : 0 : continue;
378 : : }
379 : 6 : JS::RootedObject rvalObj{cx, &val.toObject()};
380 : :
381 : 6 : out << " -> ";
382 : :
383 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "name", &val)) {
384 : 0 : log_exception_brief(cx);
385 : 0 : continue;
386 : : }
387 [ - + ]: 6 : if (val.isString()) {
388 : 0 : JS::UniqueChars name{gjs_string_to_utf8(cx, val)};
389 [ # # ]: 0 : if (name)
390 : 0 : out << name.get() << "@";
391 : 0 : log_exception_brief(cx);
392 : 0 : }
393 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "source", &val)) {
394 : 0 : log_exception_brief(cx);
395 : 0 : continue;
396 : : }
397 [ + - ]: 6 : if (val.isString()) {
398 : 6 : JS::UniqueChars source{gjs_string_to_utf8(cx, val)};
399 [ + - ]: 6 : if (source)
400 : 6 : out << source.get();
401 : 6 : log_exception_brief(cx);
402 : 6 : }
403 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "line", &val)) {
404 : 0 : log_exception_brief(cx);
405 : 0 : continue;
406 : : }
407 [ + - ]: 6 : if (val.isInt32()) {
408 : 6 : out << ":" << val.toInt32();
409 : : }
410 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "column", &val)) {
411 : 0 : log_exception_brief(cx);
412 : 0 : continue;
413 : : }
414 [ + - ]: 6 : if (val.isInt32()) {
415 : 6 : out << ":" << val.toInt32() + 1;
416 : : }
417 [ + - + - : 2715 : }
+ - + + +
+ + + + +
+ ]
418 : 90 : return out.str();
419 : 90 : }
420 : :
421 : 15 : JS::RootedValue stack{cx};
422 [ + - - + ]: 30 : if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) ||
423 [ - + ]: 15 : !stack.isString()) {
424 : 0 : log_exception_brief(cx);
425 : 0 : return {};
426 : : }
427 : :
428 : 15 : JS::RootedString str{cx, stack.toString()};
429 : : bool is_empty;
430 [ + - + + : 15 : if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty) {
+ + ]
431 : 5 : log_exception_brief(cx);
432 : 5 : return {};
433 : : }
434 : :
435 : 10 : JS::UniqueChars utf8_stack{JS_EncodeStringToUTF8(cx, str)};
436 [ - + ]: 10 : if (!utf8_stack) {
437 : 0 : log_exception_brief(cx);
438 : 0 : return {};
439 : : }
440 : :
441 : 10 : out << '\n' << utf8_stack.get();
442 : 10 : return out.str();
443 : 105 : }
444 : :
445 : : // Helper function: format the file name, line number, and column number where a
446 : : // SyntaxError occurred.
447 : 2 : static std::string format_syntax_error_location(JSContext* cx,
448 : : JS::HandleObject exc) {
449 : 2 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
450 : :
451 : 2 : JS::RootedValue property(cx);
452 : 2 : int32_t line = 0;
453 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.line_number(), &property)) {
454 [ + - ]: 2 : if (property.isInt32())
455 : 2 : line = property.toInt32();
456 : : }
457 : 2 : log_exception_brief(cx);
458 : :
459 : 2 : int32_t column = 0;
460 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.column_number(), &property)) {
461 [ + - ]: 2 : if (property.isInt32())
462 : 2 : column = property.toInt32();
463 : : }
464 : 2 : log_exception_brief(cx);
465 : :
466 : 2 : JS::UniqueChars utf8_filename;
467 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.file_name(), &property)) {
468 [ + - ]: 2 : if (property.isString()) {
469 : 2 : JS::RootedString str(cx, property.toString());
470 : 2 : utf8_filename = JS_EncodeStringToUTF8(cx, str);
471 : 2 : }
472 : : }
473 : 2 : log_exception_brief(cx);
474 : :
475 : 2 : std::ostringstream out;
476 : 2 : out << " @ ";
477 [ + - ]: 2 : if (utf8_filename)
478 : 2 : out << utf8_filename.get();
479 : : else
480 : 0 : out << "<unknown>";
481 : 2 : out << ":" << line << ":" << column;
482 : 2 : return out.str();
483 : 2 : }
484 : :
485 : : using CauseSet = JS::GCHashSet<JSObject*, js::DefaultHasher<JSObject*>,
486 : : js::SystemAllocPolicy>;
487 : :
488 : 103 : static std::string format_exception_with_cause(
489 : : JSContext* cx, JS::HandleObject exc_obj,
490 : : JS::MutableHandle<CauseSet> seen_causes) {
491 : 103 : std::ostringstream out;
492 : 103 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
493 : :
494 : 103 : out << format_exception_stack(cx, exc_obj);
495 : :
496 : 103 : JS::RootedValue v_cause(cx);
497 [ - + ]: 103 : if (!JS_GetPropertyById(cx, exc_obj, atoms.cause(), &v_cause))
498 : 0 : log_exception_brief(cx);
499 [ + + ]: 103 : if (v_cause.isUndefined())
500 : 93 : return out.str();
501 : :
502 : 10 : JS::RootedObject cause(cx);
503 [ + + ]: 10 : if (v_cause.isObject()) {
504 : 9 : cause = &v_cause.toObject();
505 : 9 : CauseSet::AddPtr entry = seen_causes.lookupForAdd(cause);
506 [ + + ]: 9 : if (entry)
507 : 1 : return out.str(); // cause has been printed already, ref cycle
508 [ - + ]: 8 : if (!seen_causes.add(entry, cause))
509 : 0 : return out.str(); // out of memory, just stop here
510 : : }
511 : :
512 : 9 : out << "\nCaused by: ";
513 : 9 : JS::RootedString exc_str(cx, exception_to_string(cx, v_cause));
514 [ + - ]: 9 : if (exc_str) {
515 : 9 : JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str);
516 [ + - ]: 9 : if (utf8_exception)
517 : 9 : out << utf8_exception.get();
518 : 9 : }
519 : 9 : log_exception_brief(cx);
520 : :
521 [ + + ]: 9 : if (v_cause.isObject())
522 : 8 : out << format_exception_with_cause(cx, cause, seen_causes);
523 : :
524 : 9 : return out.str();
525 : 103 : }
526 : :
527 : 98 : static std::string format_exception_log_message(JSContext* cx,
528 : : JS::HandleValue exc,
529 : : JS::HandleString message) {
530 : 98 : std::ostringstream out;
531 : :
532 [ + + ]: 98 : if (message) {
533 : 17 : JS::UniqueChars utf8_message = JS_EncodeStringToUTF8(cx, message);
534 : 17 : log_exception_brief(cx);
535 [ + - ]: 17 : if (utf8_message)
536 : 17 : out << utf8_message.get() << ": ";
537 : 17 : }
538 : :
539 : 98 : JS::RootedString exc_str(cx, exception_to_string(cx, exc));
540 [ + - ]: 98 : if (exc_str) {
541 : 98 : JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str);
542 [ + - ]: 98 : if (utf8_exception)
543 : 98 : out << utf8_exception.get();
544 : 98 : }
545 : 98 : log_exception_brief(cx);
546 : :
547 [ + + ]: 98 : if (!exc.isObject())
548 : 1 : return out.str();
549 : :
550 : 97 : JS::RootedObject exc_obj(cx, &exc.toObject());
551 : 97 : const JSClass* syntax_error = js::ProtoKeyToClass(JSProto_SyntaxError);
552 [ + + ]: 97 : if (JS_InstanceOf(cx, exc_obj, syntax_error, nullptr)) {
553 : : // We log syntax errors differently, because the stack for those
554 : : // includes only the referencing module, but we want to print out the
555 : : // file name, line number, and column number from the exception.
556 : : // We assume that syntax errors have no cause property, and are not the
557 : : // cause of other exceptions, so no recursion.
558 : 2 : out << format_syntax_error_location(cx, exc_obj)
559 : 2 : << format_exception_stack(cx, exc_obj);
560 : 2 : return out.str();
561 : : }
562 : :
563 : 95 : JS::Rooted<CauseSet> seen_causes(cx);
564 : 95 : seen_causes.putNew(exc_obj);
565 : 95 : out << format_exception_with_cause(cx, exc_obj, &seen_causes);
566 : 95 : return out.str();
567 : 98 : }
568 : :
569 : : /**
570 : : * gjs_log_exception_full:
571 : : * @cx: the #JSContext
572 : : * @exc: the exception value to be logged
573 : : * @message: a string to prepend to the log message
574 : : * @level: the severity level at which to log the exception
575 : : *
576 : : * Currently, uses %G_LOG_LEVEL_WARNING if the exception is being printed after
577 : : * being caught, and %G_LOG_LEVEL_CRITICAL if it was not caught by user code.
578 : : */
579 : 98 : void gjs_log_exception_full(JSContext* cx, JS::HandleValue exc,
580 : : JS::HandleString message, GLogLevelFlags level) {
581 : 98 : JS::AutoSaveExceptionState saved_exc(cx);
582 : 98 : std::string log_msg = format_exception_log_message(cx, exc, message);
583 : 98 : g_log(G_LOG_DOMAIN, level, "JS ERROR: %s", log_msg.c_str());
584 : 98 : saved_exc.restore();
585 : 98 : }
586 : :
587 : : /**
588 : : * gjs_log_exception:
589 : : * @cx: the #JSContext
590 : : *
591 : : * Logs the exception pending on @cx, if any, in response to an exception being
592 : : * thrown that user code cannot catch or has already caught.
593 : : *
594 : : * Returns: %true if an exception was logged, %false if there was none pending.
595 : : */
596 : : bool
597 : 10228 : gjs_log_exception(JSContext *context)
598 : : {
599 : 10228 : JS::RootedValue exc(context);
600 [ + + ]: 10228 : if (!JS_GetPendingException(context, &exc))
601 : 10218 : return false;
602 : :
603 : 10 : JS_ClearPendingException(context);
604 : :
605 : 10 : gjs_log_exception_full(context, exc, nullptr, G_LOG_LEVEL_WARNING);
606 : 10 : return true;
607 : 10228 : }
608 : :
609 : : /**
610 : : * gjs_log_exception_uncaught:
611 : : * @cx: the #JSContext
612 : : *
613 : : * Logs the exception pending on @cx, if any, indicating an uncaught exception
614 : : * in the running JS program.
615 : : * (Currently, due to main loop boundaries, uncaught exceptions may not bubble
616 : : * all the way back up to the top level, so this doesn't necessarily mean the
617 : : * program exits with an error.)
618 : : *
619 : : * Returns: %true if an exception was logged, %false if there was none pending.
620 : : */
621 : 10208 : bool gjs_log_exception_uncaught(JSContext* cx) {
622 : 10208 : JS::RootedValue exc(cx);
623 [ + + ]: 10208 : if (!JS_GetPendingException(cx, &exc))
624 : 10193 : return false;
625 : :
626 : 15 : JS_ClearPendingException(cx);
627 : :
628 : 15 : gjs_log_exception_full(cx, exc, nullptr, G_LOG_LEVEL_CRITICAL);
629 : 15 : return true;
630 : 10208 : }
631 : :
632 : : #ifdef __linux__
633 : : // This type has to be long and not int32_t or int64_t, because of the %ld
634 : : // sscanf specifier mandated in "man proc". The NOLINT comment is because
635 : : // cpplint will ask you to avoid long in favour of defined bit width types.
636 : 0 : static void _linux_get_self_process_size(long* rss_size) // NOLINT(runtime/int)
637 : : {
638 : : char *iter;
639 : : gsize len;
640 : : int i;
641 : :
642 : 0 : *rss_size = 0;
643 : :
644 : 0 : Gjs::AutoChar contents;
645 [ # # ]: 0 : if (!g_file_get_contents("/proc/self/stat", contents.out(), &len, nullptr))
646 : 0 : return;
647 : :
648 : 0 : iter = contents;
649 : : // See "man proc" for where this 23 comes from
650 [ # # ]: 0 : for (i = 0; i < 23; i++) {
651 : 0 : iter = strchr (iter, ' ');
652 [ # # ]: 0 : if (!iter)
653 : 0 : return;
654 : 0 : iter++;
655 : : }
656 : 0 : sscanf(iter, " %ld", rss_size);
657 [ # # ]: 0 : }
658 : :
659 : : // We initiate a GC if RSS has grown by this much
660 : : static uint64_t linux_rss_trigger;
661 : : static int64_t last_gc_check_time;
662 : : #endif
663 : :
664 : : void
665 : 0 : gjs_gc_if_needed (JSContext *context)
666 : : {
667 : : #ifdef __linux__
668 : : {
669 : : long rss_size; // NOLINT(runtime/int)
670 : : gint64 now;
671 : :
672 : : /* We rate limit GCs to at most one per 5 frames.
673 : : One frame is 16666 microseconds (1000000/60)*/
674 : 0 : now = g_get_monotonic_time();
675 [ # # ]: 0 : if (now - last_gc_check_time < 5 * 16666)
676 : 0 : return;
677 : :
678 : 0 : last_gc_check_time = now;
679 : :
680 : 0 : _linux_get_self_process_size(&rss_size);
681 : :
682 : : /* linux_rss_trigger is initialized to 0, so currently
683 : : * we always do a full GC early.
684 : : *
685 : : * Here we see if the RSS has grown by 25% since
686 : : * our last look; if so, initiate a full GC. In
687 : : * theory using RSS is bad if we get swapped out,
688 : : * since we may be overzealous in GC, but on the
689 : : * other hand, if swapping is going on, better
690 : : * to GC.
691 : : */
692 [ # # ]: 0 : if (rss_size < 0)
693 : 0 : return; // doesn't make sense
694 : 0 : uint64_t rss_usize = rss_size;
695 [ # # ]: 0 : if (rss_usize > linux_rss_trigger) {
696 [ # # ]: 0 : linux_rss_trigger = MIN(G_MAXUINT32, rss_usize * 1.25);
697 : 0 : JS::NonIncrementalGC(context, JS::GCOptions::Shrink,
698 : : Gjs::GCReason::LINUX_RSS_TRIGGER);
699 [ # # ]: 0 : } else if (rss_size < (0.75 * linux_rss_trigger)) {
700 : : /* If we've shrunk by 75%, lower the trigger */
701 : 0 : linux_rss_trigger = rss_usize * 1.25;
702 : : }
703 : : }
704 : : #else // !__linux__
705 : : (void)context;
706 : : #endif
707 : : }
708 : :
709 : : /**
710 : : * gjs_maybe_gc:
711 : : *
712 : : * Low level version of gjs_context_maybe_gc().
713 : : */
714 : : void
715 : 0 : gjs_maybe_gc (JSContext *context)
716 : : {
717 : 0 : JS_MaybeGC(context);
718 : 0 : gjs_gc_if_needed(context);
719 : 0 : }
720 : :
721 : 0 : const char* gjs_explain_gc_reason(JS::GCReason reason) {
722 [ # # ]: 0 : if (JS::InternalGCReason(reason))
723 : 0 : return JS::ExplainGCReason(reason);
724 : :
725 : : static const char* reason_strings[] = {
726 : : // clang-format off
727 : : "RSS above threshold",
728 : : "GjsContext disposed",
729 : : "Big Hammer hit",
730 : : "gjs_context_gc() called",
731 : : "Memory usage is low",
732 : : // clang-format on
733 : : };
734 : : static_assert(G_N_ELEMENTS(reason_strings) == Gjs::GCReason::N_REASONS,
735 : : "Explanations must match the values in Gjs::GCReason");
736 : :
737 : 0 : g_assert(size_t(reason) < size_t(JS::GCReason::FIRST_FIREFOX_REASON) +
738 : : Gjs::GCReason::N_REASONS &&
739 : : "Bad Gjs::GCReason");
740 : 0 : return reason_strings[size_t(reason) -
741 : 0 : size_t(JS::GCReason::FIRST_FIREFOX_REASON)];
742 : : }
|