LCOV - code coverage report
Current view: top level - gjs - jsapi-util.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 72.9 % 365 266
Test Date: 2025-05-21 00:02:11 Functions: 80.0 % 25 20
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 54.0 % 211 114

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

Generated by: LCOV version 2.0-1