Loading...
Searching...
No Matches
v8-sandbox.h
Go to the documentation of this file.
1// Copyright 2024 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef INCLUDE_V8_SANDBOX_H_
6#define INCLUDE_V8_SANDBOX_H_
7
8#include <cstdint>
9
10#include "v8-internal.h" // NOLINT(build/include_directory)
11#include "v8config.h" // NOLINT(build/include_directory)
12
13namespace v8 {
14
28enum class CppHeapPointerTag : uint16_t {
29 kFirstTag = 0,
30 kNullTag = 0,
31
56 kDefaultTag = 0x7000,
57
58 kZappedEntryTag = 0x7ffd,
59 kEvacuationEntryTag = 0x7ffe,
60 kFreeEntryTag = 0x7fff,
61 // The tags are limited to 15 bits, so the last tag is 0x7fff.
62 kLastTag = 0x7fff,
63};
64
66
69
77 public:
84};
85
86namespace internal {
87
88#ifdef V8_COMPRESS_POINTERS
89V8_INLINE static Address* GetCppHeapPointerTableBase(v8::Isolate* isolate) {
90 Address addr = reinterpret_cast<Address>(isolate) +
91 Internals::kIsolateCppHeapPointerTableOffset +
93 return *reinterpret_cast<Address**>(addr);
94}
95#endif // V8_COMPRESS_POINTERS
96
97template <typename T>
98V8_INLINE static T* ReadCppHeapPointerField(v8::Isolate* isolate,
99 Address heap_object_ptr, int offset,
100 CppHeapPointerTagRange tag_range) {
101 // This is a specialized version of the the CppHeapPointerTable accessors
102 // which (1) allows the code to be inlined into the callers for performance
103 // and (2) is optimized for code size as there are a huge number of callers
104 // from auto-generated bindings code.
105
106#ifdef V8_COMPRESS_POINTERS
107 const CppHeapPointerHandle handle =
108 Internals::ReadRawField<CppHeapPointerHandle>(heap_object_ptr, offset);
109 const uint32_t index = handle >> kExternalPointerIndexShift;
110 const Address* table = GetCppHeapPointerTableBase(isolate);
111 const std::atomic<Address>* ptr =
112 reinterpret_cast<const std::atomic<Address>*>(&table[index]);
113 Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed);
114
115 // Note: the cast to uint32_t is important here. Otherwise, the uint16_t's
116 // would be promoted to int in the range check below, which would result in
117 // undefined behavior (signed integer underflow) if the actual value is less
118 // than the lower bound. Then, the compiler would take advantage of the
119 // undefined behavior and turn the range check into a simple
120 // `actual_tag <= last_tag` comparison, which is incorrect.
121 uint32_t actual_tag = static_cast<uint16_t>(entry);
122 // The actual_tag is shifted to the left by one and contains the marking
123 // bit in the LSB. To ignore that during the type check, simply add one to
124 // the (shifted) range.
125 constexpr int kTagShift = internal::kCppHeapPointerTagShift;
126 uint32_t first_tag = static_cast<uint32_t>(tag_range.first) << kTagShift;
127 uint32_t last_tag = (static_cast<uint32_t>(tag_range.last) << kTagShift) + 1;
128 if (V8_LIKELY(actual_tag >= first_tag && actual_tag <= last_tag)) {
129 entry = entry >> kCppHeapPointerPayloadShift;
130 } else {
131 // If the type check failed, we simply return nullptr here. That way:
132 // 1. The null handle always results in nullptr being returned here, which
133 // is a desired property. Otherwise, we would need an explicit check for
134 // the null handle above, and therefore an additional branch. This
135 // works because the 0th entry of the table always contains nullptr
136 // tagged with the null tag (i.e. an all-zeros entry). As such,
137 // regardless of whether the type check succeeds, the result will
138 // always be nullptr.
139 // 2. The returned pointer is guaranteed to crash even on platforms with
140 // top byte ignore (TBI), such as Arm64. The alternative would be to
141 // simply return the original entry with the left-shifted payload.
142 // However, due to TBI, an access to that may not always result in a
143 // crash (specifically, if the second most significant byte happens to
144 // be zero). In addition, there shouldn't be a difference on Arm64
145 // between returning nullptr or the original entry, since it will
146 // simply compile to a `csel x0, x8, xzr, lo` instead of a
147 // `csel x0, x10, x8, lo` instruction.
148 // 3. The machine code sequence ends up being pretty short, which is
149 // important here as this code will be inlined into a lot of functions.
150 entry = 0;
151 }
152 return reinterpret_cast<T*>(entry);
153#else // !V8_COMPRESS_POINTERS
154 return reinterpret_cast<T*>(
155 Internals::ReadRawField<Address>(heap_object_ptr, offset));
156#endif // !V8_COMPRESS_POINTERS
157}
158
159// TODO(saelo): temporary workaround needed to introduce range-based type
160// checks for the external pointer table. See comment above
161// ExternalPointerCanBeEmpty(ExternalPointerTagRange) function for details.
162V8_INLINE static constexpr bool ExternalPointerCanBeEmpty(
163 CppHeapPointerTagRange tag_range) {
164 return true;
165}
166
167} // namespace internal
168} // namespace v8
169
170#endif // INCLUDE_V8_SANDBOX_H_
Definition: v8-isolate.h:291
Definition: v8-sandbox.h:76
static void InitializeBeforeThreadCreation()
static const int kExternalPointerTableBasePointerOffset
Definition: v8-internal.h:976
uint32_t CppHeapPointerHandle
Definition: v8-internal.h:375
constexpr uint64_t kCppHeapPointerPayloadShift
Definition: v8-internal.h:392
constexpr uint64_t kCppHeapPointerTagShift
Definition: v8-internal.h:391
uintptr_t Address
Definition: v8-internal.h:38
Definition: libplatform.h:15
internal::TagRange< CppHeapPointerTag > CppHeapPointerTagRange
Definition: v8-sandbox.h:65
CppHeapPointerTag
Definition: v8-sandbox.h:28
constexpr CppHeapPointerTagRange kAnyCppHeapPointer(CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kLastTag)
Definition: v8-internal.h:484
#define V8_EXPORT
Definition: v8config.h:854
#define V8_INLINE
Definition: v8config.h:508
#define V8_LIKELY(condition)
Definition: v8config.h:668