LCOV - code coverage report
Current view: top level - gi - closure.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 96.4 % 56 54
Test Date: 2024-04-29 05:18:28 Functions: 100.0 % 8 8
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 83.3 % 18 15

             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: 2021 Canonical Ltd.
       5                 :             : // SPDX-FileContributor: Marco Trevisan <marco.trevisan@canonical.com>
       6                 :             : 
       7                 :             : #include <config.h>
       8                 :             : 
       9                 :             : #include <glib.h>  // for g_assert
      10                 :             : 
      11                 :             : #include <js/CallAndConstruct.h>
      12                 :             : #include <js/Realm.h>
      13                 :             : #include <js/RootingAPI.h>
      14                 :             : #include <js/TypeDecls.h>
      15                 :             : #include <js/Value.h>
      16                 :             : 
      17                 :             : #include "gi/closure.h"
      18                 :             : #include "gjs/context-private.h"
      19                 :             : #include "gjs/jsapi-util-root.h"
      20                 :             : #include "gjs/jsapi-util.h"
      21                 :             : #include "gjs/mem-private.h"
      22                 :             : #include "util/log.h"
      23                 :             : 
      24                 :             : namespace Gjs {
      25                 :             : 
      26                 :       12209 : Closure::Closure(JSContext* cx, JSObject* callable, bool root,
      27                 :       12209 :                  const char* description GJS_USED_VERBOSE_GCLOSURE)
      28                 :       12209 :     : m_cx(cx) {
      29                 :       12209 :     GJS_INC_COUNTER(closure);
      30                 :             :     GClosureNotify closure_notify;
      31                 :             : 
      32         [ +  + ]:       12209 :     if (root) {
      33                 :             :         // Fully manage closure lifetime if so asked
      34                 :       11792 :         auto* gjs = GjsContextPrivate::from_cx(cx);
      35                 :       11792 :         g_assert(cx == gjs->context());
      36                 :       11792 :         m_callable.root(cx, callable);
      37                 :       11792 :         gjs->register_notifier(global_context_notifier_cb, this);
      38                 :       11792 :         closure_notify = [](void*, GClosure* closure) {
      39                 :       11789 :             static_cast<Closure*>(closure)->closure_invalidated();
      40                 :       11789 :         };
      41                 :             :     } else {
      42                 :             :         // Only mark the closure as invalid if memory is managed
      43                 :             :         // outside (i.e. by object.c for signals)
      44                 :         417 :         m_callable = callable;
      45                 :         417 :         closure_notify = [](void*, GClosure* closure) {
      46                 :         416 :             static_cast<Closure*>(closure)->closure_set_invalid();
      47                 :         416 :         };
      48                 :             :     }
      49                 :             : 
      50                 :       12209 :     g_closure_add_invalidate_notifier(this, nullptr, closure_notify);
      51                 :             : 
      52                 :             :     gjs_debug_closure("Create closure %p which calls callable %p '%s'", this,
      53                 :             :                       m_callable.debug_addr(), description);
      54                 :       12209 : }
      55                 :             : 
      56                 :             : /*
      57                 :             :  * Memory management of closures is "interesting" because we're keeping around
      58                 :             :  * a JSContext* and then trying to use it spontaneously from the main loop.
      59                 :             :  * I don't think that's really quite kosher, and perhaps the problem is that
      60                 :             :  * (in xulrunner) we just need to save a different context.
      61                 :             :  *
      62                 :             :  * Or maybe the right fix is to create our own context just for this?
      63                 :             :  *
      64                 :             :  * But for the moment, we save the context that was used to create the closure.
      65                 :             :  *
      66                 :             :  * Here's the problem: this context can be destroyed. AFTER the
      67                 :             :  * context is destroyed, or at least potentially after, the objects in
      68                 :             :  * the context's global object may be garbage collected. Remember that
      69                 :             :  * JSObject* belong to a runtime, not a context.
      70                 :             :  *
      71                 :             :  * There is apparently no robust way to track context destruction in
      72                 :             :  * SpiderMonkey, because the context can be destroyed without running
      73                 :             :  * the garbage collector, and xulrunner takes over the JS_SetContextCallback()
      74                 :             :  * callback. So there's no callback for us.
      75                 :             :  *
      76                 :             :  * So, when we go to use our context, we iterate the contexts in the runtime
      77                 :             :  * and see if ours is still in the valid list, and decide to invalidate
      78                 :             :  * the closure if it isn't.
      79                 :             :  *
      80                 :             :  * The closure can thus be destroyed in several cases:
      81                 :             :  * - invalidation by unref, e.g. when a signal is disconnected, closure is unref'd
      82                 :             :  * - invalidation because we were invoked while the context was dead
      83                 :             :  * - invalidation through finalization (we were garbage collected)
      84                 :             :  *
      85                 :             :  * These don't have to happen in the same order; garbage collection can
      86                 :             :  * be either before, or after, context destruction.
      87                 :             :  *
      88                 :             :  */
      89                 :             : 
      90                 :       24388 : void Closure::unset_context() {
      91         [ +  + ]:       24388 :     if (!m_cx)
      92                 :       12367 :         return;
      93                 :             : 
      94   [ +  -  +  +  :       12021 :     if (m_callable && m_callable.rooted()) {
                   +  + ]
      95                 :       11605 :         auto* gjs = GjsContextPrivate::from_cx(m_cx);
      96                 :       11605 :         gjs->unregister_notifier(global_context_notifier_cb, this);
      97                 :             :     }
      98                 :             : 
      99                 :       12021 :     m_cx = nullptr;
     100                 :             : }
     101                 :             : 
     102                 :         184 : void Closure::global_context_finalized() {
     103                 :             :     gjs_debug_closure(
     104                 :             :         "Context global object destroy notifier on closure %p which calls "
     105                 :             :         "callable %p",
     106                 :             :         this, m_callable.debug_addr());
     107                 :             : 
     108         [ +  - ]:         184 :     if (m_callable) {
     109                 :             :         // Manually unset the context as we don't need to unregister the
     110                 :             :         // notifier here, or we'd end up touching a vector we're iterating
     111                 :         184 :         m_cx = nullptr;
     112                 :         184 :         reset();
     113                 :             :         // Notify any closure reference holders they
     114                 :             :         // may want to drop references.
     115                 :         184 :         g_closure_invalidate(this);
     116                 :             :     }
     117                 :         184 : }
     118                 :             : 
     119                 :             : /* Invalidation is like "dispose" - it is guaranteed to happen at
     120                 :             :  * finalize, but may happen before finalize. Normally, g_closure_invalidate()
     121                 :             :  * is called when the "target" of the closure becomes invalid, so that the
     122                 :             :  * source (the signal connection, say can be removed.) The usage above
     123                 :             :  * in global_context_finalized() is typical. Since the target of the closure
     124                 :             :  * is under our control, it's unlikely that g_closure_invalidate() will ever
     125                 :             :  * be called by anyone else, but in case it ever does, it's slightly better
     126                 :             :  * to remove the "keep alive" here rather than in the finalize notifier.
     127                 :             :  *
     128                 :             :  * Unlike "dispose" invalidation only happens once.
     129                 :             :  */
     130                 :       11789 : void Closure::closure_invalidated() {
     131                 :       11789 :     GJS_DEC_COUNTER(closure);
     132                 :             :     gjs_debug_closure("Invalidating closure %p which calls callable %p", this,
     133                 :             :                       m_callable.debug_addr());
     134                 :             : 
     135         [ +  + ]:       11789 :     if (!m_callable) {
     136                 :             :         gjs_debug_closure("   (closure %p already dead, nothing to do)", this);
     137                 :         184 :         return;
     138                 :             :     }
     139                 :             : 
     140                 :             :     /* The context still exists, remove our destroy notifier. Otherwise we
     141                 :             :      * would call the destroy notifier on an already-freed closure.
     142                 :             :      *
     143                 :             :      * This happens in the normal case, when the closure is
     144                 :             :      * invalidated for some reason other than destruction of the
     145                 :             :      * JSContext.
     146                 :             :      */
     147                 :             :     gjs_debug_closure(
     148                 :             :         "   (closure %p's context was alive, "
     149                 :             :         "removing our destroy notifier on global object)",
     150                 :             :         this);
     151                 :             : 
     152                 :       11605 :     reset();
     153                 :             : }
     154                 :             : 
     155                 :         416 : void Closure::closure_set_invalid() {
     156                 :             :     gjs_debug_closure("Invalidating signal closure %p which calls callable %p",
     157                 :             :                       this, m_callable.debug_addr());
     158                 :             : 
     159                 :         416 :     m_callable.prevent_collection();
     160                 :         416 :     reset();
     161                 :             : 
     162                 :         416 :     GJS_DEC_COUNTER(closure);
     163                 :         416 : }
     164                 :             : 
     165                 :        8902 : bool Closure::invoke(JS::HandleObject this_obj,
     166                 :             :                      const JS::HandleValueArray& args,
     167                 :             :                      JS::MutableHandleValue retval) {
     168         [ -  + ]:        8902 :     if (!m_callable) {
     169                 :             :         /* We were destroyed; become a no-op */
     170                 :           0 :         reset();
     171                 :           0 :         return false;
     172                 :             :     }
     173                 :             : 
     174                 :        8902 :     JSAutoRealm ar{m_cx, m_callable.get()};
     175                 :             : 
     176                 :        8902 :     if (gjs_log_exception(m_cx)) {
     177                 :             :         gjs_debug_closure(
     178                 :             :             "Exception was pending before invoking callback??? "
     179                 :             :             "Not expected - closure %p",
     180                 :             :             this);
     181                 :             :     }
     182                 :             : 
     183                 :        8902 :     JS::RootedValue v_callable{m_cx, JS::ObjectValue(*m_callable.get())};
     184                 :        8902 :     bool ok = JS::Call(m_cx, this_obj, v_callable, args, retval);
     185                 :        8901 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx);
     186                 :             : 
     187         [ +  + ]:        8901 :     if (!ok) {
     188                 :             :         /* Exception thrown... */
     189                 :             :         gjs_debug_closure(
     190                 :             :             "Closure invocation failed (exception should have been thrown) "
     191                 :             :             "closure %p callable %p",
     192                 :             :             this, m_callable.debug_addr());
     193                 :          18 :         return false;
     194                 :             :     }
     195                 :             : 
     196                 :        8883 :     if (gjs_log_exception_uncaught(m_cx)) {
     197                 :             :         gjs_debug_closure(
     198                 :             :             "Closure invocation succeeded but an exception was set"
     199                 :             :             " - closure %p",
     200                 :             :             m_cx);
     201                 :             :     }
     202                 :             : 
     203                 :        8883 :     gjs->schedule_gc_if_needed();
     204                 :        8883 :     return true;
     205                 :        8901 : }
     206                 :             : 
     207                 :             : }  // namespace Gjs
        

Generated by: LCOV version 2.0-1