// Copyright 2013 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "src/handles/global-handles.h" #include "include/v8-embedder-heap.h" #include "include/v8-function.h" #include "src/api/api-inl.h" #include "src/execution/isolate.h" #include "src/flags/flags.h" #include "src/heap/factory.h" #include "src/heap/heap-inl.h" #include "src/objects/objects-inl.h" #include "test/unittests/heap/heap-utils.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { namespace { struct TracedReferenceWrapper { v8::TracedReference handle; }; class NonRootingEmbedderRootsHandler final : public v8::EmbedderRootsHandler { public: bool IsRoot(const v8::TracedReference& handle) final { return false; } void ResetRoot(const v8::TracedReference& handle) final { for (auto* wrapper : wrappers_) { if (wrapper->handle == handle) { wrapper->handle.Reset(); } } } void Register(TracedReferenceWrapper* wrapper) { wrappers_.push_back(wrapper); } private: std::vector wrappers_; }; void SimpleCallback(const v8::FunctionCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); info.GetReturnValue().Set(v8::Number::New(isolate, 0)); } struct FlagAndGlobal { bool flag; v8::Global handle; }; void ResetHandleAndSetFlag(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.GetParameter()->flag = true; } template void ConstructJSObject(v8::Isolate* isolate, v8::Local context, HandleContainer* flag_and_persistent) { v8::HandleScope handle_scope(isolate); v8::Local object(v8::Object::New(isolate)); CHECK(!object.IsEmpty()); flag_and_persistent->handle.Reset(isolate, object); CHECK(!flag_and_persistent->handle.IsEmpty()); } void ConstructJSObject(v8::Isolate* isolate, v8::Global* global) { v8::HandleScope scope(isolate); v8::Local object(v8::Object::New(isolate)); CHECK(!object.IsEmpty()); *global = v8::Global(isolate, object); CHECK(!global->IsEmpty()); } void ConstructJSObject(v8::Isolate* isolate, v8::TracedReference* handle) { v8::HandleScope scope(isolate); v8::Local object(v8::Object::New(isolate)); CHECK(!object.IsEmpty()); *handle = v8::TracedReference(isolate, object); CHECK(!handle->IsEmpty()); } template void ConstructJSApiObject(v8::Isolate* isolate, v8::Local context, HandleContainer* flag_and_persistent) { v8::HandleScope handle_scope(isolate); v8::Local fun = v8::FunctionTemplate::New(isolate, SimpleCallback); v8::Local object = fun->GetFunction(context) .ToLocalChecked() ->NewInstance(context) .ToLocalChecked(); CHECK(!object.IsEmpty()); flag_and_persistent->handle.Reset(isolate, object); CHECK(!flag_and_persistent->handle.IsEmpty()); } enum class SurvivalMode { kSurvives, kDies }; template void WeakHandleTest(v8::Isolate* isolate, ConstructFunction construct_function, ModifierFunction modifier_function, GCFunction gc_function, SurvivalMode survives) { v8::HandleScope scope(isolate); DisableConservativeStackScanningScopeForTesting no_stack_scanning( reinterpret_cast(isolate)->heap()); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); FlagAndGlobal fp; construct_function(isolate, context, &fp); CHECK(IsNewObjectInCorrectGeneration(isolate, fp.handle)); fp.handle.SetWeak(&fp, &ResetHandleAndSetFlag, v8::WeakCallbackType::kParameter); fp.flag = false; modifier_function(&fp); gc_function(); CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !fp.flag); CHECK_IMPLIES(survives == SurvivalMode::kDies, fp.flag); } void EmptyWeakCallback(const v8::WeakCallbackInfo& data) {} class GlobalHandlesTest : public TestWithContext { protected: template void TracedReferenceTestWithScavenge(ConstructFunction construct_function, ModifierFunction modifier_function, SurvivalMode survives) { v8::Isolate* isolate = v8_isolate(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( i_isolate()->heap()); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); NonRootingEmbedderRootsHandler roots_handler; v8_isolate()->SetEmbedderRootsHandler(&roots_handler); auto fp = std::make_unique(); roots_handler.Register(fp.get()); construct_function(isolate, context, fp.get()); CHECK(IsNewObjectInCorrectGeneration(isolate, fp->handle)); modifier_function(fp.get()); CollectGarbage(i::NEW_SPACE); // Scavenge clear properly resets the original handle, so we can check the // handle directly here. CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !fp->handle.IsEmpty()); CHECK_IMPLIES(survives == SurvivalMode::kDies, fp->handle.IsEmpty()); v8_isolate()->SetEmbedderRootsHandler(nullptr); } }; } // namespace TEST_F(GlobalHandlesTest, EternalHandles) { Isolate* isolate = i_isolate(); v8::Isolate* v8_isolate = reinterpret_cast(isolate); EternalHandles* eternal_handles = isolate->eternal_handles(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( isolate->heap()); // Create a number of handles that will not be on a block boundary const int kArrayLength = 2048 - 1; int indices[kArrayLength]; v8::Eternal eternals[kArrayLength]; CHECK_EQ(0, eternal_handles->handles_count()); for (int i = 0; i < kArrayLength; i++) { indices[i] = -1; HandleScope scope(isolate); v8::Local object = v8::Object::New(v8_isolate); object ->Set(v8_isolate->GetCurrentContext(), i, v8::Integer::New(v8_isolate, i)) .FromJust(); // Create with internal api eternal_handles->Create(isolate, *v8::Utils::OpenHandle(*object), &indices[i]); // Create with external api CHECK(eternals[i].IsEmpty()); eternals[i].Set(v8_isolate, object); CHECK(!eternals[i].IsEmpty()); } CollectAllAvailableGarbage(); for (int i = 0; i < kArrayLength; i++) { for (int j = 0; j < 2; j++) { HandleScope scope(isolate); v8::Local local; if (j == 0) { // Test internal api local = v8::Utils::ToLocal(eternal_handles->Get(indices[i])); } else { // Test external api local = eternals[i].Get(v8_isolate); } v8::Local object = v8::Local::Cast(local); v8::Local value = object->Get(v8_isolate->GetCurrentContext(), i).ToLocalChecked(); CHECK(value->IsInt32()); CHECK_EQ(i, value->Int32Value(v8_isolate->GetCurrentContext()).FromJust()); } } CHECK_EQ(2 * kArrayLength, eternal_handles->handles_count()); // Create an eternal via the constructor { HandleScope scope(isolate); v8::Local object = v8::Object::New(v8_isolate); v8::Eternal eternal(v8_isolate, object); CHECK(!eternal.IsEmpty()); CHECK(object == eternal.Get(v8_isolate)); } CHECK_EQ(2 * kArrayLength + 1, eternal_handles->handles_count()); } TEST_F(GlobalHandlesTest, PersistentBaseGetLocal) { v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::Local o = v8::Object::New(isolate); CHECK(!o.IsEmpty()); v8::Persistent p(isolate, o); CHECK(o == p.Get(isolate)); CHECK(v8::Local::New(isolate, p) == p.Get(isolate)); v8::Global g(isolate, o); CHECK(o == g.Get(isolate)); CHECK(v8::Local::New(isolate, g) == g.Get(isolate)); } TEST_F(GlobalHandlesTest, WeakPersistentSmi) { v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::Local n = v8::Number::New(isolate, 0); v8::Global g(isolate, n); // Should not crash. g.SetWeak(nullptr, &EmptyWeakCallback, v8::WeakCallbackType::kParameter); } TEST_F(GlobalHandlesTest, PhantomHandlesWithoutCallbacks) { v8::Isolate* isolate = v8_isolate(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( i_isolate()->heap()); v8::Global g1, g2; { v8::HandleScope scope(isolate); g1.Reset(isolate, v8::Object::New(isolate)); g1.SetWeak(); g2.Reset(isolate, v8::Object::New(isolate)); g2.SetWeak(); } CHECK(!g1.IsEmpty()); CHECK(!g2.IsEmpty()); CollectAllAvailableGarbage(); CHECK(g1.IsEmpty()); CHECK(g2.IsEmpty()); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSObjectDiesOnScavenge) { if (v8_flags.single_generation) return; WeakHandleTest( v8_isolate(), &ConstructJSObject, [](FlagAndGlobal* fp) {}, [this]() { CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kDies); } TEST_F(GlobalHandlesTest, TracedReferenceToUnmodifiedJSObjectSurvivesScavenge) { if (v8_flags.single_generation) return; ManualGCScope manual_gc(i_isolate()); TracedReferenceTestWithScavenge( &ConstructJSObject, [](TracedReferenceWrapper* fp) {}, SurvivalMode::kSurvives); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSObjectDiesOnMarkCompact) { WeakHandleTest( v8_isolate(), &ConstructJSObject, [](FlagAndGlobal* fp) {}, [this]() { CollectAllGarbage(); }, SurvivalMode::kDies); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSObjectSurvivesMarkCompactWhenInHandle) { WeakHandleTest( v8_isolate(), &ConstructJSObject, [this](FlagAndGlobal* fp) { v8::Local handle = v8::Local::New(v8_isolate(), fp->handle); USE(handle); }, [this]() { CollectAllGarbage(); }, SurvivalMode::kSurvives); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSApiObjectDiesOnScavenge) { if (v8_flags.single_generation) return; WeakHandleTest( v8_isolate(), &ConstructJSApiObject, [](FlagAndGlobal* fp) {}, [this]() { CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kDies); } TEST_F(GlobalHandlesTest, TracedReferenceToUnmodifiedJSApiObjectDiesOnScavenge) { if (v8_flags.single_generation) return; ManualGCScope manual_gc(i_isolate()); TracedReferenceTestWithScavenge( &ConstructJSApiObject, [](TracedReferenceWrapper* fp) {}, SurvivalMode::kDies); } TEST_F(GlobalHandlesTest, TracedReferenceToJSApiObjectWithIdentityHashSurvivesScavenge) { if (v8_flags.single_generation) return; ManualGCScope manual_gc(i_isolate()); Isolate* isolate = i_isolate(); HandleScope scope(isolate); Handle weakmap = isolate->factory()->NewJSWeakMap(); TracedReferenceTestWithScavenge( &ConstructJSApiObject, [this, &weakmap, isolate](TracedReferenceWrapper* fp) { v8::HandleScope scope(v8_isolate()); Handle key = Utils::OpenHandle(*fp->handle.Get(v8_isolate())); Handle smi(Smi::FromInt(23), isolate); int32_t hash = key->GetOrCreateHash(isolate).value(); JSWeakCollection::Set(weakmap, key, smi, hash); }, SurvivalMode::kSurvives); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSApiObjectSurvivesScavengeWhenInHandle) { if (v8_flags.single_generation) return; WeakHandleTest( v8_isolate(), &ConstructJSApiObject, [this](FlagAndGlobal* fp) { v8::Local handle = v8::Local::New(v8_isolate(), fp->handle); USE(handle); }, [this]() { CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kSurvives); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSApiObjectDiesOnMarkCompact) { WeakHandleTest( v8_isolate(), &ConstructJSApiObject, [](FlagAndGlobal* fp) {}, [this]() { CollectAllGarbage(); }, SurvivalMode::kDies); } TEST_F(GlobalHandlesTest, WeakHandleToUnmodifiedJSApiObjectSurvivesMarkCompactWhenInHandle) { WeakHandleTest( v8_isolate(), &ConstructJSApiObject, [this](FlagAndGlobal* fp) { v8::Local handle = v8::Local::New(v8_isolate(), fp->handle); USE(handle); }, [this]() { CollectAllGarbage(); }, SurvivalMode::kSurvives); } TEST_F(GlobalHandlesTest, TracedReferenceToJSApiObjectWithModifiedMapSurvivesScavenge) { if (v8_flags.single_generation) return; v8::Isolate* isolate = v8_isolate(); TracedReference handle; { v8::HandleScope scope(isolate); // Create an API object which does not have the same map as constructor. auto function_template = FunctionTemplate::New(isolate); auto instance_t = function_template->InstanceTemplate(); instance_t->Set(isolate, "a", v8::Number::New(isolate, 10)); auto function = function_template->GetFunction(v8_context()).ToLocalChecked(); auto i = function->NewInstance(v8_context()).ToLocalChecked(); handle.Reset(isolate, i); } CollectGarbage(i::NEW_SPACE); CHECK(!handle.IsEmpty()); } TEST_F(GlobalHandlesTest, TracedReferenceTOJsApiObjectWithElementsSurvivesScavenge) { if (v8_flags.single_generation) return; v8::Isolate* isolate = v8_isolate(); TracedReference handle; { v8::HandleScope scope(isolate); // Create an API object which has elements. auto function_template = FunctionTemplate::New(isolate); auto instance_t = function_template->InstanceTemplate(); instance_t->Set(isolate, "1", v8::Number::New(isolate, 10)); instance_t->Set(isolate, "2", v8::Number::New(isolate, 10)); auto function = function_template->GetFunction(v8_context()).ToLocalChecked(); auto i = function->NewInstance(v8_context()).ToLocalChecked(); handle.Reset(isolate, i); } CollectGarbage(i::NEW_SPACE); CHECK(!handle.IsEmpty()); } namespace { void ForceScavenge2(const v8::WeakCallbackInfo& data) { data.GetParameter()->flag = true; YoungGC(data.GetIsolate()); } void ForceScavenge1(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.SetSecondPassCallback(ForceScavenge2); } void ForceMarkSweep2(const v8::WeakCallbackInfo& data) { data.GetParameter()->flag = true; FullGC(data.GetIsolate()); } void ForceMarkSweep1(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.SetSecondPassCallback(ForceMarkSweep2); } } // namespace TEST_F(GlobalHandlesTest, GCFromWeakCallbacks) { v8::Isolate* isolate = v8_isolate(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( i_isolate()->heap()); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); if (v8_flags.single_generation) { FlagAndGlobal fp; ConstructJSApiObject(isolate, context, &fp); CHECK_IMPLIES(!v8_flags.single_generation, !InYoungGeneration(isolate, fp.handle)); fp.flag = false; fp.handle.SetWeak(&fp, &ForceMarkSweep1, v8::WeakCallbackType::kParameter); CollectAllGarbage(); EmptyMessageQueues(); CHECK(fp.flag); return; } static const int kNumberOfGCTypes = 2; using Callback = v8::WeakCallbackInfo::Callback; Callback gc_forcing_callback[kNumberOfGCTypes] = {&ForceScavenge1, &ForceMarkSweep1}; using GCInvoker = std::function; GCInvoker invoke_gc[kNumberOfGCTypes] = { [this]() { CollectGarbage(i::NEW_SPACE); }, [this]() { CollectAllGarbage(); }}; for (int outer_gc = 0; outer_gc < kNumberOfGCTypes; outer_gc++) { for (int inner_gc = 0; inner_gc < kNumberOfGCTypes; inner_gc++) { FlagAndGlobal fp; ConstructJSApiObject(isolate, context, &fp); CHECK(InYoungGeneration(isolate, fp.handle)); fp.flag = false; fp.handle.SetWeak(&fp, gc_forcing_callback[inner_gc], v8::WeakCallbackType::kParameter); invoke_gc[outer_gc](); EmptyMessageQueues(); CHECK(fp.flag); } } } namespace { void SecondPassCallback(const v8::WeakCallbackInfo& data) { data.GetParameter()->flag = true; } void FirstPassCallback(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.SetSecondPassCallback(SecondPassCallback); } } // namespace TEST_F(GlobalHandlesTest, SecondPassPhantomCallbacks) { v8::Isolate* isolate = v8_isolate(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( i_isolate()->heap()); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); FlagAndGlobal fp; ConstructJSApiObject(isolate, context, &fp); fp.flag = false; fp.handle.SetWeak(&fp, FirstPassCallback, v8::WeakCallbackType::kParameter); CHECK(!fp.flag); CollectAllGarbage(); CollectAllGarbage(); CHECK(fp.flag); } TEST_F(GlobalHandlesTest, MoveStrongGlobal) { v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::Global* global = new Global(); ConstructJSObject(isolate, global); CollectAllGarbage(); v8::Global global2(std::move(*global)); delete global; CollectAllGarbage(); } TEST_F(GlobalHandlesTest, MoveWeakGlobal) { v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::Global* global = new Global(); ConstructJSObject(isolate, global); CollectAllGarbage(); global->SetWeak(); v8::Global global2(std::move(*global)); delete global; CollectAllGarbage(); } TEST_F(GlobalHandlesTest, TotalSizeRegularNode) { v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::Global* global = new Global(); CHECK_EQ(i_isolate()->global_handles()->TotalSize(), 0); CHECK_EQ(i_isolate()->global_handles()->UsedSize(), 0); ConstructJSObject(isolate, global); CHECK_GT(i_isolate()->global_handles()->TotalSize(), 0); CHECK_GT(i_isolate()->global_handles()->UsedSize(), 0); delete global; CHECK_GT(i_isolate()->global_handles()->TotalSize(), 0); CHECK_EQ(i_isolate()->global_handles()->UsedSize(), 0); } TEST_F(GlobalHandlesTest, TotalSizeTracedNode) { ManualGCScope manual_gc(i_isolate()); v8::Isolate* isolate = v8_isolate(); v8::HandleScope scope(isolate); v8::TracedReference* handle = new TracedReference(); CHECK_EQ(i_isolate()->traced_handles()->total_size_bytes(), 0); CHECK_EQ(i_isolate()->traced_handles()->used_size_bytes(), 0); ConstructJSObject(isolate, handle); CHECK_GT(i_isolate()->traced_handles()->total_size_bytes(), 0); CHECK_GT(i_isolate()->traced_handles()->used_size_bytes(), 0); delete handle; CollectAllGarbage(); CHECK_GT(i_isolate()->traced_handles()->total_size_bytes(), 0); CHECK_EQ(i_isolate()->traced_handles()->used_size_bytes(), 0); } } // namespace internal } // namespace v8