[heap] Verify the shared heap before tearing down a client heap
In the case of bugs creating shared->local edges, this lets us catch dangling pointers via CHECKs before they happen. Also removed some redundant checks in the shared struct verifier. Existing heap verification already checks that all of a Heap's pointers are contained within it. Bug: v8:12547 Change-Id: Ic7a007b3b6559e3dfd0286fbf869586023c6f801 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3704911 Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Shu-yu Guo <syg@chromium.org> Cr-Commit-Position: refs/heads/main@{#81335}
This commit is contained in:
parent
358ff9bc41
commit
54c69fc584
@ -327,6 +327,8 @@ void HeapObject::HeapObjectVerify(Isolate* isolate) {
|
||||
// static
|
||||
void HeapObject::VerifyHeapPointer(Isolate* isolate, Object p) {
|
||||
CHECK(p.IsHeapObject());
|
||||
// If you crashed here and {isolate->is_shared()}, there is a bug causing the
|
||||
// host of {p} to point to a non-shared object.
|
||||
CHECK(IsValidHeapObject(isolate->heap(), HeapObject::cast(p)));
|
||||
CHECK_IMPLIES(V8_EXTERNAL_CODE_SPACE_BOOL, !p.IsCode());
|
||||
}
|
||||
@ -1238,6 +1240,7 @@ USE_TORQUE_VERIFIER(JSWrappedFunction)
|
||||
|
||||
void JSSharedStruct::JSSharedStructVerify(Isolate* isolate) {
|
||||
CHECK(IsJSSharedStruct());
|
||||
CHECK(InSharedWritableHeap());
|
||||
JSObjectVerify(isolate);
|
||||
CHECK(HasFastProperties());
|
||||
// Shared structs can only point to primitives or other shared HeapObjects,
|
||||
@ -1258,10 +1261,9 @@ void JSSharedStruct::JSSharedStructVerify(Isolate* isolate) {
|
||||
|
||||
void JSAtomicsMutex::JSAtomicsMutexVerify(Isolate* isolate) {
|
||||
CHECK(IsJSAtomicsMutex());
|
||||
CHECK(InSharedHeap());
|
||||
CHECK(InSharedWritableHeap());
|
||||
JSObjectVerify(isolate);
|
||||
Map mutex_map = map();
|
||||
CHECK(mutex_map.InSharedHeap());
|
||||
CHECK(mutex_map.GetBackPointer().IsUndefined(isolate));
|
||||
CHECK(!mutex_map.is_extensible());
|
||||
CHECK(!mutex_map.is_prototype_map());
|
||||
|
@ -4609,6 +4609,26 @@ void Heap::VerifyReadOnlyHeap() {
|
||||
read_only_space_->Verify(isolate());
|
||||
}
|
||||
|
||||
void Heap::VerifySharedHeap(Isolate* initiator) {
|
||||
DCHECK(IsShared());
|
||||
|
||||
// Stop all client isolates attached to this isolate.
|
||||
GlobalSafepointScope global_safepoint(initiator);
|
||||
|
||||
// Migrate shared isolate to the main thread of the initiator isolate.
|
||||
v8::Locker locker(reinterpret_cast<v8::Isolate*>(isolate()));
|
||||
v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate()));
|
||||
|
||||
DCHECK_NOT_NULL(isolate()->global_safepoint());
|
||||
|
||||
// Free all shared LABs to make the shared heap iterable.
|
||||
isolate()->global_safepoint()->IterateClientIsolates([](Isolate* client) {
|
||||
client->heap()->FreeSharedLinearAllocationAreas();
|
||||
});
|
||||
|
||||
Verify();
|
||||
}
|
||||
|
||||
class SlotVerifyingVisitor : public ObjectVisitorWithCageBases {
|
||||
public:
|
||||
SlotVerifyingVisitor(Isolate* isolate, std::set<Address>* untyped,
|
||||
@ -6037,9 +6057,14 @@ void Heap::StartTearDown() {
|
||||
// tear down parts of the Isolate.
|
||||
if (FLAG_verify_heap) {
|
||||
AllowGarbageCollection allow_gc;
|
||||
IgnoreLocalGCRequests ignore_gc_requests(this);
|
||||
SafepointScope scope(this);
|
||||
Verify();
|
||||
|
||||
// If this is a client Isolate of a shared Isolate, verify that there are no
|
||||
// shared-to-local pointers before tearing down the client Isolate and
|
||||
// creating dangling pointers.
|
||||
if (Isolate* shared_isolate = isolate()->shared_isolate()) {
|
||||
shared_isolate->heap()->VerifySharedHeap(isolate());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -1617,9 +1617,15 @@ class Heap {
|
||||
#ifdef VERIFY_HEAP
|
||||
// Verify the heap is in its normal state before or after a GC.
|
||||
V8_EXPORT_PRIVATE void Verify();
|
||||
|
||||
// Verify the read-only heap after all read-only heap objects have been
|
||||
// created.
|
||||
void VerifyReadOnlyHeap();
|
||||
|
||||
// Verify the shared heap, initiating from a client heap. This performs a
|
||||
// global safepoint, then the normal heap verification.
|
||||
void VerifySharedHeap(Isolate* initiator);
|
||||
|
||||
void VerifyRememberedSetFor(HeapObject object);
|
||||
|
||||
// Verify that cached size of invalidated object is up-to-date.
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "src/common/assert-scope.h"
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/heap/heap-inl.h"
|
||||
#include "src/heap/parked-scope.h"
|
||||
#include "src/heap/read-only-heap.h"
|
||||
#include "src/heap/safepoint.h"
|
||||
#include "src/heap/spaces.h"
|
||||
@ -5040,7 +5041,13 @@ UNINITIALIZED_TEST(SharedStrings) {
|
||||
CheckObjectsAreInSharedHeap(i_isolate1);
|
||||
CheckObjectsAreInSharedHeap(i_isolate2);
|
||||
|
||||
isolate1->Dispose();
|
||||
{
|
||||
// Because both isolate1 and isolate2 are considered running on the main
|
||||
// thread, one must be parked to avoid deadlock in the shared heap
|
||||
// verification that may happen on client heap disposal.
|
||||
ParkedScope parked(i_isolate2->main_thread_local_isolate());
|
||||
isolate1->Dispose();
|
||||
}
|
||||
isolate2->Dispose();
|
||||
Isolate::Delete(reinterpret_cast<Isolate*>(shared_isolate));
|
||||
|
||||
|
@ -21,6 +21,27 @@ struct V8_NODISCARD IsolateWrapper {
|
||||
v8::Isolate* const isolate;
|
||||
};
|
||||
|
||||
// Some tests in this file allocate two Isolates in the same thread to directly
|
||||
// test shared string behavior. Because both are considered running, when
|
||||
// disposing these Isolates, one must be parked to not cause a deadlock in the
|
||||
// shared heap verification that happens on client Isolate disposal.
|
||||
struct V8_NODISCARD IsolatePairWrapper {
|
||||
IsolatePairWrapper(v8::Isolate* isolate1, v8::Isolate* isolate2)
|
||||
: isolate1(isolate1), isolate2(isolate2) {}
|
||||
|
||||
~IsolatePairWrapper() {
|
||||
{
|
||||
i::ParkedScope parked(
|
||||
reinterpret_cast<Isolate*>(isolate1)->main_thread_local_isolate());
|
||||
isolate2->Dispose();
|
||||
}
|
||||
isolate1->Dispose();
|
||||
}
|
||||
|
||||
v8::Isolate* const isolate1;
|
||||
v8::Isolate* const isolate2;
|
||||
};
|
||||
|
||||
class MultiClientIsolateTest {
|
||||
public:
|
||||
MultiClientIsolateTest() {
|
||||
@ -111,10 +132,10 @@ UNINITIALIZED_TEST(InPlaceInternalization) {
|
||||
FLAG_shared_string_table = true;
|
||||
|
||||
MultiClientIsolateTest test;
|
||||
IsolateWrapper isolate1_wrapper(test.NewClientIsolate());
|
||||
IsolateWrapper isolate2_wrapper(test.NewClientIsolate());
|
||||
v8::Isolate* isolate1 = isolate1_wrapper.isolate;
|
||||
v8::Isolate* isolate2 = isolate2_wrapper.isolate;
|
||||
IsolatePairWrapper isolates_wrapper(test.NewClientIsolate(),
|
||||
test.NewClientIsolate());
|
||||
v8::Isolate* isolate1 = isolates_wrapper.isolate1;
|
||||
v8::Isolate* isolate2 = isolates_wrapper.isolate2;
|
||||
Isolate* i_isolate1 = reinterpret_cast<Isolate*>(isolate1);
|
||||
Factory* factory1 = i_isolate1->factory();
|
||||
Isolate* i_isolate2 = reinterpret_cast<Isolate*>(isolate2);
|
||||
@ -179,10 +200,10 @@ UNINITIALIZED_TEST(YoungInternalization) {
|
||||
FLAG_shared_string_table = true;
|
||||
|
||||
MultiClientIsolateTest test;
|
||||
IsolateWrapper isolate1_wrapper(test.NewClientIsolate());
|
||||
IsolateWrapper isolate2_wrapper(test.NewClientIsolate());
|
||||
v8::Isolate* isolate1 = isolate1_wrapper.isolate;
|
||||
v8::Isolate* isolate2 = isolate2_wrapper.isolate;
|
||||
IsolatePairWrapper isolates_wrapper(test.NewClientIsolate(),
|
||||
test.NewClientIsolate());
|
||||
v8::Isolate* isolate1 = isolates_wrapper.isolate1;
|
||||
v8::Isolate* isolate2 = isolates_wrapper.isolate2;
|
||||
Isolate* i_isolate1 = reinterpret_cast<Isolate*>(isolate1);
|
||||
Factory* factory1 = i_isolate1->factory();
|
||||
Isolate* i_isolate2 = reinterpret_cast<Isolate*>(isolate2);
|
||||
|
Loading…
Reference in New Issue
Block a user