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 : 90 : 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 [ + - ]: 90 : if (description)
67 : 90 : gjs_throw(cx, "No property '%s' in %s (or %s)",
68 : 180 : 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 : 90 : }
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 : 2810 : 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 : 2810 : value.setUndefined();
91 : :
92 [ - + ]: 2810 : if (G_UNLIKELY(!JS_GetPropertyById(context, obj, property_name, value)))
93 : 0 : return false;
94 : :
95 [ + + ]: 2810 : if (G_LIKELY(!value.isUndefined()))
96 : 2808 : 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 : 27922 : 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 : 27922 : JS::RootedValue prop_value(cx);
168 [ + + + - : 55756 : if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
+ + ]
169 : 27834 : prop_value.isObject()) {
170 : 27834 : value.set(&prop_value.toObject());
171 : 27834 : return true;
172 : : }
173 : :
174 : 88 : throw_property_lookup_error(cx, obj, description, property_name,
175 : : "it was not an object");
176 : 88 : return false;
177 : 27922 : }
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: format the error's stack property.
273 : 105 : static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) {
274 : 105 : auto Ok = JS::SavedFrameResult::Ok;
275 : 105 : JS::AutoSaveExceptionState saved_exc(cx);
276 : : auto restore =
277 : 210 : mozilla::MakeScopeExit([&saved_exc]() { saved_exc.restore(); });
278 : :
279 : 105 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
280 : 105 : std::ostringstream out;
281 : :
282 : : // Check both the internal SavedFrame object and the stack property.
283 : : // GErrors will not have the former, and internal errors will not
284 : : // have the latter.
285 : 105 : JS::RootedObject saved_frame{cx, JS::ExceptionStackOrNull(exc)};
286 [ + + ]: 105 : if (saved_frame) {
287 : 90 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
288 : 90 : Gjs::AutoMainRealm ar{gjs};
289 : 90 : JS::RootedObject global{cx, gjs->global()};
290 : 90 : JS::RootedObject registry{cx, gjs_get_source_map_registry(global)};
291 : :
292 : 90 : JS::UniqueChars utf8_stack{format_saved_frame(cx, saved_frame)};
293 [ - + ]: 90 : if (!utf8_stack)
294 : 0 : return {};
295 : :
296 : 90 : char* utf8_stack_str = utf8_stack.get();
297 : 90 : std::stringstream ss{utf8_stack_str};
298 : : // append source map info when available to each line
299 [ + + ]: 999 : while (saved_frame) {
300 : 909 : JS::RootedObject consumer{cx};
301 : 909 : JS::RootedString source_string{cx};
302 : : bool success;
303 : : uint32_t line;
304 : 909 : JS::TaggedColumnNumberOneOrigin column;
305 : 909 : std::string stack_line;
306 : :
307 : : // print the original stack trace
308 : 909 : std::getline(ss, stack_line, '\n');
309 : 909 : out << '\n' << stack_line;
310 : :
311 : 909 : success =
312 : 909 : JS::GetSavedFrameSource(cx, nullptr, saved_frame,
313 [ + - ]: 907 : &source_string) == Ok &&
314 [ + + + - ]: 1816 : JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line) == Ok &&
315 : 907 : JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &column) ==
316 : : Ok;
317 : 909 : JS::GetSavedFrameParent(cx, nullptr, saved_frame, &saved_frame);
318 [ + + ]: 909 : if (!success) {
319 : 2 : continue;
320 : : }
321 : :
322 : 907 : JS::RootedValue source_key{cx, JS::StringValue(source_string)};
323 : : success =
324 : 907 : gjs_global_source_map_get(cx, registry, source_key, &consumer);
325 [ + - + + : 907 : if (!success || !consumer) {
+ + ]
326 : 901 : continue;
327 : : }
328 : :
329 : : // build query obj for consumer
330 : 6 : JS::RootedObject input_obj{cx, JS_NewPlainObject(cx)};
331 [ - + ]: 6 : if (!input_obj) {
332 : 0 : continue;
333 : : }
334 : 6 : if (!JS_DefineProperty(cx, input_obj, "line", line,
335 [ + - ]: 12 : JSPROP_ENUMERATE) ||
336 [ - + - + ]: 12 : !JS_DefineProperty(cx, input_obj, "column",
337 : 6 : column.oneOriginValue() - 1,
338 : : JSPROP_ENUMERATE)) {
339 : 0 : continue;
340 : : }
341 : :
342 : 6 : JS::RootedValue val{cx, JS::ObjectValue(*input_obj)};
343 : 12 : if (!JS::Call(cx, consumer, "originalPositionFor",
344 [ - + ]: 12 : JS::HandleValueArray(val), &val)) {
345 : 0 : continue;
346 : : }
347 : 6 : JS::RootedObject rvalObj{cx, &val.toObject()};
348 : :
349 : 6 : out << " -> ";
350 : :
351 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "name", &val)) {
352 : 0 : continue;
353 : : }
354 [ - + ]: 6 : if (val.isString()) {
355 : 0 : JS::RootedString string{cx, val.toString()};
356 : 0 : out << JS_EncodeStringToUTF8(cx, string).get() << "@";
357 : 0 : }
358 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "source", &val)) {
359 : 0 : continue;
360 : : }
361 [ + - ]: 6 : if (val.isString()) {
362 : 6 : JS::RootedString string{cx, val.toString()};
363 : 6 : out << JS_EncodeStringToUTF8(cx, string).get();
364 : 6 : }
365 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "line", &val)) {
366 : 0 : continue;
367 : : }
368 [ + - ]: 6 : if (val.isInt32()) {
369 : 6 : out << ":" << val.toInt32();
370 : : }
371 [ - + ]: 6 : if (!JS_GetProperty(cx, rvalObj, "column", &val)) {
372 : 0 : continue;
373 : : }
374 [ + - ]: 6 : if (val.isInt32()) {
375 : 6 : out << ":" << val.toInt32() + 1;
376 : : }
377 [ + - + - : 3616 : }
+ - + + +
+ + + +
+ ]
378 : 90 : return out.str();
379 : 90 : }
380 : :
381 : 15 : JS::RootedValue stack{cx};
382 [ + - - + : 15 : if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) || !stack.isString())
- + ]
383 : 0 : return {};
384 : :
385 : 15 : JS::RootedString str{cx, stack.toString()};
386 : : bool is_empty;
387 [ + - + + : 15 : if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty)
+ + ]
388 : 5 : return {};
389 : :
390 : 10 : JS::UniqueChars utf8_stack{JS_EncodeStringToUTF8(cx, str)};
391 [ - + ]: 10 : if (!utf8_stack)
392 : 0 : return {};
393 : :
394 : 10 : out << '\n' << utf8_stack.get();
395 : 10 : return out.str();
396 : 105 : }
397 : :
398 : : // Helper function: format the file name, line number, and column number where a
399 : : // SyntaxError occurred.
400 : 2 : static std::string format_syntax_error_location(JSContext* cx,
401 : : JS::HandleObject exc) {
402 : 2 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
403 : :
404 : 2 : JS::RootedValue property(cx);
405 : 2 : int32_t line = 0;
406 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.line_number(), &property)) {
407 [ + - ]: 2 : if (property.isInt32())
408 : 2 : line = property.toInt32();
409 : : }
410 : 2 : JS_ClearPendingException(cx);
411 : :
412 : 2 : int32_t column = 0;
413 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.column_number(), &property)) {
414 [ + - ]: 2 : if (property.isInt32())
415 : 2 : column = property.toInt32();
416 : : }
417 : 2 : JS_ClearPendingException(cx);
418 : :
419 : 2 : JS::UniqueChars utf8_filename;
420 [ + - ]: 2 : if (JS_GetPropertyById(cx, exc, atoms.file_name(), &property)) {
421 [ + - ]: 2 : if (property.isString()) {
422 : 2 : JS::RootedString str(cx, property.toString());
423 : 2 : utf8_filename = JS_EncodeStringToUTF8(cx, str);
424 : 2 : }
425 : : }
426 : 2 : JS_ClearPendingException(cx);
427 : :
428 : 2 : std::ostringstream out;
429 : 2 : out << " @ ";
430 [ + - ]: 2 : if (utf8_filename)
431 : 2 : out << utf8_filename.get();
432 : : else
433 : 0 : out << "<unknown>";
434 : 2 : out << ":" << line << ":" << column;
435 : 2 : return out.str();
436 : 2 : }
437 : :
438 : : using CauseSet = JS::GCHashSet<JSObject*, js::DefaultHasher<JSObject*>,
439 : : js::SystemAllocPolicy>;
440 : :
441 : 103 : static std::string format_exception_with_cause(
442 : : JSContext* cx, JS::HandleObject exc_obj,
443 : : JS::MutableHandle<CauseSet> seen_causes) {
444 : 103 : std::ostringstream out;
445 : 103 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
446 : :
447 : 103 : out << format_exception_stack(cx, exc_obj);
448 : :
449 : 103 : JS::RootedValue v_cause(cx);
450 [ - + ]: 103 : if (!JS_GetPropertyById(cx, exc_obj, atoms.cause(), &v_cause))
451 : 0 : JS_ClearPendingException(cx);
452 [ + + ]: 103 : if (v_cause.isUndefined())
453 : 93 : return out.str();
454 : :
455 : 10 : JS::RootedObject cause(cx);
456 [ + + ]: 10 : if (v_cause.isObject()) {
457 : 9 : cause = &v_cause.toObject();
458 : 9 : CauseSet::AddPtr entry = seen_causes.lookupForAdd(cause);
459 [ + + ]: 9 : if (entry)
460 : 1 : return out.str(); // cause has been printed already, ref cycle
461 [ - + ]: 8 : if (!seen_causes.add(entry, cause))
462 : 0 : return out.str(); // out of memory, just stop here
463 : : }
464 : :
465 : 9 : out << "\nCaused by: ";
466 : 9 : JS::RootedString exc_str(cx, exception_to_string(cx, v_cause));
467 [ + - ]: 9 : if (exc_str) {
468 : 9 : JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str);
469 [ + - ]: 9 : if (utf8_exception)
470 : 9 : out << utf8_exception.get();
471 : 9 : }
472 : 9 : JS_ClearPendingException(cx);
473 : :
474 [ + + ]: 9 : if (v_cause.isObject())
475 : 8 : out << format_exception_with_cause(cx, cause, seen_causes);
476 : :
477 : 9 : return out.str();
478 : 103 : }
479 : :
480 : 98 : static std::string format_exception_log_message(JSContext* cx,
481 : : JS::HandleValue exc,
482 : : JS::HandleString message) {
483 : 98 : std::ostringstream out;
484 : :
485 [ + + ]: 98 : if (message) {
486 : 17 : JS::UniqueChars utf8_message = JS_EncodeStringToUTF8(cx, message);
487 : 17 : JS_ClearPendingException(cx);
488 [ + - ]: 17 : if (utf8_message)
489 : 17 : out << utf8_message.get() << ": ";
490 : 17 : }
491 : :
492 : 98 : JS::RootedString exc_str(cx, exception_to_string(cx, exc));
493 [ + - ]: 98 : if (exc_str) {
494 : 98 : JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str);
495 [ + - ]: 98 : if (utf8_exception)
496 : 98 : out << utf8_exception.get();
497 : 98 : }
498 : 98 : JS_ClearPendingException(cx);
499 : :
500 [ + + ]: 98 : if (!exc.isObject())
501 : 1 : return out.str();
502 : :
503 : 97 : JS::RootedObject exc_obj(cx, &exc.toObject());
504 : 97 : const JSClass* syntax_error = js::ProtoKeyToClass(JSProto_SyntaxError);
505 [ + + ]: 97 : if (JS_InstanceOf(cx, exc_obj, syntax_error, nullptr)) {
506 : : // We log syntax errors differently, because the stack for those
507 : : // includes only the referencing module, but we want to print out the
508 : : // file name, line number, and column number from the exception.
509 : : // We assume that syntax errors have no cause property, and are not the
510 : : // cause of other exceptions, so no recursion.
511 : 2 : out << format_syntax_error_location(cx, exc_obj)
512 : 2 : << format_exception_stack(cx, exc_obj);
513 : 2 : return out.str();
514 : : }
515 : :
516 : 95 : JS::Rooted<CauseSet> seen_causes(cx);
517 : 95 : seen_causes.putNew(exc_obj);
518 : 95 : out << format_exception_with_cause(cx, exc_obj, &seen_causes);
519 : 95 : return out.str();
520 : 98 : }
521 : :
522 : : /**
523 : : * gjs_log_exception_full:
524 : : * @cx: the #JSContext
525 : : * @exc: the exception value to be logged
526 : : * @message: a string to prepend to the log message
527 : : * @level: the severity level at which to log the exception
528 : : *
529 : : * Currently, uses %G_LOG_LEVEL_WARNING if the exception is being printed after
530 : : * being caught, and %G_LOG_LEVEL_CRITICAL if it was not caught by user code.
531 : : */
532 : 98 : void gjs_log_exception_full(JSContext* cx, JS::HandleValue exc,
533 : : JS::HandleString message, GLogLevelFlags level) {
534 : 98 : JS::AutoSaveExceptionState saved_exc(cx);
535 : 98 : std::string log_msg = format_exception_log_message(cx, exc, message);
536 : 98 : g_log(G_LOG_DOMAIN, level, "JS ERROR: %s", log_msg.c_str());
537 : 98 : saved_exc.restore();
538 : 98 : }
539 : :
540 : : /**
541 : : * gjs_log_exception:
542 : : * @cx: the #JSContext
543 : : *
544 : : * Logs the exception pending on @cx, if any, in response to an exception being
545 : : * thrown that user code cannot catch or has already caught.
546 : : *
547 : : * Returns: %true if an exception was logged, %false if there was none pending.
548 : : */
549 : : bool
550 : 10213 : gjs_log_exception(JSContext *context)
551 : : {
552 : 10213 : JS::RootedValue exc(context);
553 [ + + ]: 10213 : if (!JS_GetPendingException(context, &exc))
554 : 10203 : return false;
555 : :
556 : 10 : JS_ClearPendingException(context);
557 : :
558 : 10 : gjs_log_exception_full(context, exc, nullptr, G_LOG_LEVEL_WARNING);
559 : 10 : return true;
560 : 10213 : }
561 : :
562 : : /**
563 : : * gjs_log_exception_uncaught:
564 : : * @cx: the #JSContext
565 : : *
566 : : * Logs the exception pending on @cx, if any, indicating an uncaught exception
567 : : * in the running JS program.
568 : : * (Currently, due to main loop boundaries, uncaught exceptions may not bubble
569 : : * all the way back up to the top level, so this doesn't necessarily mean the
570 : : * program exits with an error.)
571 : : *
572 : : * Returns: %true if an exception was logged, %false if there was none pending.
573 : : */
574 : 10193 : bool gjs_log_exception_uncaught(JSContext* cx) {
575 : 10193 : JS::RootedValue exc(cx);
576 [ + + ]: 10193 : if (!JS_GetPendingException(cx, &exc))
577 : 10178 : return false;
578 : :
579 : 15 : JS_ClearPendingException(cx);
580 : :
581 : 15 : gjs_log_exception_full(cx, exc, nullptr, G_LOG_LEVEL_CRITICAL);
582 : 15 : return true;
583 : 10193 : }
584 : :
585 : : #ifdef __linux__
586 : : // This type has to be long and not int32_t or int64_t, because of the %ld
587 : : // sscanf specifier mandated in "man proc". The NOLINT comment is because
588 : : // cpplint will ask you to avoid long in favour of defined bit width types.
589 : 0 : static void _linux_get_self_process_size(long* rss_size) // NOLINT(runtime/int)
590 : : {
591 : : char *iter;
592 : : gsize len;
593 : : int i;
594 : :
595 : 0 : *rss_size = 0;
596 : :
597 : 0 : Gjs::AutoChar contents;
598 [ # # ]: 0 : if (!g_file_get_contents("/proc/self/stat", contents.out(), &len, nullptr))
599 : 0 : return;
600 : :
601 : 0 : iter = contents;
602 : : // See "man proc" for where this 23 comes from
603 [ # # ]: 0 : for (i = 0; i < 23; i++) {
604 : 0 : iter = strchr (iter, ' ');
605 [ # # ]: 0 : if (!iter)
606 : 0 : return;
607 : 0 : iter++;
608 : : }
609 : 0 : sscanf(iter, " %ld", rss_size);
610 [ # # ]: 0 : }
611 : :
612 : : // We initiate a GC if RSS has grown by this much
613 : : static uint64_t linux_rss_trigger;
614 : : static int64_t last_gc_check_time;
615 : : #endif
616 : :
617 : : void
618 : 0 : gjs_gc_if_needed (JSContext *context)
619 : : {
620 : : #ifdef __linux__
621 : : {
622 : : long rss_size; // NOLINT(runtime/int)
623 : : gint64 now;
624 : :
625 : : /* We rate limit GCs to at most one per 5 frames.
626 : : One frame is 16666 microseconds (1000000/60)*/
627 : 0 : now = g_get_monotonic_time();
628 [ # # ]: 0 : if (now - last_gc_check_time < 5 * 16666)
629 : 0 : return;
630 : :
631 : 0 : last_gc_check_time = now;
632 : :
633 : 0 : _linux_get_self_process_size(&rss_size);
634 : :
635 : : /* linux_rss_trigger is initialized to 0, so currently
636 : : * we always do a full GC early.
637 : : *
638 : : * Here we see if the RSS has grown by 25% since
639 : : * our last look; if so, initiate a full GC. In
640 : : * theory using RSS is bad if we get swapped out,
641 : : * since we may be overzealous in GC, but on the
642 : : * other hand, if swapping is going on, better
643 : : * to GC.
644 : : */
645 [ # # ]: 0 : if (rss_size < 0)
646 : 0 : return; // doesn't make sense
647 : 0 : uint64_t rss_usize = rss_size;
648 [ # # ]: 0 : if (rss_usize > linux_rss_trigger) {
649 [ # # ]: 0 : linux_rss_trigger = MIN(G_MAXUINT32, rss_usize * 1.25);
650 : 0 : JS::NonIncrementalGC(context, JS::GCOptions::Shrink,
651 : : Gjs::GCReason::LINUX_RSS_TRIGGER);
652 [ # # ]: 0 : } else if (rss_size < (0.75 * linux_rss_trigger)) {
653 : : /* If we've shrunk by 75%, lower the trigger */
654 : 0 : linux_rss_trigger = rss_usize * 1.25;
655 : : }
656 : : }
657 : : #else // !__linux__
658 : : (void)context;
659 : : #endif
660 : : }
661 : :
662 : : /**
663 : : * gjs_maybe_gc:
664 : : *
665 : : * Low level version of gjs_context_maybe_gc().
666 : : */
667 : : void
668 : 0 : gjs_maybe_gc (JSContext *context)
669 : : {
670 : 0 : JS_MaybeGC(context);
671 : 0 : gjs_gc_if_needed(context);
672 : 0 : }
673 : :
674 : 0 : const char* gjs_explain_gc_reason(JS::GCReason reason) {
675 [ # # ]: 0 : if (JS::InternalGCReason(reason))
676 : 0 : return JS::ExplainGCReason(reason);
677 : :
678 : : static const char* reason_strings[] = {
679 : : // clang-format off
680 : : "RSS above threshold",
681 : : "GjsContext disposed",
682 : : "Big Hammer hit",
683 : : "gjs_context_gc() called",
684 : : "Memory usage is low",
685 : : // clang-format on
686 : : };
687 : : static_assert(G_N_ELEMENTS(reason_strings) == Gjs::GCReason::N_REASONS,
688 : : "Explanations must match the values in Gjs::GCReason");
689 : :
690 : 0 : g_assert(size_t(reason) < size_t(JS::GCReason::FIRST_FIREFOX_REASON) +
691 : : Gjs::GCReason::N_REASONS &&
692 : : "Bad Gjs::GCReason");
693 : 0 : return reason_strings[size_t(reason) -
694 : 0 : size_t(JS::GCReason::FIRST_FIREFOX_REASON)];
695 : : }
|