LCOV - code coverage report
Current view: top level - gjs - jsapi-util-root.h (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 98.9 % 89 88
Test Date: 2024-04-16 04:37:39 Functions: 100.0 % 29 29
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: 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                 :        1540 :     [[nodiscard]] static bool update_after_gc(JSTracer* trc,
      68                 :             :                                               JS::Heap<JSObject*>* location) {
      69                 :        1540 :         JS_UpdateWeakPointerAfterGC(trc, location);
      70                 :        1540 :         return (location->unbarrieredGet() == nullptr);
      71                 :             :     }
      72                 :             : 
      73                 :         416 :     static void expose_to_js(JS::Heap<JSObject *>& thing) {
      74                 :         416 :         JSObject *obj = thing.unbarrieredGet();
      75                 :             :         /* If the object has been swept already, then the zone is nullptr */
      76   [ +  -  -  +  :         416 :         if (!obj || !JS::GetGCThingZone(JS::GCCellPtr(obj)))
                   -  + ]
      77                 :           0 :             return;
      78         [ +  + ]:         416 :         if (!JS::RuntimeHeapIsCollecting())
      79                 :         144 :             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                 :       76710 :     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                 :       76710 :     }
     100                 :             : 
     101                 :             :     void
     102                 :       13406 :     teardown_rooting()
     103                 :             :     {
     104                 :       13406 :         debug("teardown_rooting()");
     105                 :       13406 :         g_assert(m_root);
     106                 :             : 
     107                 :       13406 :         m_root.reset();
     108                 :             : 
     109                 :       13406 :         new (&m_heap) JS::Heap<T>();
     110                 :       13406 :     }
     111                 :             : 
     112                 :             :  public:
     113                 :       13789 :     GjsMaybeOwned() {
     114                 :       13789 :         debug("created");
     115                 :       13789 :     }
     116                 :             : 
     117                 :       13762 :     ~GjsMaybeOwned() {
     118                 :       13762 :         debug("destroyed");
     119                 :       13762 :     }
     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                 :       29538 :     [[nodiscard]] constexpr const T get() const {
     126         [ +  + ]:       29538 :         return m_root ? m_root->get() : m_heap.get();
     127                 :             :     }
     128                 :       29538 :     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                 :        6924 :     [[nodiscard]] constexpr const void* debug_addr(
     133                 :             :         std::enable_if_t<std::is_pointer_v<U>>* = nullptr) const {
     134         [ +  + ]:        6924 :         return m_root ? m_root->get() : m_heap.unbarrieredGet();
     135                 :             :     }
     136                 :             : 
     137                 :         485 :     constexpr bool operator==(const T& other) const {
     138         [ +  + ]:         485 :         if (m_root)
     139                 :          43 :             return m_root->get() == other;
     140                 :         442 :         return m_heap == other;
     141                 :             :     }
     142                 :         482 :     constexpr bool operator!=(const T& other) const {
     143                 :         482 :         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                 :       36268 :     constexpr bool operator==(std::nullptr_t) const {
     149         [ +  + ]:       36268 :         if (m_root)
     150                 :       31963 :             return m_root->get() == nullptr;
     151                 :        4305 :         return m_heap.unbarrieredGet() == nullptr;
     152                 :             :     }
     153                 :       36268 :     constexpr bool operator!=(std::nullptr_t) const {
     154                 :       36268 :         return !(*this == nullptr);
     155                 :             :     }
     156                 :             : 
     157                 :             :     /* Likewise the truth value does not require a read barrier */
     158                 :       36268 :     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                 :       13414 :     void root(JSContext* cx, const T& thing) {
     171                 :       13414 :         debug("root()");
     172                 :       13414 :         g_assert(!m_root);
     173                 :       13414 :         g_assert(m_heap.get() == JS::SafelyInitialized<T>::create());
     174                 :       13414 :         m_heap.~Heap();
     175                 :       13414 :         m_root = std::make_unique<JS::PersistentRooted<T>>(cx, thing);
     176                 :       13414 :     }
     177                 :             : 
     178                 :             :     /* You can only assign directly to the GjsMaybeOwned wrapper in the
     179                 :             :      * non-rooted case. */
     180                 :             :     void
     181                 :        1967 :     operator=(const T& thing)
     182                 :             :     {
     183                 :        1967 :         g_assert(!m_root);
     184                 :        1967 :         m_heap = thing;
     185                 :        1967 :     }
     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                 :         416 :     void prevent_collection() {
     191                 :         416 :         debug("prevent_collection()");
     192                 :         416 :         g_assert(!m_root);
     193                 :         416 :         GjsHeapOperation<T>::expose_to_js(m_heap);
     194                 :         416 :     }
     195                 :             : 
     196                 :       17130 :     void reset() {
     197                 :       17130 :         debug("reset()");
     198         [ +  + ]:       17130 :         if (!m_root) {
     199                 :        3724 :             m_heap = JS::SafelyInitialized<T>::create();
     200                 :        3724 :             return;
     201                 :             :         }
     202                 :             : 
     203                 :       13406 :         teardown_rooting();
     204                 :             :     }
     205                 :             : 
     206                 :        1609 :     void switch_to_rooted(JSContext* cx) {
     207                 :        1609 :         debug("switch to rooted");
     208                 :        1609 :         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                 :        1609 :         JS::Rooted<T> thing(cx, m_heap);
     213                 :             : 
     214                 :        1609 :         reset();
     215                 :        1609 :         root(cx, thing);
     216                 :        1609 :         g_assert(m_root);
     217                 :        1609 :     }
     218                 :             : 
     219                 :        1468 :     void switch_to_unrooted(JSContext* cx) {
     220                 :        1468 :         debug("switch to unrooted");
     221                 :        1468 :         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                 :        1468 :         JS::Rooted<T> thing(cx, *m_root);
     226                 :             : 
     227                 :        1468 :         reset();
     228                 :        1468 :         m_heap = thing;
     229                 :        1468 :         g_assert(!m_root);
     230                 :        1468 :     }
     231                 :             : 
     232                 :             :     /* Tracing makes no sense in the rooted case, because JS::PersistentRooted
     233                 :             :      * already takes care of that. */
     234                 :             :     void
     235                 :         176 :     trace(JSTracer   *tracer,
     236                 :             :           const char *name)
     237                 :             :     {
     238                 :         176 :         debug("trace()");
     239                 :         176 :         g_assert(!m_root);
     240                 :         176 :         JS::TraceEdge<T>(tracer, &m_heap, name);
     241                 :         176 :     }
     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                 :        1540 :     bool update_after_gc(JSTracer* trc) {
     247                 :        1540 :         debug("update_after_gc()");
     248                 :        1540 :         g_assert(!m_root);
     249                 :        1540 :         return GjsHeapOperation<T>::update_after_gc(trc, &m_heap);
     250                 :             :     }
     251                 :             : 
     252                 :       21115 :     [[nodiscard]] constexpr bool rooted() const { return m_root != nullptr; }
     253                 :             : };
     254                 :             : 
     255                 :             : #endif  // GJS_JSAPI_UTIL_ROOT_H_
        

Generated by: LCOV version 2.0-1