v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
external-pointer-table-inl.h
Go to the documentation of this file.
1// Copyright 2021 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 V8_SANDBOX_EXTERNAL_POINTER_TABLE_INL_H_
6#define V8_SANDBOX_EXTERNAL_POINTER_TABLE_INL_H_
7
9// Include the non-inl header before the rest of the headers.
10
13
14#ifdef V8_COMPRESS_POINTERS
15
16namespace v8 {
17namespace internal {
18
19void ExternalPointerTableEntry::MakeExternalPointerEntry(Address value,
21 bool mark_as_alive) {
22 // The 2nd most significant byte must be empty as we store the tag in int.
26
27 Payload new_payload(value, tag);
28 if (V8_UNLIKELY(mark_as_alive)) {
29 new_payload.SetMarkBit();
30 }
31 payload_.store(new_payload, std::memory_order_relaxed);
32 MaybeUpdateRawPointerForLSan(value);
33}
34
35Address ExternalPointerTableEntry::GetExternalPointer(
36 ExternalPointerTagRange tag_range) const {
37 auto payload = payload_.load(std::memory_order_relaxed);
38 DCHECK(payload.ContainsPointer());
39 return payload.Untag(tag_range);
40}
41
42void ExternalPointerTableEntry::SetExternalPointer(Address value,
44 // The 2nd most significant byte must be empty as we store the tag in int.
46 DCHECK(payload_.load(std::memory_order_relaxed).ContainsPointer());
47
48 Payload new_payload(value, tag);
49 // Writing an entry currently also marks it as alive. In the future, we might
50 // want to drop this and instead use write barriers where necessary.
51 new_payload.SetMarkBit();
52 payload_.store(new_payload, std::memory_order_relaxed);
53 MaybeUpdateRawPointerForLSan(value);
54}
55
56bool ExternalPointerTableEntry::HasExternalPointer(
57 ExternalPointerTagRange tag_range) const {
58 auto payload = payload_.load(std::memory_order_relaxed);
59 if (!payload.ContainsPointer()) return false;
60 return payload.IsTaggedWithTagIn(tag_range);
61}
62
63Address ExternalPointerTableEntry::ExchangeExternalPointer(
64 Address value, ExternalPointerTag tag) {
65 // The 2nd most significant byte must be empty as we store the tag in int.
67
68 Payload new_payload(value, tag);
69 // Writing an entry currently also marks it as alive. In the future, we might
70 // want to drop this and instead use write barriers where necessary.
71 new_payload.SetMarkBit();
72 Payload old_payload =
73 payload_.exchange(new_payload, std::memory_order_relaxed);
74 DCHECK(old_payload.ContainsPointer());
75 MaybeUpdateRawPointerForLSan(value);
76 return old_payload.Untag(tag);
77}
78
79ExternalPointerTag ExternalPointerTableEntry::GetExternalPointerTag() const {
80 auto payload = payload_.load(std::memory_order_relaxed);
81 DCHECK(payload.ContainsPointer());
82 return payload.ExtractTag();
83}
84
85Address ExternalPointerTableEntry::ExtractManagedResourceOrNull() const {
86 auto payload = payload_.load(std::memory_order_relaxed);
87 ExternalPointerTag tag = payload.ExtractTag();
89 return payload.Untag(tag);
90 }
91 return kNullAddress;
92}
93
94void ExternalPointerTableEntry::MakeZappedEntry() {
95 Payload new_payload(kNullAddress, kExternalPointerZappedEntryTag);
96 payload_.store(new_payload, std::memory_order_relaxed);
97}
98
99void ExternalPointerTableEntry::MakeFreelistEntry(uint32_t next_entry_index) {
100 // The next freelist entry is stored in the lower bits of the entry.
101 static_assert(kMaxExternalPointers <= std::numeric_limits<uint32_t>::max());
102 Payload new_payload(next_entry_index, kExternalPointerFreeEntryTag);
103 payload_.store(new_payload, std::memory_order_relaxed);
104}
105
106uint32_t ExternalPointerTableEntry::GetNextFreelistEntryIndex() const {
107 auto payload = payload_.load(std::memory_order_relaxed);
108 return payload.ExtractFreelistLink();
109}
110
111void ExternalPointerTableEntry::Mark() {
112 auto old_payload = payload_.load(std::memory_order_relaxed);
113 DCHECK(old_payload.ContainsPointer());
114
115 auto new_payload = old_payload;
116 new_payload.SetMarkBit();
117
118 // We don't need to perform the CAS in a loop: if the new value is not equal
119 // to the old value, then the mutator must've just written a new value into
120 // the entry. This in turn must've set the marking bit already (see e.g.
121 // SetExternalPointer), so we don't need to do it again.
122 bool success = payload_.compare_exchange_strong(old_payload, new_payload,
123 std::memory_order_relaxed);
124 DCHECK(success || old_payload.HasMarkBitSet());
125 USE(success);
126}
127
128void ExternalPointerTableEntry::MakeEvacuationEntry(Address handle_location) {
129 Payload new_payload(handle_location, kExternalPointerEvacuationEntryTag);
130 payload_.store(new_payload, std::memory_order_relaxed);
131}
132
133bool ExternalPointerTableEntry::HasEvacuationEntry() const {
134 auto payload = payload_.load(std::memory_order_relaxed);
135 return payload.ContainsEvacuationEntry();
136}
137
138void ExternalPointerTableEntry::Evacuate(ExternalPointerTableEntry& dest,
139 EvacuateMarkMode mode) {
140 auto payload = payload_.load(std::memory_order_relaxed);
141 // We expect to only evacuate entries containing external pointers.
142 DCHECK(payload.ContainsPointer());
143
144 switch (mode) {
145 case EvacuateMarkMode::kTransferMark:
146 break;
147 case EvacuateMarkMode::kLeaveUnmarked:
148 DCHECK(!payload.HasMarkBitSet());
149 break;
150 case EvacuateMarkMode::kClearMark:
151 DCHECK(payload.HasMarkBitSet());
152 payload.ClearMarkBit();
153 break;
154 }
155
156 dest.payload_.store(payload, std::memory_order_relaxed);
157#if defined(LEAK_SANITIZER)
158 dest.raw_pointer_for_lsan_ = raw_pointer_for_lsan_;
159#endif // LEAK_SANITIZER
160
161 // The destination entry takes ownership of the pointer.
162 MakeZappedEntry();
163}
164
165Address ExternalPointerTable::Get(ExternalPointerHandle handle,
166 ExternalPointerTagRange tag_range) const {
167 uint32_t index = HandleToIndex(handle);
168 DCHECK(index == 0 || at(index).HasExternalPointer(tag_range));
169 return at(index).GetExternalPointer(tag_range);
170}
171
172void ExternalPointerTable::Set(ExternalPointerHandle handle, Address value,
173 ExternalPointerTag tag) {
175 uint32_t index = HandleToIndex(handle);
176 // TODO(saelo): This works for now, but once we actually free the external
177 // object here, this will probably become awkward: it's likely not intuitive
178 // that a set_foo() call on some object causes another object to be freed.
179 // Probably at that point we should instead just forbid re-setting the
180 // external pointers if they are managed (via a DCHECK).
181 FreeManagedResourceIfPresent(index);
182 TakeOwnershipOfManagedResourceIfNecessary(value, handle, tag);
183 at(index).SetExternalPointer(value, tag);
184}
185
186Address ExternalPointerTable::Exchange(ExternalPointerHandle handle,
187 Address value, ExternalPointerTag tag) {
190 uint32_t index = HandleToIndex(handle);
191 return at(index).ExchangeExternalPointer(value, tag);
192}
193
194ExternalPointerTag ExternalPointerTable::GetTag(
196 uint32_t index = HandleToIndex(handle);
197 return at(index).GetExternalPointerTag();
198}
199
200void ExternalPointerTable::Zap(ExternalPointerHandle handle) {
201 // Zapping the null entry is a nop. This is useful as we reset the handle of
202 // managed resources to the kNullExternalPointerHandle when the entry is
203 // deleted. See SweepAndCompact.
204 if (handle == kNullExternalPointerHandle) return;
205 uint32_t index = HandleToIndex(handle);
206 at(index).MakeZappedEntry();
207}
208
209ExternalPointerHandle ExternalPointerTable::AllocateAndInitializeEntry(
210 Space* space, Address initial_value, ExternalPointerTag tag) {
211 DCHECK(space->BelongsTo(this));
212 uint32_t index = AllocateEntry(space);
213 at(index).MakeExternalPointerEntry(initial_value, tag,
214 space->allocate_black());
215 ExternalPointerHandle handle = IndexToHandle(index);
216 TakeOwnershipOfManagedResourceIfNecessary(initial_value, handle, tag);
217 return handle;
218}
219
220void ExternalPointerTable::Mark(Space* space, ExternalPointerHandle handle,
221 Address handle_location) {
222 DCHECK(space->BelongsTo(this));
223
224 // The handle_location must always contain the given handle. Except if the
225 // slot is lazily-initialized. In that case, the handle may transition from
226 // the null handle to a valid handle. However, in that case the
227 // newly-allocated entry will already have been marked as alive during
228 // allocation, and so we don't need to do anything here.
229#ifdef DEBUG
231 reinterpret_cast<ExternalPointerHandle*>(handle_location));
232 DCHECK(handle == kNullExternalPointerHandle || handle == current_handle);
233#endif
234
235 // If the handle is null, it doesn't have an EPT entry; no mark is needed.
236 if (handle == kNullExternalPointerHandle) return;
237
238 uint32_t index = HandleToIndex(handle);
239 DCHECK(space->Contains(index));
240
241 // If the table is being compacted and the entry is inside the evacuation
242 // area, then allocate and set up an evacuation entry for it.
243 MaybeCreateEvacuationEntry(space, index, handle_location);
244
245 // Even if the entry is marked for evacuation, it still needs to be marked as
246 // alive as it may be visited during sweeping before being evacuation.
247 at(index).Mark();
248}
249
250void ExternalPointerTable::Evacuate(Space* from_space, Space* to_space,
252 Address handle_location,
253 EvacuateMarkMode mode) {
254 DCHECK(from_space->BelongsTo(this));
255 DCHECK(to_space->BelongsTo(this));
256
257 CHECK(IsValidHandle(handle));
258
259 auto handle_ptr = reinterpret_cast<ExternalPointerHandle*>(handle_location);
260
261#ifdef DEBUG
262 // Unlike Mark(), we require that the mutator is stopped, so we can simply
263 // verify that the location stores the handle with a non-atomic load.
264 DCHECK_EQ(handle, *handle_ptr);
265#endif
266
267 // If the handle is null, it doesn't have an EPT entry; no evacuation is
268 // needed.
269 if (handle == kNullExternalPointerHandle) return;
270
271 uint32_t from_index = HandleToIndex(handle);
272 DCHECK(from_space->Contains(from_index));
273 uint32_t to_index = AllocateEntry(to_space);
274
275 at(from_index).Evacuate(at(to_index), mode);
276 ExternalPointerHandle new_handle = IndexToHandle(to_index);
277
278 if (Address addr = at(to_index).ExtractManagedResourceOrNull()) {
279 ManagedResource* resource = reinterpret_cast<ManagedResource*>(addr);
280 DCHECK_EQ(resource->ept_entry_, handle);
281 resource->ept_entry_ = new_handle;
282 }
283
284 // Update slot to point to new handle.
285 base::AsAtomic32::Relaxed_Store(handle_ptr, new_handle);
286}
287
288// static
289bool ExternalPointerTable::IsValidHandle(ExternalPointerHandle handle) {
290 uint32_t index = handle >> kExternalPointerIndexShift;
291 return handle == index << kExternalPointerIndexShift;
292}
293
294// static
295uint32_t ExternalPointerTable::HandleToIndex(ExternalPointerHandle handle) {
296 DCHECK(IsValidHandle(handle));
297 uint32_t index = handle >> kExternalPointerIndexShift;
298#if defined(LEAK_SANITIZER)
299 // When LSan is active, we use "fat" entries that also store the raw pointer
300 // to that LSan can find live references. However, we do this transparently:
301 // we simply multiply the handle by two so that `(handle >> index_shift) * 8`
302 // still produces the correct offset of the entry in the table. However, this
303 // is not secure as an attacker could reference the raw pointer instead of
304 // the encoded pointer in an entry, thereby bypassing the type checks. As
305 // such, this mode must only be used in testing environments. Alternatively,
306 // all places that access external pointer table entries must be made aware
307 // that the entries are 16 bytes large when LSan is active.
308 index /= 2;
309#endif // LEAK_SANITIZER
311 return index;
312}
313
314// static
315ExternalPointerHandle ExternalPointerTable::IndexToHandle(uint32_t index) {
317 ExternalPointerHandle handle = index << kExternalPointerIndexShift;
318#if defined(LEAK_SANITIZER)
319 handle *= 2;
320#endif // LEAK_SANITIZER
322 return handle;
323}
324
325bool ExternalPointerTable::Contains(Space* space,
327 DCHECK(space->BelongsTo(this));
328 return space->Contains(HandleToIndex(handle));
329}
330
331void ExternalPointerTable::Space::NotifyExternalPointerFieldInvalidated(
332 Address field_address, ExternalPointerTagRange tag_range) {
333 // We do not currently support invalidating fields containing managed
334 // external pointers. If this is ever needed, we would probably need to free
335 // the managed object here as we may otherwise fail to do so during sweeping.
337#ifdef DEBUG
339 reinterpret_cast<ExternalPointerHandle*>(field_address));
340 DCHECK(Contains(HandleToIndex(handle)));
341#endif
342 AddInvalidatedField(field_address);
343}
344
345void ExternalPointerTable::ManagedResource::ZapExternalPointerTableEntry() {
346 if (owning_table_) {
347 owning_table_->Zap(ept_entry_);
348 }
349 ept_entry_ = kNullExternalPointerHandle;
350}
351
352void ExternalPointerTable::TakeOwnershipOfManagedResourceIfNecessary(
354 if (IsManagedExternalPointerType(tag) && value != kNullAddress) {
355 ManagedResource* resource = reinterpret_cast<ManagedResource*>(value);
356 DCHECK_EQ(resource->ept_entry_, kNullExternalPointerHandle);
357 resource->owning_table_ = this;
358 resource->ept_entry_ = handle;
359 }
360}
361
362void ExternalPointerTable::FreeManagedResourceIfPresent(uint32_t entry_index) {
363 // In the future, this would be where we actually delete the external
364 // resource. Currently, the deletion still happens elsewhere, and so here we
365 // instead set the resource's handle to the null handle so that the resource
366 // does not attempt to zap its entry when it is eventually destroyed.
367 if (Address addr = at(entry_index).ExtractManagedResourceOrNull()) {
368 ManagedResource* resource = reinterpret_cast<ManagedResource*>(addr);
369
370 // This can currently only happen during snapshot stress mode as we cannot
371 // normally serialized managed resources. In snapshot stress mode, the new
372 // isolate will be destroyed and the old isolate (really, the old isolate's
373 // external pointer table) therefore effectively retains ownership of the
374 // resource. As such, we need to save and restore the relevant fields of
375 // the external resource. Once the external pointer table itself destroys
376 // the managed resource when freeing the corresponding table entry, this
377 // workaround can be removed again.
378 DCHECK_IMPLIES(!v8_flags.stress_snapshot,
379 resource->ept_entry_ == IndexToHandle(entry_index));
380 resource->ept_entry_ = kNullExternalPointerHandle;
381 }
382}
383
384} // namespace internal
385} // namespace v8
386
387#endif // V8_COMPRESS_POINTERS
388
389#endif // V8_SANDBOX_EXTERNAL_POINTER_TABLE_INL_H_
static T Acquire_Load(T *addr)
static void Relaxed_Store(T *addr, typename std::remove_reference< T >::type new_value)
V8_INLINE IndirectHandle< T > handle(Tagged< T > object, Isolate *isolate)
Definition handles-inl.h:72
TagRange< ExternalPointerTag > ExternalPointerTagRange
constexpr uint64_t kExternalPointerTagAndMarkbitMask
constexpr size_t kMaxExternalPointers
constexpr ExternalPointerHandle kNullExternalPointerHandle
@ kExternalPointerEvacuationEntryTag
@ kExternalPointerFreeEntryTag
@ kExternalPointerZappedEntryTag
V8_EXPORT_PRIVATE FlagValues v8_flags
static V8_INLINE constexpr bool IsManagedExternalPointerType(ExternalPointerTagRange tag_range)
uint32_t ExternalPointerHandle
return value
Definition map-inl.h:893
static constexpr Address kNullAddress
Definition v8-internal.h:53
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define USE(...)
Definition macros.h:293
#define V8_UNLIKELY(condition)
Definition v8config.h:660