LCOV - code coverage report
Current view: top level - gjs - jsapi-util.cpp (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 211 274 77.0 %
Date: 2024-02-27 17:05:05 Functions: 20 24 83.3 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 74 138 53.6 %

           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                 :         84 : 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         [ +  - ]:         84 :     if (description)
      60                 :         84 :         gjs_throw(cx, "No property '%s' in %s (or %s)",
      61                 :        168 :                   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                 :         84 : }
      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                 :       2641 : 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                 :       2641 :     value.setUndefined();
      84                 :            : 
      85         [ -  + ]:       2641 :     if (G_UNLIKELY(!JS_GetPropertyById(context, obj, property_name, value)))
      86                 :          0 :         return false;
      87                 :            : 
      88         [ +  + ]:       2641 :     if (G_LIKELY(!value.isUndefined()))
      89                 :       2639 :         return true;
      90                 :            : 
      91                 :          2 :     throw_property_lookup_error(context, obj, obj_description, property_name,
      92                 :            :                                 "its value was undefined");
      93                 :          2 :     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                 :        173 : bool gjs_object_require_property(JSContext* cx, JS::HandleObject obj,
     136                 :            :                                  const char* description,
     137                 :            :                                  JS::HandleId property_name,
     138                 :            :                                  JS::UniqueChars* value) {
     139                 :        173 :     JS::RootedValue prop_value(cx);
     140         [ +  - ]:        173 :     if (JS_GetPropertyById(cx, obj, property_name, &prop_value)) {
     141                 :        173 :         JS::UniqueChars tmp = gjs_string_to_utf8(cx, prop_value);
     142         [ +  - ]:        173 :         if (tmp) {
     143                 :        173 :             *value = std::move(tmp);
     144                 :        173 :             return true;
     145                 :            :         }
     146         [ -  + ]:        173 :     }
     147                 :            : 
     148                 :          0 :     throw_property_lookup_error(cx, obj, description, property_name,
     149                 :            :                                 "it was not a valid string");
     150                 :          0 :     return false;
     151                 :        173 : }
     152                 :            : 
     153                 :            : bool
     154                 :      25496 : 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                 :      25496 :     JS::RootedValue prop_value(cx);
     161   [ +  +  +  -  :      50910 :     if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
                   +  + ]
     162                 :      25414 :         prop_value.isObject()) {
     163                 :      25414 :         value.set(&prop_value.toObject());
     164                 :      25414 :         return true;
     165                 :            :     }
     166                 :            : 
     167                 :         82 :     throw_property_lookup_error(cx, obj, description, property_name,
     168                 :            :                                 "it was not an object");
     169                 :         82 :     return false;
     170                 :      25496 : }
     171                 :            : 
     172                 :            : bool
     173                 :        322 : 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                 :        322 :     JS::RootedValue prop_value(cx);
     180   [ +  -  +  - ]:        644 :     if (JS_GetPropertyById(cx, obj, property_name, &prop_value) &&
     181         [ +  - ]:        644 :         JS::ToUint32(cx, prop_value, value)) {
     182                 :        322 :         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                 :        322 : }
     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                 :        375 : JSObject* gjs_build_string_array(JSContext* context,
     214                 :            :                                  const std::vector<std::string>& strings) {
     215                 :        375 :     JS::RootedValueVector elems(context);
     216         [ -  + ]:        375 :     if (!elems.reserve(strings.size())) {
     217                 :          0 :         JS_ReportOutOfMemory(context);
     218                 :          0 :         return nullptr;
     219                 :            :     }
     220                 :            : 
     221         [ +  + ]:       1747 :     for (const std::string& string : strings) {
     222                 :       1372 :         JS::ConstUTF8CharsZ chars(string.c_str(), string.size());
     223                 :            :         JS::RootedValue element(context,
     224                 :       1372 :             JS::StringValue(JS_NewStringCopyUTF8Z(context, chars)));
     225                 :       1372 :         elems.infallibleAppend(element);
     226                 :       1372 :     }
     227                 :            : 
     228                 :        375 :     return JS::NewArrayObject(context, elems);
     229                 :        375 : }
     230                 :            : 
     231                 :        311 : 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                 :        311 :     JS::RootedObject array(context, gjs_build_string_array(context, strings));
     237         [ -  + ]:        311 :     if (!array)
     238                 :          0 :         return nullptr;
     239                 :            : 
     240         [ -  + ]:        311 :     if (!JS_DefineProperty(context, in_object, array_name, array, attrs))
     241                 :          0 :         return nullptr;
     242                 :            : 
     243                 :        311 :     return array;
     244                 :        311 : }
     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                 :       8827 : gjs_log_exception(JSContext  *context)
     456                 :            : {
     457                 :       8827 :     JS::RootedValue exc(context);
     458         [ +  + ]:       8827 :     if (!JS_GetPendingException(context, &exc))
     459                 :       8817 :         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                 :       8827 : }
     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                 :       8806 : bool gjs_log_exception_uncaught(JSContext* cx) {
     480                 :       8806 :     JS::RootedValue exc(cx);
     481         [ +  + ]:       8806 :     if (!JS_GetPendingException(cx, &exc))
     482                 :       8793 :         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                 :       8806 : }
     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                 :        483 : const char* gjs_explain_gc_reason(JS::GCReason reason) {
     580         [ +  + ]:        483 :     if (JS::InternalGCReason(reason))
     581                 :        278 :         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                 :        205 :     g_assert(size_t(reason) < size_t(JS::GCReason::FIRST_FIREFOX_REASON) +
     596                 :            :                                   Gjs::GCReason::N_REASONS &&
     597                 :            :              "Bad Gjs::GCReason");
     598                 :        205 :     return reason_strings[size_t(reason) -
     599                 :        205 :                           size_t(JS::GCReason::FIRST_FIREFOX_REASON)];
     600                 :            : }

Generated by: LCOV version 1.14