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