diff options
Diffstat (limited to 'js/src/builtin/temporal/Wrapped.h')
-rw-r--r-- | js/src/builtin/temporal/Wrapped.h | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/Wrapped.h b/js/src/builtin/temporal/Wrapped.h new file mode 100644 index 0000000000..904a9b3ac9 --- /dev/null +++ b/js/src/builtin/temporal/Wrapped.h @@ -0,0 +1,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 */ |