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

           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                 :      11591 : Closure::Closure(JSContext* cx, JSObject* callable, bool root,
      27                 :      11591 :                  const char* description GJS_USED_VERBOSE_GCLOSURE)
      28                 :      11591 :     : m_cx(cx) {
      29                 :      11591 :     GJS_INC_COUNTER(closure);
      30                 :            :     GClosureNotify closure_notify;
      31                 :            : 
      32         [ +  + ]:      11591 :     if (root) {
      33                 :            :         // Fully manage closure lifetime if so asked
      34                 :      11316 :         auto* gjs = GjsContextPrivate::from_cx(cx);
      35                 :      11316 :         g_assert(cx == gjs->context());
      36                 :      11316 :         m_callable.root(cx, callable);
      37                 :      11316 :         gjs->register_notifier(global_context_notifier_cb, this);
      38                 :      11316 :         closure_notify = [](void*, GClosure* closure) {
      39                 :      11313 :             static_cast<Closure*>(closure)->closure_invalidated();
      40                 :      11313 :         };
      41                 :            :     } else {
      42                 :            :         // Only mark the closure as invalid if memory is managed
      43                 :            :         // outside (i.e. by object.c for signals)
      44                 :        275 :         m_callable = callable;
      45                 :        275 :         closure_notify = [](void*, GClosure* closure) {
      46                 :        274 :             static_cast<Closure*>(closure)->closure_set_invalid();
      47                 :        274 :         };
      48                 :            :     }
      49                 :            : 
      50                 :      11591 :     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                 :      11591 : }
      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                 :      23156 : void Closure::unset_context() {
      91         [ +  + ]:      23156 :     if (!m_cx)
      92                 :      11788 :         return;
      93                 :            : 
      94   [ +  -  +  +  :      11368 :     if (m_callable && m_callable.rooted()) {
                   +  + ]
      95                 :      11094 :         auto* gjs = GjsContextPrivate::from_cx(m_cx);
      96                 :      11094 :         gjs->unregister_notifier(global_context_notifier_cb, this);
      97                 :            :     }
      98                 :            : 
      99                 :      11368 :     m_cx = nullptr;
     100                 :            : }
     101                 :            : 
     102                 :        219 : 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         [ +  - ]:        219 :     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                 :        219 :         m_cx = nullptr;
     112                 :        219 :         reset();
     113                 :            :         // Notify any closure reference holders they
     114                 :            :         // may want to drop references.
     115                 :        219 :         g_closure_invalidate(this);
     116                 :            :     }
     117                 :        219 : }
     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                 :      11313 : void Closure::closure_invalidated() {
     131                 :      11313 :     GJS_DEC_COUNTER(closure);
     132                 :            :     gjs_debug_closure("Invalidating closure %p which calls callable %p", this,
     133                 :            :                       m_callable.debug_addr());
     134                 :            : 
     135         [ +  + ]:      11313 :     if (!m_callable) {
     136                 :            :         gjs_debug_closure("   (closure %p already dead, nothing to do)", this);
     137                 :        219 :         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                 :      11094 :     reset();
     153                 :            : }
     154                 :            : 
     155                 :        274 : 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                 :        274 :     m_callable.prevent_collection();
     160                 :        274 :     reset();
     161                 :            : 
     162                 :        274 :     GJS_DEC_COUNTER(closure);
     163                 :        274 : }
     164                 :            : 
     165                 :       8317 : bool Closure::invoke(JS::HandleObject this_obj,
     166                 :            :                      const JS::HandleValueArray& args,
     167                 :            :                      JS::MutableHandleValue retval) {
     168         [ -  + ]:       8317 :     if (!m_callable) {
     169                 :            :         /* We were destroyed; become a no-op */
     170                 :          0 :         reset();
     171                 :          0 :         return false;
     172                 :            :     }
     173                 :            : 
     174                 :       8317 :     JSAutoRealm ar(m_cx, m_callable);
     175                 :            : 
     176                 :       8317 :     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                 :       8317 :     JS::RootedValue v_callable(m_cx, JS::ObjectValue(*m_callable));
     184                 :       8317 :     bool ok = JS::Call(m_cx, this_obj, v_callable, args, retval);
     185                 :       8316 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx);
     186                 :            : 
     187         [ +  + ]:       8316 :     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                 :         17 :         return false;
     194                 :            :     }
     195                 :            : 
     196                 :       8299 :     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                 :       8299 :     gjs->schedule_gc_if_needed();
     204                 :       8299 :     return true;
     205                 :       8316 : }
     206                 :            : 
     207                 :            : }  // namespace Gjs

Generated by: LCOV version 1.14