// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef INCLUDE_V8_LOCAL_HANDLE_H_ #define INCLUDE_V8_LOCAL_HANDLE_H_ #include #include #include "v8-internal.h" // NOLINT(build/include_directory) namespace v8 { class Boolean; template class BasicTracedReference; class Context; class EscapableHandleScope; template class Eternal; template class FunctionCallbackInfo; class Isolate; template class MaybeLocal; template class NonCopyablePersistentTraits; class Object; template > class Persistent; template class PersistentBase; template class PersistentValueMapBase; template class PersistentValueVector; class Primitive; class Private; template class PropertyCallbackInfo; template class ReturnValue; class String; template class Traced; template class TracedReference; class TracedReferenceBase; class Utils; namespace internal { template class CustomArguments; } // namespace internal namespace api_internal { // Called when ToLocalChecked is called on an empty Local. V8_EXPORT void ToLocalEmpty(); } // namespace api_internal /** * A stack-allocated class that governs a number of local handles. * After a handle scope has been created, all local handles will be * allocated within that handle scope until either the handle scope is * deleted or another handle scope is created. If there is already a * handle scope and a new one is created, all allocations will take * place in the new handle scope until it is deleted. After that, * new handles will again be allocated in the original handle scope. * * After the handle scope of a local handle has been deleted the * garbage collector will no longer track the object stored in the * handle and may deallocate it. The behavior of accessing a handle * for which the handle scope has been deleted is undefined. */ class V8_EXPORT V8_NODISCARD HandleScope { public: explicit HandleScope(Isolate* isolate); ~HandleScope(); /** * Counts the number of allocated handles. */ static int NumberOfHandles(Isolate* isolate); V8_INLINE Isolate* GetIsolate() const { return reinterpret_cast(i_isolate_); } HandleScope(const HandleScope&) = delete; void operator=(const HandleScope&) = delete; protected: V8_INLINE HandleScope() = default; void Initialize(Isolate* isolate); static internal::Address* CreateHandle(internal::Isolate* i_isolate, internal::Address value); private: // Declaring operator new and delete as deleted is not spec compliant. // Therefore declare them private instead to disable dynamic alloc void* operator new(size_t size); void* operator new[](size_t size); void operator delete(void*, size_t); void operator delete[](void*, size_t); internal::Isolate* i_isolate_; internal::Address* prev_next_; internal::Address* prev_limit_; // Local::New uses CreateHandle with an Isolate* parameter. template friend class Local; // Object::GetInternalField and Context::GetEmbedderData use CreateHandle with // a HeapObject in their shortcuts. friend class Object; friend class Context; }; /** * An object reference managed by the v8 garbage collector. * * All objects returned from v8 have to be tracked by the garbage collector so * that it knows that the objects are still alive. Also, because the garbage * collector may move objects, it is unsafe to point directly to an object. * Instead, all objects are stored in handles which are known by the garbage * collector and updated whenever an object moves. Handles should always be * passed by value (except in cases like out-parameters) and they should never * be allocated on the heap. * * There are two types of handles: local and persistent handles. * * Local handles are light-weight and transient and typically used in local * operations. They are managed by HandleScopes. That means that a HandleScope * must exist on the stack when they are created and that they are only valid * inside of the HandleScope active during their creation. For passing a local * handle to an outer HandleScope, an EscapableHandleScope and its Escape() * method must be used. * * Persistent handles can be used when storing objects across several * independent operations and have to be explicitly deallocated when they're no * longer used. * * It is safe to extract the object stored in the handle by dereferencing the * handle (for instance, to extract the Object* from a Local); the value * will still be governed by a handle behind the scenes and the same rules apply * to these values as to their handles. */ template class Local { public: V8_INLINE Local() : val_(nullptr) {} template V8_INLINE Local(Local that) : val_(reinterpret_cast(*that)) { /** * This check fails when trying to convert between incompatible * handles. For example, converting from a Local to a * Local. */ static_assert(std::is_base_of::value, "type check"); } /** * Returns true if the handle is empty. */ V8_INLINE bool IsEmpty() const { return val_ == nullptr; } /** * Sets the handle to be empty. IsEmpty() will then return true. */ V8_INLINE void Clear() { val_ = nullptr; } V8_INLINE T* operator->() const { return val_; } V8_INLINE T* operator*() const { return val_; } /** * Checks whether two handles are the same. * Returns true if both are empty, or if the objects to which they refer * are identical. * * If both handles refer to JS objects, this is the same as strict equality. * For primitives, such as numbers or strings, a `false` return value does not * indicate that the values aren't equal in the JavaScript sense. * Use `Value::StrictEquals()` to check primitives for equality. */ template V8_INLINE bool operator==(const Local& that) const { internal::Address* a = reinterpret_cast(this->val_); internal::Address* b = reinterpret_cast(that.val_); if (a == nullptr) return b == nullptr; if (b == nullptr) return false; return *a == *b; } template V8_INLINE bool operator==(const PersistentBase& that) const { internal::Address* a = reinterpret_cast(this->val_); internal::Address* b = reinterpret_cast(that.val_); if (a == nullptr) return b == nullptr; if (b == nullptr) return false; return *a == *b; } /** * Checks whether two handles are different. * Returns true if only one of the handles is empty, or if * the objects to which they refer are different. * * If both handles refer to JS objects, this is the same as strict * non-equality. For primitives, such as numbers or strings, a `true` return * value does not indicate that the values aren't equal in the JavaScript * sense. Use `Value::StrictEquals()` to check primitives for equality. */ template V8_INLINE bool operator!=(const Local& that) const { return !operator==(that); } template V8_INLINE bool operator!=(const Persistent& that) const { return !operator==(that); } /** * Cast a handle to a subclass, e.g. Local to Local. * This is only valid if the handle actually refers to a value of the * target type. */ template V8_INLINE static Local Cast(Local that) { #ifdef V8_ENABLE_CHECKS // If we're going to perform the type check then we have to check // that the handle isn't empty before doing the checked cast. if (that.IsEmpty()) return Local(); #endif return Local(T::Cast(*that)); } /** * Calling this is equivalent to Local::Cast(). * In particular, this is only valid if the handle actually refers to a value * of the target type. */ template V8_INLINE Local As() const { return Local::Cast(*this); } /** * Create a local handle for the content of another handle. * The referee is kept alive by the local handle even when * the original handle is destroyed/disposed. */ V8_INLINE static Local New(Isolate* isolate, Local that) { return New(isolate, that.val_); } V8_INLINE static Local New(Isolate* isolate, const PersistentBase& that) { return New(isolate, that.val_); } V8_INLINE static Local New(Isolate* isolate, const BasicTracedReference& that) { return New(isolate, *that); } private: friend class TracedReferenceBase; friend class Utils; template friend class Eternal; template friend class PersistentBase; template friend class Persistent; template friend class Local; template friend class MaybeLocal; template friend class FunctionCallbackInfo; template friend class PropertyCallbackInfo; friend class String; friend class Object; friend class Context; friend class Isolate; friend class Private; template friend class internal::CustomArguments; friend Local Undefined(Isolate* isolate); friend Local Null(Isolate* isolate); friend Local True(Isolate* isolate); friend Local False(Isolate* isolate); friend class HandleScope; friend class EscapableHandleScope; template friend class PersistentValueMapBase; template friend class PersistentValueVector; template friend class ReturnValue; template friend class Traced; template friend class BasicTracedReference; template friend class TracedReference; explicit V8_INLINE Local(T* that) : val_(that) {} V8_INLINE static Local New(Isolate* isolate, T* that) { if (that == nullptr) return Local(); T* that_ptr = that; internal::Address* p = reinterpret_cast(that_ptr); return Local(reinterpret_cast(HandleScope::CreateHandle( reinterpret_cast(isolate), *p))); } T* val_; }; #if !defined(V8_IMMINENT_DEPRECATION_WARNINGS) // Handle is an alias for Local for historical reasons. template using Handle = Local; #endif /** * A MaybeLocal<> is a wrapper around Local<> that enforces a check whether * the Local<> is empty before it can be used. * * If an API method returns a MaybeLocal<>, the API method can potentially fail * either because an exception is thrown, or because an exception is pending, * e.g. because a previous API call threw an exception that hasn't been caught * yet, or because a TerminateExecution exception was thrown. In that case, an * empty MaybeLocal is returned. */ template class MaybeLocal { public: V8_INLINE MaybeLocal() : val_(nullptr) {} template V8_INLINE MaybeLocal(Local that) : val_(reinterpret_cast(*that)) { static_assert(std::is_base_of::value, "type check"); } V8_INLINE bool IsEmpty() const { return val_ == nullptr; } /** * Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, * |false| is returned and |out| is assigned with nullptr. */ template V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local* out) const { out->val_ = IsEmpty() ? nullptr : this->val_; return !IsEmpty(); } /** * Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, * V8 will crash the process. */ V8_INLINE Local ToLocalChecked() { if (V8_UNLIKELY(val_ == nullptr)) api_internal::ToLocalEmpty(); return Local(val_); } /** * Converts this MaybeLocal<> to a Local<>, using a default value if this * MaybeLocal<> is empty. */ template V8_INLINE Local FromMaybe(Local default_value) const { return IsEmpty() ? default_value : Local(val_); } private: T* val_; }; /** * A HandleScope which first allocates a handle in the current scope * which will be later filled with the escape value. */ class V8_EXPORT V8_NODISCARD EscapableHandleScope : public HandleScope { public: explicit EscapableHandleScope(Isolate* isolate); V8_INLINE ~EscapableHandleScope() = default; /** * Pushes the value into the previous scope and returns a handle to it. * Cannot be called twice. */ template V8_INLINE Local Escape(Local value) { internal::Address* slot = Escape(reinterpret_cast(*value)); return Local(reinterpret_cast(slot)); } template V8_INLINE MaybeLocal EscapeMaybe(MaybeLocal value) { return Escape(value.FromMaybe(Local())); } EscapableHandleScope(const EscapableHandleScope&) = delete; void operator=(const EscapableHandleScope&) = delete; private: // Declaring operator new and delete as deleted is not spec compliant. // Therefore declare them private instead to disable dynamic alloc void* operator new(size_t size); void* operator new[](size_t size); void operator delete(void*, size_t); void operator delete[](void*, size_t); internal::Address* Escape(internal::Address* escape_value); internal::Address* escape_slot_; }; /** * A SealHandleScope acts like a handle scope in which no handle allocations * are allowed. It can be useful for debugging handle leaks. * Handles can be allocated within inner normal HandleScopes. */ class V8_EXPORT V8_NODISCARD SealHandleScope { public: explicit SealHandleScope(Isolate* isolate); ~SealHandleScope(); SealHandleScope(const SealHandleScope&) = delete; void operator=(const SealHandleScope&) = delete; private: // Declaring operator new and delete as deleted is not spec compliant. // Therefore declare them private instead to disable dynamic alloc void* operator new(size_t size); void* operator new[](size_t size); void operator delete(void*, size_t); void operator delete[](void*, size_t); internal::Isolate* const i_isolate_; internal::Address* prev_limit_; int prev_sealed_level_; }; } // namespace v8 #endif // INCLUDE_V8_LOCAL_HANDLE_H_