LCOV - code coverage report
Current view: top level - gjs - jsapi-util-root.h (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 88 89 98.9 %
Date: 2024-02-27 17:05:05 Functions: 29 29 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: 2017 Endless Mobile, Inc.
       4                 :            : // SPDX-FileCopyrightText: 2019 Canonical, Ltd.
       5                 :            : 
       6                 :            : #ifndef GJS_JSAPI_UTIL_ROOT_H_
       7                 :            : #define GJS_JSAPI_UTIL_ROOT_H_
       8                 :            : 
       9                 :            : #include <config.h>
      10                 :            : 
      11                 :            : #include <cstddef>  // for nullptr_t
      12                 :            : #include <memory>
      13                 :            : #include <new>
      14                 :            : #include <type_traits>  // for enable_if_t, is_pointer
      15                 :            : 
      16                 :            : #include <glib.h>
      17                 :            : 
      18                 :            : #include <js/GCAPI.h>
      19                 :            : #include <js/HeapAPI.h>     // for ExposeObjectToActiveJS, GetGCThingZone
      20                 :            : #include <js/RootingAPI.h>  // for SafelyInitialized
      21                 :            : #include <js/TracingAPI.h>
      22                 :            : #include <js/TypeDecls.h>
      23                 :            : 
      24                 :            : #include "util/log.h"
      25                 :            : 
      26                 :            : /* jsapi-util-root.h - Utilities for dealing with the lifetime and ownership of
      27                 :            :  * JS Objects and other things that can be collected by the garbage collector
      28                 :            :  * (collectively called "GC things.")
      29                 :            :  *
      30                 :            :  * GjsMaybeOwned<T> is a multi-purpose wrapper for a GC thing of type T. You can
      31                 :            :  * wrap a thing in one of three ways:
      32                 :            :  *
      33                 :            :  * - trace the thing (tie it to the lifetime of another GC thing),
      34                 :            :  * - root the thing (keep it alive as long as the wrapper is in existence),
      35                 :            :  * - maintain a weak pointer to the thing (not keep it alive at all and have it
      36                 :            :  *   possibly be finalized out from under you).
      37                 :            :  *
      38                 :            :  * To trace or maintain a weak pointer, simply assign a thing of type T to the
      39                 :            :  * GjsMaybeOwned wrapper. For tracing, you must call the trace() method when
      40                 :            :  * your other GC thing is traced.
      41                 :            :  *
      42                 :            :  * Rooting requires a JSContext so can't just assign a thing of type T. Instead
      43                 :            :  * you need to call the root() method to set up rooting.
      44                 :            :  *
      45                 :            :  * If the thing is rooted, it will be unrooted either when the GjsMaybeOwned is
      46                 :            :  * destroyed, or when the JSContext is destroyed. In the latter case, you can
      47                 :            :  * get an optional notification by registering a callback in the PrivateContext.
      48                 :            :  *
      49                 :            :  * To switch between one of the three modes, you must first call reset(). This
      50                 :            :  * drops all references to any GC thing and leaves the GjsMaybeOwned in the
      51                 :            :  * same state as if it had just been constructed.
      52                 :            :  */
      53                 :            : 
      54                 :            : /* This struct contains operations that must be implemented differently
      55                 :            :  * depending on the type of the GC thing. Add more types as necessary. If an
      56                 :            :  * implementation is never used, it's OK to leave it out. The compiler will
      57                 :            :  * complain if it's used somewhere but not instantiated here.
      58                 :            :  */
      59                 :            : template<typename T>
      60                 :            : struct GjsHeapOperation {
      61                 :            :     [[nodiscard]] static bool update_after_gc(JS::Heap<T>* location);
      62                 :            :     static void expose_to_js(JS::Heap<T>& thing);
      63                 :            : };
      64                 :            : 
      65                 :            : template<>
      66                 :            : struct GjsHeapOperation<JSObject *> {
      67                 :       1535 :     [[nodiscard]] static bool update_after_gc(JSTracer* trc,
      68                 :            :                                               JS::Heap<JSObject*>* location) {
      69                 :       1535 :         JS_UpdateWeakPointerAfterGC(trc, location);
      70                 :       1535 :         return (location->unbarrieredGet() == nullptr);
      71                 :            :     }
      72                 :            : 
      73                 :        406 :     static void expose_to_js(JS::Heap<JSObject *>& thing) {
      74                 :        406 :         JSObject *obj = thing.unbarrieredGet();
      75                 :            :         /* If the object has been swept already, then the zone is nullptr */
      76   [ +  -  -  +  :        406 :         if (!obj || !JS::GetGCThingZone(JS::GCCellPtr(obj)))
                   -  + ]
      77                 :          0 :             return;
      78         [ +  + ]:        406 :         if (!JS::RuntimeHeapIsCollecting())
      79                 :        137 :             JS::ExposeObjectToActiveJS(obj);
      80                 :            :     }
      81                 :            : };
      82                 :            : 
      83                 :            : /* GjsMaybeOwned is intended for use as a member of classes that are allocated
      84                 :            :  * on the heap. Do not allocate GjsMaybeOwned on the stack, and do not allocate
      85                 :            :  * any instances of classes that have it as a member on the stack either. */
      86                 :            : template<typename T>
      87                 :            : class GjsMaybeOwned {
      88                 :            :  private:
      89                 :            :     /* m_root value controls which of these members we can access. When switching
      90                 :            :      * from one to the other, be careful to call the constructor and destructor
      91                 :            :      * of JS::Heap, since they use post barriers. */
      92                 :            :     JS::Heap<T> m_heap;
      93                 :            :     std::unique_ptr<JS::PersistentRooted<T>> m_root;
      94                 :            : 
      95                 :            :     /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */
      96                 :      75805 :     inline void debug(const char* what GJS_USED_VERBOSE_LIFECYCLE) {
      97                 :            :         gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "GjsMaybeOwned %p %s", this,
      98                 :            :                             what);
      99                 :      75805 :     }
     100                 :            : 
     101                 :            :     void
     102                 :      13241 :     teardown_rooting()
     103                 :            :     {
     104                 :      13241 :         debug("teardown_rooting()");
     105                 :      13241 :         g_assert(m_root);
     106                 :            : 
     107                 :      13241 :         m_root.reset();
     108                 :            : 
     109                 :      13241 :         new (&m_heap) JS::Heap<T>();
     110                 :      13241 :     }
     111                 :            : 
     112                 :            :  public:
     113                 :      13625 :     GjsMaybeOwned() {
     114                 :      13625 :         debug("created");
     115                 :      13625 :     }
     116                 :            : 
     117                 :      13598 :     ~GjsMaybeOwned() {
     118                 :      13598 :         debug("destroyed");
     119                 :      13598 :     }
     120                 :            : 
     121                 :            :     /* To access the GC thing, call get(). In many cases you can just use the
     122                 :            :      * GjsMaybeOwned wrapper in place of the GC thing itself due to the implicit
     123                 :            :      * cast operator. But if you want to call methods on the GC thing, for
     124                 :            :      * example if it's a JS::Value, you have to use get(). */
     125                 :      29277 :     [[nodiscard]] constexpr const T get() const {
     126         [ +  + ]:      29277 :         return m_root ? m_root->get() : m_heap.get();
     127                 :            :     }
     128                 :      29277 :     constexpr operator const T() const { return get(); }
     129                 :            : 
     130                 :            :     /* Use debug_addr() only for debug logging, because it is unbarriered. */
     131                 :            :     template <typename U = T>
     132                 :       6850 :     [[nodiscard]] constexpr const void* debug_addr(
     133                 :            :         std::enable_if_t<std::is_pointer_v<U>>* = nullptr) const {
     134         [ +  + ]:       6850 :         return m_root ? m_root->get() : m_heap.unbarrieredGet();
     135                 :            :     }
     136                 :            : 
     137                 :        486 :     constexpr bool operator==(const T& other) const {
     138         [ +  + ]:        486 :         if (m_root)
     139                 :         43 :             return m_root->get() == other;
     140                 :        443 :         return m_heap == other;
     141                 :            :     }
     142                 :        483 :     constexpr bool operator!=(const T& other) const {
     143                 :        483 :         return !(*this == other);
     144                 :            :     }
     145                 :            : 
     146                 :            :     /* We can access the pointer without a read barrier if the only thing we
     147                 :            :      * are doing with it is comparing it to nullptr. */
     148                 :      35866 :     constexpr bool operator==(std::nullptr_t) const {
     149         [ +  + ]:      35866 :         if (m_root)
     150                 :      31589 :             return m_root->get() == nullptr;
     151                 :       4277 :         return m_heap.unbarrieredGet() == nullptr;
     152                 :            :     }
     153                 :      35866 :     constexpr bool operator!=(std::nullptr_t) const {
     154                 :      35866 :         return !(*this == nullptr);
     155                 :            :     }
     156                 :            : 
     157                 :            :     /* Likewise the truth value does not require a read barrier */
     158                 :      35866 :     constexpr explicit operator bool() const { return *this != nullptr; }
     159                 :            : 
     160                 :            :     /* You can get a Handle<T> if the thing is rooted, so that you can use this
     161                 :            :      * wrapper with stack rooting. However, you must not do this if the
     162                 :            :      * JSContext can be destroyed while the Handle is live. */
     163                 :            :     [[nodiscard]] constexpr JS::Handle<T> handle() {
     164                 :            :         g_assert(m_root);
     165                 :            :         return *m_root;
     166                 :            :     }
     167                 :            : 
     168                 :            :     /* Roots the GC thing. You must not use this if you're already using the
     169                 :            :      * wrapper to store a non-rooted GC thing. */
     170                 :      13249 :     void root(JSContext* cx, const T& thing) {
     171                 :      13249 :         debug("root()");
     172                 :      13249 :         g_assert(!m_root);
     173                 :      13249 :         g_assert(m_heap.get() == JS::SafelyInitialized<T>::create());
     174                 :      13249 :         m_heap.~Heap();
     175                 :      13249 :         m_root = std::make_unique<JS::PersistentRooted<T>>(cx, thing);
     176                 :      13249 :     }
     177                 :            : 
     178                 :            :     /* You can only assign directly to the GjsMaybeOwned wrapper in the
     179                 :            :      * non-rooted case. */
     180                 :            :     void
     181                 :       1950 :     operator=(const T& thing)
     182                 :            :     {
     183                 :       1950 :         g_assert(!m_root);
     184                 :       1950 :         m_heap = thing;
     185                 :       1950 :     }
     186                 :            : 
     187                 :            :     /* Marks an object as reachable for one GC with ExposeObjectToActiveJS().
     188                 :            :      * Use to avoid stopping tracing an object during GC. This makes no sense
     189                 :            :      * in the rooted case. */
     190                 :        406 :     void prevent_collection() {
     191                 :        406 :         debug("prevent_collection()");
     192                 :        406 :         g_assert(!m_root);
     193                 :        406 :         GjsHeapOperation<T>::expose_to_js(m_heap);
     194                 :        406 :     }
     195                 :            : 
     196                 :      16929 :     void reset() {
     197                 :      16929 :         debug("reset()");
     198         [ +  + ]:      16929 :         if (!m_root) {
     199                 :       3688 :             m_heap = JS::SafelyInitialized<T>::create();
     200                 :       3688 :             return;
     201                 :            :         }
     202                 :            : 
     203                 :      13241 :         teardown_rooting();
     204                 :            :     }
     205                 :            : 
     206                 :       1590 :     void switch_to_rooted(JSContext* cx) {
     207                 :       1590 :         debug("switch to rooted");
     208                 :       1590 :         g_assert(!m_root);
     209                 :            : 
     210                 :            :         /* Prevent the thing from being garbage collected while it is in neither
     211                 :            :          * m_heap nor m_root */
     212                 :       1590 :         JS::Rooted<T> thing(cx, m_heap);
     213                 :            : 
     214                 :       1590 :         reset();
     215                 :       1590 :         root(cx, thing);
     216                 :       1590 :         g_assert(m_root);
     217                 :       1590 :     }
     218                 :            : 
     219                 :       1449 :     void switch_to_unrooted(JSContext* cx) {
     220                 :       1449 :         debug("switch to unrooted");
     221                 :       1449 :         g_assert(m_root);
     222                 :            : 
     223                 :            :         /* Prevent the thing from being garbage collected while it is in neither
     224                 :            :          * m_heap nor m_root */
     225                 :       1449 :         JS::Rooted<T> thing(cx, *m_root);
     226                 :            : 
     227                 :       1449 :         reset();
     228                 :       1449 :         m_heap = thing;
     229                 :       1449 :         g_assert(!m_root);
     230                 :       1449 :     }
     231                 :            : 
     232                 :            :     /* Tracing makes no sense in the rooted case, because JS::PersistentRooted
     233                 :            :      * already takes care of that. */
     234                 :            :     void
     235                 :        183 :     trace(JSTracer   *tracer,
     236                 :            :           const char *name)
     237                 :            :     {
     238                 :        183 :         debug("trace()");
     239                 :        183 :         g_assert(!m_root);
     240                 :        183 :         JS::TraceEdge<T>(tracer, &m_heap, name);
     241                 :        183 :     }
     242                 :            : 
     243                 :            :     /* If not tracing, then you must call this method during GC in order to
     244                 :            :      * update the object's location if it was moved, or null it out if it was
     245                 :            :      * finalized. If the object was finalized, returns true. */
     246                 :       1535 :     bool update_after_gc(JSTracer* trc) {
     247                 :       1535 :         debug("update_after_gc()");
     248                 :       1535 :         g_assert(!m_root);
     249                 :       1535 :         return GjsHeapOperation<T>::update_after_gc(trc, &m_heap);
     250                 :            :     }
     251                 :            : 
     252                 :      20895 :     [[nodiscard]] constexpr bool rooted() const { return m_root != nullptr; }
     253                 :            : };
     254                 :            : 
     255                 :            : #endif  // GJS_JSAPI_UTIL_ROOT_H_

Generated by: LCOV version 1.14