LCOV - code coverage report
Current view: top level - gjs - jsapi-util.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 76.3 % 334 255
Test Date: 2025-02-15 06:20:10 Functions: 79.2 % 24 19
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 56.2 % 194 109

             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                 :             : }
        

Generated by: LCOV version 2.0-1