[handles] Introduce v8::LocalVector
According to V8's public API documentation, local handles (i.e., objects of type v8::Local<T>) "should never be allocated on the heap". This disallows heap-allocated data structures containing instances of v8::Local, like std::vector<v8::Local<v8::String>>. It is unfortunate that the V8 API itself requires the usage of such data structures. This CL introduces a compile-time flag v8_enable_local_off_stack_check which enforces a run-time DCHECK, that all v8::Local<T> objects are indeed stack-allocated. The check is disabled by default. It will fail for all heap data structures containing local handles. The CL also introduces v8::LocalVector<T> as an intended replacement for std::vector<v8::Local<T>>. This class implements correctly heap-allocated vectors of local handles. The backing store of such vectors does not trigger the off-stack check. Furthermore, if direct locals are used, the backing store is also registered as a strong roots region. Additionally, the CL modifies root visitors so that, when direct locals are used, they bypass slots containing kTaggedNullPointer. In the direct local configuration, this value is used to represent "empty handles" and is expected to be found in the backing stores of v8::LocalVector<T>, for default-constructed elements. Bug: v8:13257 Bug: v8:13270 Bug: chromium:1454114 Change-Id: I1fa6277eab95fa3609d840fca211e2531592e657 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4905902 Reviewed-by: Anton Bikineev <bikineev@chromium.org> Commit-Queue: Nikolaos Papaspyrou <nikolaos@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/main@{#90335}
This commit is contained in:

committed by
V8 LUCI CQ

parent
39818791db
commit
e1649301df
BUILD.bazelBUILD.gn
bazel
gni
include
src
flags
heap
evacuation-verifier-inl.hheap-verifier.ccheap.ccincremental-marking.ccmark-compact.ccminor-mark-sweep.ccread-only-promotion.ccyoung-generation-marking-visitor-inl.h
objects
profiler
test/unittests/heap
@ -42,6 +42,7 @@ load(":bazel/v8-non-pointer-compression.bzl", "v8_binary_non_pointer_compression
|
||||
# v8_enable_conservative_stack_scanning
|
||||
# v8_enable_direct_handle
|
||||
# v8_enable_direct_local
|
||||
# v8_enable_local_off_stack_check
|
||||
# v8_enable_ignition_dispatch_counting
|
||||
# v8_enable_builtins_optimization
|
||||
# v8_enable_builtins_profiling
|
||||
|
4
BUILD.gn
4
BUILD.gn
@ -1216,6 +1216,9 @@ config("features") {
|
||||
if (v8_enable_extensible_ro_snapshot) {
|
||||
defines += [ "V8_ENABLE_EXTENSIBLE_RO_SNAPSHOT" ]
|
||||
}
|
||||
if (v8_enable_local_off_stack_check) {
|
||||
defines += [ "V8_ENABLE_LOCAL_OFF_STACK_CHECK" ]
|
||||
}
|
||||
}
|
||||
|
||||
config("toolchain") {
|
||||
@ -2631,6 +2634,7 @@ action("v8_dump_build_config") {
|
||||
"is_ios=$is_ios",
|
||||
"js_shared_memory=$js_shared_memory",
|
||||
"lite_mode=$v8_enable_lite_mode",
|
||||
"local_off_stack_check=$v8_enable_local_off_stack_check",
|
||||
"mips_arch_variant=\"$mips_arch_variant_var\"",
|
||||
"mips_use_msa=$mips_use_msa_var",
|
||||
"msan=$is_msan",
|
||||
|
@ -564,6 +564,7 @@ def build_config_content(cpu, icu):
|
||||
("is_ios", "false"),
|
||||
("js_shared_memory", "false"),
|
||||
("lite_mode", "false"),
|
||||
("local_off_stack_check", "false"),
|
||||
("mips_arch_variant", '""'),
|
||||
("mips_use_msa", "false"),
|
||||
("msan", "false"),
|
||||
|
@ -98,6 +98,9 @@ declare_args() {
|
||||
# Use direct pointers in local handles.
|
||||
v8_enable_direct_local = false
|
||||
|
||||
# Check for off-stack allocated local handles.
|
||||
v8_enable_local_off_stack_check = false
|
||||
|
||||
v8_enable_google_benchmark = false
|
||||
|
||||
cppgc_is_standalone = false
|
||||
|
@ -1040,14 +1040,19 @@ class V8_EXPORT StrongRootAllocatorBase {
|
||||
// as strong roots.
|
||||
template <typename T>
|
||||
class StrongRootAllocator : public StrongRootAllocatorBase,
|
||||
public std::allocator<T> {
|
||||
private std::allocator<T> {
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
explicit StrongRootAllocator(Heap* heap) : StrongRootAllocatorBase(heap) {}
|
||||
explicit StrongRootAllocator(v8::Isolate* isolate)
|
||||
: StrongRootAllocatorBase(isolate) {}
|
||||
template <typename U>
|
||||
StrongRootAllocator(const StrongRootAllocator<U>& other) noexcept
|
||||
: StrongRootAllocatorBase(other) {}
|
||||
|
||||
using std::allocator<T>::allocate;
|
||||
using std::allocator<T>::deallocate;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "v8-handle-base.h" // NOLINT(build/include_directory)
|
||||
|
||||
@ -17,6 +18,8 @@ template <class T>
|
||||
class LocalBase;
|
||||
template <class T>
|
||||
class Local;
|
||||
template <class T>
|
||||
class LocalVector;
|
||||
template <class F>
|
||||
class MaybeLocal;
|
||||
|
||||
@ -67,6 +70,8 @@ class ConsoleCallArguments;
|
||||
namespace internal {
|
||||
template <typename T>
|
||||
class CustomArguments;
|
||||
template <typename T>
|
||||
class LocalUnchecked;
|
||||
class SamplingHeapProfiler;
|
||||
} // namespace internal
|
||||
|
||||
@ -241,10 +246,12 @@ class LocalBase : public IndirectHandleBase {
|
||||
template <class T>
|
||||
class Local : public LocalBase<T> {
|
||||
public:
|
||||
V8_INLINE Local() = default;
|
||||
V8_INLINE Local() : LocalBase<T>() { VerifyOnStack(); }
|
||||
|
||||
template <class S>
|
||||
V8_INLINE Local(Local<S> that) : LocalBase<T>(that) {
|
||||
VerifyOnStack();
|
||||
|
||||
/**
|
||||
* This check fails when trying to convert between incompatible
|
||||
* handles. For example, converting from a Local<String> to a
|
||||
@ -374,10 +381,18 @@ class Local : public LocalBase<T> {
|
||||
friend class internal::SamplingHeapProfiler;
|
||||
friend class internal::HandleHelper;
|
||||
friend class debug::ConsoleCallArguments;
|
||||
friend class internal::LocalUnchecked<T>;
|
||||
|
||||
V8_INLINE explicit Local<T>(const LocalBase<T>& other)
|
||||
struct no_checking_tag {};
|
||||
|
||||
explicit Local(no_checking_tag) : LocalBase<T>() {}
|
||||
explicit Local(const Local<T>& other, no_checking_tag)
|
||||
: LocalBase<T>(other) {}
|
||||
|
||||
V8_INLINE explicit Local<T>(const LocalBase<T>& other) : LocalBase<T>(other) {
|
||||
VerifyOnStack();
|
||||
}
|
||||
|
||||
V8_INLINE static Local<T> FromSlot(internal::Address* slot) {
|
||||
return Local<T>(LocalBase<T>::FromSlot(slot));
|
||||
}
|
||||
@ -401,6 +416,185 @@ class Local : public LocalBase<T> {
|
||||
V8_INLINE Local<S> UnsafeAs() const {
|
||||
return Local<S>(LocalBase<S>(*this));
|
||||
}
|
||||
|
||||
void VerifyOnStack() const {
|
||||
#ifdef V8_ENABLE_LOCAL_OFF_STACK_CHECK
|
||||
internal::HandleHelper::VerifyOnStack(this);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
// A local variant that is suitable for off-stack allocation.
|
||||
// Used internally by LocalVector<T>. Not to be used directly!
|
||||
template <typename T>
|
||||
class LocalUnchecked : public Local<T> {
|
||||
public:
|
||||
LocalUnchecked() : Local<T>(do_not_check) {}
|
||||
LocalUnchecked(const LocalUnchecked& other) : Local<T>(other, do_not_check) {}
|
||||
LocalUnchecked& operator=(const LocalUnchecked&) = default;
|
||||
|
||||
// Implicit conversion from Local.
|
||||
LocalUnchecked(const Local<T>& other) // NOLINT(runtime/explicit)
|
||||
: Local<T>(other, do_not_check) {}
|
||||
|
||||
private:
|
||||
static constexpr typename Local<T>::no_checking_tag do_not_check{};
|
||||
};
|
||||
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
// Off-stack allocated direct locals must be registered as strong roots.
|
||||
// For off-stack indirect locals, this is not necessary.
|
||||
|
||||
template <typename T>
|
||||
class StrongRootAllocator<LocalUnchecked<T>> : public StrongRootAllocatorBase {
|
||||
public:
|
||||
using value_type = LocalUnchecked<T>;
|
||||
static_assert(std::is_standard_layout_v<value_type>);
|
||||
static_assert(sizeof(value_type) == sizeof(Address));
|
||||
|
||||
explicit StrongRootAllocator(Heap* heap) : StrongRootAllocatorBase(heap) {}
|
||||
explicit StrongRootAllocator(v8::Isolate* isolate)
|
||||
: StrongRootAllocatorBase(isolate) {}
|
||||
template <typename U>
|
||||
StrongRootAllocator(const StrongRootAllocator<U>& other) noexcept
|
||||
: StrongRootAllocatorBase(other) {}
|
||||
|
||||
value_type* allocate(size_t n) {
|
||||
return reinterpret_cast<value_type*>(allocate_impl(n));
|
||||
}
|
||||
void deallocate(value_type* p, size_t n) noexcept {
|
||||
return deallocate_impl(reinterpret_cast<Address*>(p), n);
|
||||
}
|
||||
};
|
||||
#endif // V8_ENABLE_DIRECT_LOCAL
|
||||
} // namespace internal
|
||||
|
||||
template <typename T>
|
||||
class LocalVector {
|
||||
public:
|
||||
using value_type = Local<T>;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using size_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
using iterator = Local<T>*;
|
||||
using const_iterator = const Local<T>*;
|
||||
|
||||
explicit LocalVector(Isolate* isolate) : backing_(make_allocator(isolate)) {}
|
||||
LocalVector(Isolate* isolate, size_t n)
|
||||
: backing_(n, make_allocator(isolate)) {}
|
||||
explicit LocalVector(Isolate* isolate, std::initializer_list<Local<T>> init)
|
||||
: backing_(make_allocator(isolate)) {
|
||||
if (init.size() == 0) return;
|
||||
backing_.reserve(init.size());
|
||||
backing_.insert(backing_.end(), init.begin(), init.end());
|
||||
}
|
||||
|
||||
iterator begin() noexcept { return to_iter(backing_.begin()); }
|
||||
const_iterator begin() const noexcept { return to_iter(backing_.begin()); }
|
||||
iterator end() noexcept { return to_iter(backing_.end()); }
|
||||
const_iterator end() const noexcept { return to_iter(backing_.end()); }
|
||||
|
||||
size_t size() const noexcept { return backing_.size(); }
|
||||
bool empty() const noexcept { return backing_.empty(); }
|
||||
void reserve(size_t n) { backing_.reserve(n); }
|
||||
void shrink_to_fit() { backing_.shrink_to_fit(); }
|
||||
|
||||
Local<T>& operator[](size_t n) { return backing_[n]; }
|
||||
const Local<T>& operator[](size_t n) const { return backing_[n]; }
|
||||
|
||||
Local<T>& at(size_t n) { return backing_.at(n); }
|
||||
const Local<T>& at(size_t n) const { return backing_.at(n); }
|
||||
|
||||
Local<T>& front() { return backing_.front(); }
|
||||
const Local<T>& front() const { return backing_.front(); }
|
||||
Local<T>& back() { return backing_.back(); }
|
||||
const Local<T>& back() const { return backing_.back(); }
|
||||
|
||||
Local<T>* data() noexcept { return backing_.data(); }
|
||||
const Local<T>* data() const noexcept { return backing_.data(); }
|
||||
|
||||
iterator insert(const_iterator pos, const Local<T>& value) {
|
||||
return to_iter(backing_.insert(from_iter(pos), value));
|
||||
}
|
||||
|
||||
template <class InputIt>
|
||||
iterator insert(const_iterator pos, InputIt first, InputIt last) {
|
||||
return to_iter(backing_.insert(from_iter(pos), first, last));
|
||||
}
|
||||
|
||||
iterator insert(const_iterator pos, std::initializer_list<Local<T>> init) {
|
||||
return to_iter(backing_.insert(from_iter(pos), init.begin(), init.end()));
|
||||
}
|
||||
|
||||
LocalVector<T>& operator=(std::initializer_list<Local<T>> init) {
|
||||
backing_.clear();
|
||||
backing_.insert(backing_.end(), init.begin(), init.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push_back(const Local<T>& x) { backing_.push_back(x); }
|
||||
void pop_back() { backing_.pop_back(); }
|
||||
void emplace_back(const Local<T>& x) { backing_.emplace_back(x); }
|
||||
|
||||
void clear() noexcept { backing_.clear(); }
|
||||
void resize(size_t n) { backing_.resize(n); }
|
||||
void swap(LocalVector<T>& other) { backing_.swap(other.backing_); }
|
||||
|
||||
friend bool operator==(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ == y.backing_;
|
||||
}
|
||||
friend bool operator!=(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ != y.backing_;
|
||||
}
|
||||
friend bool operator<(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ < y.backing_;
|
||||
}
|
||||
friend bool operator>(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ > y.backing_;
|
||||
}
|
||||
friend bool operator<=(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ <= y.backing_;
|
||||
}
|
||||
friend bool operator>=(const LocalVector<T>& x, const LocalVector<T>& y) {
|
||||
return x.backing_ >= y.backing_;
|
||||
}
|
||||
|
||||
private:
|
||||
using element_type = internal::LocalUnchecked<T>;
|
||||
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
using allocator_type = internal::StrongRootAllocator<element_type>;
|
||||
|
||||
static allocator_type make_allocator(Isolate* isolate) noexcept {
|
||||
return allocator_type(isolate);
|
||||
}
|
||||
#else
|
||||
using allocator_type = std::allocator<element_type>;
|
||||
|
||||
static allocator_type make_allocator(Isolate* isolate) noexcept {
|
||||
return allocator_type();
|
||||
}
|
||||
#endif // V8_ENABLE_DIRECT_LOCAL
|
||||
|
||||
using vector_type = std::vector<element_type, allocator_type>;
|
||||
|
||||
static constexpr iterator to_iter(typename vector_type::iterator it) {
|
||||
return &*it;
|
||||
}
|
||||
static constexpr const_iterator to_iter(
|
||||
typename vector_type::const_iterator it) {
|
||||
return &*it;
|
||||
}
|
||||
constexpr typename vector_type::iterator from_iter(iterator it) {
|
||||
return backing_.begin() + (it - begin());
|
||||
}
|
||||
constexpr typename vector_type::const_iterator from_iter(const_iterator it) {
|
||||
return backing_.begin() + (it - begin());
|
||||
}
|
||||
|
||||
vector_type backing_;
|
||||
};
|
||||
|
||||
#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS)
|
||||
|
@ -503,6 +503,15 @@ DEFINE_BOOL_READONLY(direct_local, V8_ENABLE_DIRECT_LOCAL_BOOL,
|
||||
"use direct local handles")
|
||||
DEFINE_IMPLICATION(direct_local, conservative_stack_scanning)
|
||||
|
||||
#ifdef V8_ENABLE_LOCAL_OFF_STACK_CHECK
|
||||
#define V8_ENABLE_LOCAL_OFF_STACK_CHECK_BOOL true
|
||||
#else
|
||||
#define V8_ENABLE_LOCAL_OFF_STACK_CHECK_BOOL false
|
||||
#endif
|
||||
DEFINE_BOOL_READONLY(local_off_stack_check,
|
||||
V8_ENABLE_LOCAL_OFF_STACK_CHECK_BOOL,
|
||||
"check for off-stack allocation of v8::Local")
|
||||
|
||||
#ifdef V8_ENABLE_FUTURE
|
||||
#define FUTURE_BOOL true
|
||||
#else
|
||||
|
@ -31,6 +31,9 @@ template <typename TSlot>
|
||||
void EvacuationVerifier::VerifyPointersImpl(TSlot start, TSlot end) {
|
||||
for (TSlot current = start; current < end; ++current) {
|
||||
typename TSlot::TObject object = current.load(cage_base());
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) continue;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
if (object.GetHeapObjectIfStrong(&heap_object)) {
|
||||
VerifyHeapObjectImpl(heap_object);
|
||||
|
@ -151,6 +151,9 @@ template <typename TSlot>
|
||||
void VerifyPointersVisitor::VerifyPointersImpl(TSlot start, TSlot end) {
|
||||
for (TSlot slot = start; slot < end; ++slot) {
|
||||
typename TSlot::TObject object = slot.load(cage_base());
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) continue;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
if (object.GetHeapObject(&heap_object)) {
|
||||
VerifyHeapObjectImpl(heap_object);
|
||||
|
@ -6384,6 +6384,9 @@ class UnreachableObjectsFilter : public HeapObjectsFilter {
|
||||
// Treat weak references as strong.
|
||||
for (TSlot p = start; p < end; ++p) {
|
||||
typename TSlot::TObject object = p.load(cage_base());
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) continue;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
if (object.GetHeapObject(&heap_object)) {
|
||||
MarkHeapObject(heap_object);
|
||||
|
@ -235,6 +235,9 @@ class IncrementalMarking::IncrementalMarkingRootMarkingVisitor final
|
||||
private:
|
||||
void MarkObjectByPointer(Root root, FullObjectSlot p) {
|
||||
Tagged<Object> object = *p;
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) return;
|
||||
#endif
|
||||
if (!IsHeapObject(object)) return;
|
||||
DCHECK(!MapWord::IsPacked(object.ptr()));
|
||||
Tagged<HeapObject> heap_object = HeapObject::cast(object);
|
||||
|
@ -189,6 +189,9 @@ class FullMarkingVerifier : public MarkingVerifierBase {
|
||||
V8_INLINE void VerifyPointersImpl(TSlot start, TSlot end) {
|
||||
for (TSlot slot = start; slot < end; ++slot) {
|
||||
typename TSlot::TObject object = slot.load(cage_base());
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) continue;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
if (object.GetHeapObjectIfStrong(&heap_object)) {
|
||||
VerifyHeapObjectImpl(heap_object);
|
||||
@ -913,6 +916,9 @@ class MarkCompactCollector::RootMarkingVisitor final : public RootVisitor {
|
||||
private:
|
||||
V8_INLINE void MarkObjectByPointer(Root root, FullObjectSlot p) {
|
||||
Tagged<Object> object = *p;
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) return;
|
||||
#endif
|
||||
if (!IsHeapObject(object)) return;
|
||||
Tagged<HeapObject> heap_object = HeapObject::cast(object);
|
||||
if (!collector_->ShouldMarkObject(heap_object)) return;
|
||||
@ -3673,6 +3679,9 @@ static inline SlotCallbackResult UpdateOldToSharedSlot(
|
||||
template <typename TSlot>
|
||||
static inline void UpdateStrongSlot(PtrComprCageBase cage_base, TSlot slot) {
|
||||
typename TSlot::TObject obj = slot.Relaxed_Load(cage_base);
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (obj.ptr() == kTaggedNullAddress) return;
|
||||
#endif
|
||||
DCHECK(!HAS_WEAK_HEAP_OBJECT_TAG(obj.ptr()));
|
||||
Tagged<HeapObject> heap_obj;
|
||||
if (obj.GetHeapObject(&heap_obj)) {
|
||||
@ -3683,6 +3692,9 @@ static inline void UpdateStrongSlot(PtrComprCageBase cage_base, TSlot slot) {
|
||||
static inline SlotCallbackResult UpdateStrongOldToSharedSlot(
|
||||
PtrComprCageBase cage_base, FullMaybeObjectSlot slot) {
|
||||
MaybeObject obj = slot.Relaxed_Load(cage_base);
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (obj.ptr() == kTaggedNullAddress) return REMOVE_SLOT;
|
||||
#endif
|
||||
DCHECK(!HAS_WEAK_HEAP_OBJECT_TAG(obj.ptr()));
|
||||
Tagged<HeapObject> heap_obj;
|
||||
if (obj.GetHeapObject(&heap_obj)) {
|
||||
|
@ -128,6 +128,9 @@ class YoungGenerationMarkingVerifier : public MarkingVerifierBase {
|
||||
GetPtrComprCageBaseFromOnHeapAddress(start.address());
|
||||
for (TSlot slot = start; slot < end; ++slot) {
|
||||
typename TSlot::TObject object = slot.load(cage_base);
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) continue;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
// Minor MS treats weak references as strong.
|
||||
if (object.GetHeapObject(&heap_object)) {
|
||||
|
@ -443,6 +443,9 @@ class ReadOnlyPromotionImpl final : public AllStatic {
|
||||
private:
|
||||
void ProcessSlot(Root root, FullObjectSlot slot) {
|
||||
Tagged<Object> old_slot_value_obj = slot.load(isolate_);
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (old_slot_value_obj.ptr() == kTaggedNullAddress) return;
|
||||
#endif
|
||||
if (!IsHeapObject(old_slot_value_obj)) return;
|
||||
Tagged<HeapObject> old_slot_value = HeapObject::cast(old_slot_value_obj);
|
||||
auto it = moves_->find(old_slot_value);
|
||||
|
@ -180,7 +180,9 @@ V8_INLINE bool YoungGenerationMarkingVisitor<marking_mode>::VisitObjectViaSlot(
|
||||
TSlot slot) {
|
||||
typename TSlot::TObject target =
|
||||
slot.Relaxed_Load(ObjectVisitorWithCageBases::cage_base());
|
||||
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (target.ptr() == kTaggedNullAddress) return false;
|
||||
#endif
|
||||
Tagged<HeapObject> heap_object;
|
||||
// Treat weak references as strong.
|
||||
if (!target.GetHeapObject(&heap_object)) {
|
||||
|
@ -243,7 +243,11 @@ class ClientRootVisitor final : public RootVisitor {
|
||||
void VisitRootPointers(Root root, const char* description,
|
||||
FullObjectSlot start, FullObjectSlot end) final {
|
||||
for (FullObjectSlot p = start; p < end; ++p) {
|
||||
if (!IsSharedHeapObject(*p)) continue;
|
||||
Tagged<Object> object = *p;
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == ValueHelper::kTaggedNullAddress) continue;
|
||||
#endif
|
||||
if (!IsSharedHeapObject(object)) continue;
|
||||
actual_visitor_->VisitRootPointer(root, description, p);
|
||||
}
|
||||
}
|
||||
|
@ -2205,12 +2205,16 @@ class RootsReferencesExtractor : public RootVisitor {
|
||||
void SetVisitingWeakRoots() { visiting_weak_roots_ = true; }
|
||||
|
||||
void VisitRootPointer(Root root, const char* description,
|
||||
FullObjectSlot object) override {
|
||||
FullObjectSlot p) override {
|
||||
Tagged<Object> object = *p;
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
if (object.ptr() == kTaggedNullAddress) return;
|
||||
#endif
|
||||
if (root == Root::kBuiltins) {
|
||||
explorer_->TagBuiltinCodeObject(Code::cast(*object), description);
|
||||
explorer_->TagBuiltinCodeObject(Code::cast(object), description);
|
||||
}
|
||||
explorer_->SetGcSubrootReference(root, description, visiting_weak_roots_,
|
||||
*object);
|
||||
object);
|
||||
}
|
||||
|
||||
void VisitRootPointers(Root root, const char* description,
|
||||
|
@ -181,5 +181,71 @@ TEST_F(StrongRootAllocatorTest, SetNotRetained) {
|
||||
EXPECT_TRUE(weak.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(StrongRootAllocatorTest, LocalVector) {
|
||||
ManualGCScope manual_gc_scope(i_isolate());
|
||||
Global<v8::FixedArray> weak;
|
||||
|
||||
{
|
||||
v8::HandleScope outer_scope(v8_isolate());
|
||||
// LocalVector uses the StrongRootAllocator for its backing store.
|
||||
LocalVector<v8::FixedArray> v(v8_isolate(), 10);
|
||||
|
||||
{
|
||||
v8::EscapableHandleScope inner_scope(v8_isolate());
|
||||
Handle<FixedArray> h = factory()->NewFixedArray(10, AllocationType::kOld);
|
||||
Local<v8::FixedArray> l = Utils::FixedArrayToLocal(h);
|
||||
weak.Reset(v8_isolate(), l);
|
||||
weak.SetWeak();
|
||||
v[7] = inner_scope.Escape(l);
|
||||
}
|
||||
|
||||
{
|
||||
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap());
|
||||
InvokeMajorGC();
|
||||
}
|
||||
EXPECT_FALSE(weak.IsEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap());
|
||||
InvokeMajorGC();
|
||||
}
|
||||
EXPECT_TRUE(weak.IsEmpty());
|
||||
}
|
||||
|
||||
#ifdef V8_ENABLE_DIRECT_LOCAL
|
||||
TEST_F(StrongRootAllocatorTest, LocalVectorWithDirect) {
|
||||
ManualGCScope manual_gc_scope(i_isolate());
|
||||
Global<v8::FixedArray> weak;
|
||||
|
||||
{
|
||||
// LocalVector uses the StrongRootAllocator for its backing store.
|
||||
LocalVector<v8::FixedArray> v(v8_isolate(), 10);
|
||||
|
||||
{
|
||||
v8::HandleScope scope(v8_isolate());
|
||||
Handle<FixedArray> h = factory()->NewFixedArray(10, AllocationType::kOld);
|
||||
Local<v8::FixedArray> l = Utils::FixedArrayToLocal(h);
|
||||
// This is legal without escaping, because locals are direct.
|
||||
v[7] = l;
|
||||
weak.Reset(v8_isolate(), l);
|
||||
weak.SetWeak();
|
||||
}
|
||||
|
||||
{
|
||||
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap());
|
||||
InvokeMajorGC();
|
||||
}
|
||||
EXPECT_FALSE(weak.IsEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap());
|
||||
InvokeMajorGC();
|
||||
}
|
||||
EXPECT_TRUE(weak.IsEmpty());
|
||||
}
|
||||
#endif // V8_ENABLE_DIRECT_LOCAL
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Reference in New Issue
Block a user