/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/include/v8-sandbox.h
170 строк
7 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// Copyright 2024 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_SANDBOX_H_ #define INCLUDE_V8_SANDBOX_H_ #include <cstdint> #include "v8-internal.h" // NOLINT(build/include_directory) #include "v8config.h" // NOLINT(build/include_directory) namespace v8 { /** * A pointer tag used for wrapping and unwrapping `CppHeap` pointers as used * with JS API wrapper objects that rely on `v8::Object::Wrap()` and * `v8::Object::Unwrap()`. * * The CppHeapPointers use a range-based type checking scheme, where on access * to a pointer, the actual type of the pointer is checked to be within a * specified range of types. This allows supporting type hierarchies, where a * type check for a supertype must succeed for any subtype. * * The tag is currently in practice limited to 15 bits since it needs to fit * together with a marking bit into the unused parts of a pointer. */ enum class CppHeapPointerTag : uint16_t { kFirstTag = 0, kNullTag = 0, /** * The lower type ids are reserved for the embedder to assign. For that, the * main requirement is that all (transitive) child classes of a given parent * class have type ids in the same range, and that there are no unrelated * types in that range. For example, given the following type hierarchy: * * A F * / \ * B E * / \ * C D * * a potential type id assignment that satistifes these requirements is * {C: 0, D: 1, B: 2, A: 3, E: 4, F: 5}. With that, the type check for type A * would check for the range [0, 4], while the check for B would check range * [0, 2], and for F it would simply check [5, 5]. * * In addition, there is an option for performance tweaks: if the size of the * type range corresponding to a supertype is a power of two and starts at a * power of two (e.g. [0x100, 0x13f]), then the compiler can often optimize * the type check to use even fewer instructions (essentially replace a AND + * SUB with a single AND). */ kDefaultTag = 0x7000, kZappedEntryTag = 0x7ffd, kEvacuationEntryTag = 0x7ffe, kFreeEntryTag = 0x7fff, // The tags are limited to 15 bits, so the last tag is 0x7fff. kLastTag = 0x7fff, }; using CppHeapPointerTagRange = internal::TagRange<CppHeapPointerTag>; constexpr CppHeapPointerTagRange kAnyCppHeapPointer( CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kLastTag); /** * Hardware support for the V8 Sandbox. * * This is an experimental feature that may change or be removed without * further notice. Use at your own risk. */ class SandboxHardwareSupport { public: /** * Initialize sandbox hardware support. This needs to be called before * creating any thread that might access sandbox memory since it sets up * hardware permissions to the memory that will be inherited on clone. */ V8_EXPORT static void InitializeBeforeThreadCreation(); }; namespace internal { #ifdef V8_COMPRESS_POINTERS V8_INLINE static Address* GetCppHeapPointerTableBase(v8::Isolate* isolate) { Address addr = reinterpret_cast<Address>(isolate) + Internals::kIsolateCppHeapPointerTableOffset + Internals::kExternalEntityTableBasePointerOffset; return *reinterpret_cast<Address**>(addr); } #endif // V8_COMPRESS_POINTERS template <typename T> V8_INLINE static T* ReadCppHeapPointerField(v8::Isolate* isolate, Address heap_object_ptr, int offset, CppHeapPointerTagRange tag_range) { // This is a specialized version of the the CppHeapPointerTable accessors // which (1) allows the code to be inlined into the callers for performance // and (2) is optimized for code size as there are a huge number of callers // from auto-generated bindings code. #ifdef V8_COMPRESS_POINTERS const CppHeapPointerHandle handle = Internals::ReadRawField<CppHeapPointerHandle>(heap_object_ptr, offset); const uint32_t index = handle >> kExternalPointerIndexShift; const Address* table = GetCppHeapPointerTableBase(isolate); const std::atomic<Address>* ptr = reinterpret_cast<const std::atomic<Address>*>(&table[index]); Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed); // Note: the cast to uint32_t is important here. Otherwise, the uint16_t's // would be promoted to int in the range check below, which would result in // undefined behavior (signed integer underflow) if the actual value is less // than the lower bound. Then, the compiler would take advantage of the // undefined behavior and turn the range check into a simple // `actual_tag <= last_tag` comparison, which is incorrect. uint32_t actual_tag = static_cast<uint16_t>(entry); // The actual_tag is shifted to the left by one and contains the marking // bit in the LSB. To ignore that during the type check, simply add one to // the (shifted) range. constexpr int kTagShift = internal::kCppHeapPointerTagShift; uint32_t first_tag = static_cast<uint32_t>(tag_range.first) << kTagShift; uint32_t last_tag = (static_cast<uint32_t>(tag_range.last) << kTagShift) + 1; if (V8_LIKELY(actual_tag >= first_tag && actual_tag <= last_tag)) { entry = entry >> kCppHeapPointerPayloadShift; } else { // If the type check failed, we simply return nullptr here. That way: // 1. The null handle always results in nullptr being returned here, which // is a desired property. Otherwise, we would need an explicit check for // the null handle above, and therefore an additional branch. This // works because the 0th entry of the table always contains nullptr // tagged with the null tag (i.e. an all-zeros entry). As such, // regardless of whether the type check succeeds, the result will // always be nullptr. // 2. The returned pointer is guaranteed to crash even on platforms with // top byte ignore (TBI), such as Arm64. The alternative would be to // simply return the original entry with the left-shifted payload. // However, due to TBI, an access to that may not always result in a // crash (specifically, if the second most significant byte happens to // be zero). In addition, there shouldn't be a difference on Arm64 // between returning nullptr or the original entry, since it will // simply compile to a `csel x0, x8, xzr, lo` instead of a // `csel x0, x10, x8, lo` instruction. // 3. The machine code sequence ends up being pretty short, which is // important here as this code will be inlined into a lot of functions. entry = 0; } return reinterpret_cast<T*>(entry); #else // !V8_COMPRESS_POINTERS return reinterpret_cast<T*>( Internals::ReadRawField<Address>(heap_object_ptr, offset)); #endif // !V8_COMPRESS_POINTERS } // TODO(saelo): temporary workaround needed to introduce range-based type // checks for the external pointer table. See comment above // ExternalPointerCanBeEmpty(ExternalPointerTagRange) function for details. V8_INLINE static constexpr bool ExternalPointerCanBeEmpty( CppHeapPointerTagRange tag_range) { return true; } } // namespace internal } // namespace v8 #endif // INCLUDE_V8_SANDBOX_H_