summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/Wrapped.h
blob: 904a9b3ac927e89c8ce4a6d8f744c87db2cc9e3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef builtin_temporal_Wrapped_h
#define builtin_temporal_Wrapped_h

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"

#include <type_traits>

#include "gc/Tracer.h"
#include "js/RootingAPI.h"
#include "vm/JSObject.h"
#include "vm/NativeObject.h"

namespace js::temporal {

/**
 * Type to represent possibly wrapped objects from a different compartment.
 *
 * This can be used to represent specific JSObject sub-classes in return types
 * without having to pass unwrapped objects around.
 */
template <class T>
class MOZ_STACK_CLASS Wrapped final {
  static_assert(std::is_pointer_v<T>);
  static_assert(std::is_convertible_v<T, NativeObject*>);

  using U = std::remove_pointer_t<T>;

  JSObject* ptr_ = nullptr;

 public:
  Wrapped() = default;

  MOZ_IMPLICIT Wrapped(decltype(nullptr)) : ptr_(nullptr) {}

  MOZ_IMPLICIT Wrapped(T ptr) : ptr_(ptr) {
    // No assertion needed when the object already has the correct type.
  }

  MOZ_IMPLICIT Wrapped(JSObject* ptr) : ptr_(ptr) {
    // Ensure the caller passed a valid pointer.
    MOZ_ASSERT_IF(ptr_, ptr_->canUnwrapAs<U>());
  }

  template <typename S>
  MOZ_IMPLICIT Wrapped(
      const JS::Rooted<S>& root,
      std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
      : Wrapped(root.get()) {}

  MOZ_IMPLICIT Wrapped(const JS::Rooted<JSObject*>& root)
      : Wrapped(root.get()) {}

  template <typename S>
  MOZ_IMPLICIT Wrapped(
      const JS::Handle<S>& root,
      std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
      : Wrapped(root.get()) {}

  MOZ_IMPLICIT Wrapped(const JS::Handle<JSObject*>& root)
      : Wrapped(root.get()) {}

  template <typename S>
  MOZ_IMPLICIT Wrapped(
      const JS::MutableHandle<S>& root,
      std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
      : Wrapped(root.get()) {}

  MOZ_IMPLICIT Wrapped(const JS::MutableHandle<JSObject*>& root)
      : Wrapped(root.get()) {}

  Wrapped& operator=(decltype(nullptr)) {
    ptr_ = nullptr;
    return *this;
  }

  Wrapped& operator=(T ptr) {
    ptr_ = ptr;
    return *this;
  }

  explicit operator bool() const { return !!ptr_; }

  JSObject* operator->() const { return ptr_; }

  JSObject& operator*() const { return *ptr_; }

  JSObject* get() const { return ptr_; }

  operator JSObject*() const { return get(); }

  auto address() const { return &ptr_; }

  U& unwrap() const {
    MOZ_ASSERT(ptr_);

    // Direct unwrap because the constructor already verified the object can be
    // unwrapped.
    //
    // We use JSObject::unwrapAs() instead of JSObject::maybeUnwrapIf(), because
    // this is an unrooted Wrapped, so hazard analysis will ensure that no
    // wrappers have been invalidated, because wrapper invalidation generally
    // only happens in the same case as GC.
    //
    // Rooted Wrapped are accessed through their WrappedPtrOperations
    // specialization, which uses JSObject::maybeUnwrapIf() to handle the
    // wrapper invalidation case correctly.
    return ptr_->unwrapAs<U>();
  }

  U* unwrapOrNull() const {
    // Direct unwrap because the constructor already verified the object can be
    // unwrapped.
    //
    // See Wrapped::unwrap() for why we don't call maybeUnwrapIf() here.
    return ptr_ ? &ptr_->unwrapAs<U>() : nullptr;
  }

  void trace(JSTracer* trc) { TraceNullableRoot(trc, &ptr_, "Wrapped::ptr_"); }
};

void ReportDeadWrapperOrAccessDenied(JSContext* cx, JSObject* obj);

} /* namespace js::temporal */

namespace js {
template <typename T, typename Container>
class WrappedPtrOperations<temporal::Wrapped<T>, Container> {
  using U = std::remove_pointer_t<T>;

  const auto& wrapped() const {
    return static_cast<const Container*>(this)->get();
  }

 public:
  explicit operator bool() const { return !!wrapped(); }

  JSObject* operator->() const { return wrapped().get(); }

  JSObject& operator*() const { return *wrapped().get(); }

  JS::Handle<JSObject*> object() const {
    return JS::Handle<JSObject*>::fromMarkedLocation(wrapped().address());
  }

  operator JS::Handle<JSObject*>() const { return object(); }

  [[nodiscard]] U* unwrap(JSContext* cx) const {
    JSObject* obj = wrapped().get();

    // Call JSObject::maybeUnwrapIf() instead of JSObject::unwrapAs() in case
    // |obj| is an invalidated wrapper.
    if (auto* unwrapped = obj->maybeUnwrapIf<U>()) {
      return unwrapped;
    }

    temporal::ReportDeadWrapperOrAccessDenied(cx, obj);
    return nullptr;
  }
};
}  // namespace js

#endif /* builtin_temporal_Wrapped_h */