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
32 // Subtypes of v8::Object::Wrappable [0x0001 .. 0x6fff]
35
60
61 // V8-internal Oilpan objects that inherit from v8::Object::Wrappable.
63 // Kept temporarily for backwards compatibility with Chromium's
64 // wrapper_type_info.h across the V8 roll.
67
69
70 // Non-v8::Object::Wrappable CppHeap objects [0x7000 .. 0x7ffc]
71 kFirstNonWrappableTag = 0x7000,
72
74 // The cppgc object holding the resource of an external string. Exposed here
75 // because the inline fast paths of v8::String::GetExternalStringResource*()
76 // read it; must match the V8-internal definition of the tag.
79
82
84
85#if !V8_ENABLE_SANDBOX
86 // Embedders that use the sandbox should use specific tags for each type.
88#endif // !V8_ENABLE_SANDBOX
89
90 kZappedEntryTag = 0x7ffd,
91 kEvacuationEntryTag = 0x7ffe,
92 kFreeEntryTag = 0x7fff,
93 // The tags are limited to 15 bits, so the last tag is 0x7fff.
94 kLastTag = 0x7fff,
95};
96
98
101
102// All tags that are used with v8::Object::Wrappable have to be within this
103// tag range. The reason is that in some cases, an APIWrapper object has to be
104// unwrapped to access the v8::Object::Wrappable base class, e.g. to get type
105// information.
109
110// The tag range that embedders can use for their own types that inherit from
111// v8::Object::Wrappable.
115
116// The tag range for all non-v8::Object::Wrappable CppHeap objects, both
117// V8-internal and embedder-owned.
121
122// The tag range that embedders can use for their own types that do not inherit
123// from v8::Object::Wrappable.
127
130
138 public:
145};
146
147namespace internal {
148
149#ifdef V8_COMPRESS_POINTERS
150V8_INLINE static Address* GetCppHeapPointerTableBase(v8::Isolate* isolate) {
151 Address addr = reinterpret_cast<Address>(isolate) +
152 Internals::kIsolateCppHeapPointerTableOffset +
154 return *reinterpret_cast<Address**>(addr);
155}
156#endif // V8_COMPRESS_POINTERS
157
158template <typename T>
159V8_INLINE static T* ReadCppHeapPointerField(v8::Isolate* isolate,
160 Address heap_object_ptr, int offset,
161 CppHeapPointerTagRange tag_range) {
162 // This is a specialized version of the CppHeapPointerTable accessors
163 // which (1) allows the code to be inlined into the callers for performance
164 // and (2) is optimized for code size as there are a huge number of callers
165 // from auto-generated bindings code.
166
167#ifdef V8_COMPRESS_POINTERS
168 const CppHeapPointerHandle handle =
169 Internals::ReadRawField<CppHeapPointerHandle>(heap_object_ptr, offset);
170 const uint32_t index = handle >> kExternalPointerIndexShift;
171 const Address* table = GetCppHeapPointerTableBase(isolate);
172 const std::atomic<Address>* ptr =
173 reinterpret_cast<const std::atomic<Address>*>(&table[index]);
174 Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed);
175
176 // Note: the cast to uint32_t is important here. Otherwise, the uint16_t's
177 // would be promoted to int in the range check below, which would result in
178 // undefined behavior (signed integer underflow) if the actual value is less
179 // than the lower bound. Then, the compiler would take advantage of the
180 // undefined behavior and turn the range check into a simple
181 // `actual_tag <= last_tag` comparison, which is incorrect.
182 uint32_t actual_tag = static_cast<uint16_t>(entry);
183 // The actual_tag is shifted to the left by one and contains the marking
184 // bit in the LSB. To ignore that during the type check, simply add one to
185 // the (shifted) range.
186 constexpr int kTagShift = internal::kCppHeapPointerTagShift;
187 uint32_t first_tag = static_cast<uint32_t>(tag_range.first) << kTagShift;
188 uint32_t last_tag = (static_cast<uint32_t>(tag_range.last) << kTagShift) + 1;
189 // Avoid DCE of the entry logic using volatile.
190 volatile Address safe_entry;
191 if (actual_tag >= first_tag && actual_tag <= last_tag) [[likely]] {
192 safe_entry = entry >> kCppHeapPointerPayloadShift;
193 } else {
194 // If the type check failed, we simply return nullptr here. That way:
195 // 1. The null handle always results in nullptr being returned here, which
196 // is a desired property. Otherwise, we would need an explicit check for
197 // the null handle above, and therefore an additional branch. This
198 // works because the 0th entry of the table always contains nullptr
199 // tagged with the null tag (i.e. an all-zeros entry). As such,
200 // regardless of whether the type check succeeds, the result will
201 // always be nullptr.
202 // 2. The returned pointer is guaranteed to crash even on platforms with
203 // top byte ignore (TBI), such as Arm64. The alternative would be to
204 // simply return the original entry with the left-shifted payload.
205 // However, due to TBI, an access to that may not always result in a
206 // crash (specifically, if the second most significant byte happens to
207 // be zero). In addition, there shouldn't be a difference on Arm64
208 // between returning nullptr or the original entry, since it will
209 // simply compile to a `csel x0, x8, xzr, lo` instead of a
210 // `csel x0, x10, x8, lo` instruction.
211 // 3. The machine code sequence ends up being pretty short, which is
212 // important here as this code will be inlined into a lot of functions.
213 safe_entry = 0;
214 }
215 return reinterpret_cast<T*>(safe_entry);
216#else // !V8_COMPRESS_POINTERS
217 return reinterpret_cast<T*>(
218 Internals::ReadRawField<Address>(heap_object_ptr, offset));
219#endif // !V8_COMPRESS_POINTERS
220}
221
222// TODO(saelo): temporary workaround needed to introduce range-based type
223// checks for the external pointer table. See comment above
224// ExternalPointerCanBeEmpty(ExternalPointerTagRange) function for details.
225V8_INLINE static constexpr bool ExternalPointerCanBeEmpty(
226 CppHeapPointerTagRange tag_range) {
227 return true;
228}
229
230} // namespace internal
231} // namespace v8
232
233#endif // INCLUDE_V8_SANDBOX_H_
Definition: v8-isolate.h:305
Definition: v8-sandbox.h:137
static void InitializeBeforeThreadCreation()
static const int kExternalEntityTableBasePointerOffset
Definition: v8-internal.h:1028
uint32_t CppHeapPointerHandle
Definition: v8-internal.h:421
constexpr uint64_t kCppHeapPointerPayloadShift
Definition: v8-internal.h:438
constexpr uint64_t kCppHeapPointerTagShift
Definition: v8-internal.h:437
uintptr_t Address
Definition: v8-internal.h:38
Definition: libplatform.h:15
constexpr CppHeapPointerTagRange kAnyCppHeapPointer(CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kZappedEntryTag)
internal::TagRange< CppHeapPointerTag > CppHeapPointerTagRange
Definition: v8-sandbox.h:97
constexpr CppHeapPointerTagRange kEmbedderNonWrappableTagRange(CppHeapPointerTag::kFirstEmbedderNonWrappableTag, CppHeapPointerTag::kLastEmbedderNonWrappableTag)
constexpr CppHeapPointerTagRange kNonWrappableTagRange(CppHeapPointerTag::kFirstNonWrappableTag, CppHeapPointerTag::kLastNonWrappableTag)
constexpr CppHeapPointerTagRange kEmbedderWrappableTagRange(CppHeapPointerTag::kFirstEmbedderWrappableTag, CppHeapPointerTag::kLastEmbedderWrappableTag)
CppHeapPointerTag
Definition: v8-sandbox.h:28
constexpr CppHeapPointerTagRange kObjectWrappableTagRange(CppHeapPointerTag::kFirstObjectWrappableTag, CppHeapPointerTag::kLastObjectWrappableTag)
Definition: v8-internal.h:536
constexpr bool Contains(Tag tag) const
Definition: v8-internal.h:571
#define V8_EXPORT
Definition: v8config.h:875
#define V8_INLINE
Definition: v8config.h:519