// 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/api-inl.h" #include "src/global-handles.h" #include "src/heap/factory.h" #include "src/isolate.h" #include "src/objects-inl.h" #include "test/cctest/cctest.h" namespace v8 { namespace internal { namespace { void SimpleCallback(const v8::FunctionCallbackInfo& info) { info.GetReturnValue().Set(v8_num(0)); } struct FlagAndPersistent { bool flag; v8::Global handle; }; void ResetHandleAndSetFlag( const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.GetParameter()->flag = true; } using ConstructFunction = void (*)(v8::Isolate* isolate, v8::Local context, FlagAndPersistent* flag_and_persistent); void ConstructJSObject(v8::Isolate* isolate, v8::Local context, FlagAndPersistent* 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 ConstructJSApiObject(v8::Isolate* isolate, v8::Local context, FlagAndPersistent* 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); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); FlagAndPersistent fp; construct_function(isolate, context, &fp); { v8::HandleScope scope(isolate); v8::Local tmp = v8::Local::New(isolate, fp.handle); CHECK(i::Heap::InNewSpace(*v8::Utils::OpenHandle(*tmp))); } 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 ResurrectingFinalizer( const v8::WeakCallbackInfo>& data) { data.GetParameter()->ClearWeak(); } void ResettingFinalizer( const v8::WeakCallbackInfo>& data) { data.GetParameter()->Reset(); } void EmptyWeakCallback(const v8::WeakCallbackInfo& data) {} void ResurrectingFinalizerSettingProperty( const v8::WeakCallbackInfo>& data) { data.GetParameter()->ClearWeak(); v8::Local o = v8::Local::New(data.GetIsolate(), *data.GetParameter()); o->Set(data.GetIsolate()->GetCurrentContext(), v8_str("finalizer"), v8_str("was here")) .FromJust(); } } // namespace TEST(EternalHandles) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); v8::Isolate* v8_isolate = reinterpret_cast(isolate); EternalHandles* eternal_handles = isolate->eternal_handles(); // 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->NumberOfHandles()); 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()); } CcTest::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->NumberOfHandles()); // 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->NumberOfHandles()); } TEST(PersistentBaseGetLocal) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::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(WeakPersistentSmi) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::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(FinalizerWeakness) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::Global g; int identity; { v8::HandleScope scope(isolate); v8::Local o = v8::Object::New(isolate); identity = o->GetIdentityHash(); g.Reset(isolate, o); g.SetWeak(&g, &ResurrectingFinalizerSettingProperty, v8::WeakCallbackType::kFinalizer); } CcTest::CollectAllAvailableGarbage(); CHECK(!g.IsEmpty()); v8::HandleScope scope(isolate); v8::Local o = v8::Local::New(isolate, g); CHECK_EQ(identity, o->GetIdentityHash()); CHECK(o->Has(isolate->GetCurrentContext(), v8_str("finalizer")).FromJust()); } TEST(PhatomHandlesWithoutCallbacks) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); 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_EQ(0u, isolate->NumberOfPhantomHandleResetsSinceLastCall()); CcTest::CollectAllAvailableGarbage(); CHECK_EQ(2u, isolate->NumberOfPhantomHandleResetsSinceLastCall()); CHECK_EQ(0u, isolate->NumberOfPhantomHandleResetsSinceLastCall()); } TEST(WeakHandleToUnmodifiedJSObjectSurvivesScavenge) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSObject, [](FlagAndPersistent* fp) {}, []() { CcTest::CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kSurvives); } TEST(WeakHandleToUnmodifiedJSObjectDiesOnMarkCompact) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSObject, [](FlagAndPersistent* fp) {}, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kDies); } TEST(WeakHandleToUnmodifiedJSObjectSurvivesMarkCompactWhenInHandle) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSObject, [](FlagAndPersistent* fp) { v8::Local handle = v8::Local::New(CcTest::isolate(), fp->handle); USE(handle); }, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kSurvives); } TEST(WeakHandleToUnmodifiedJSApiObjectDiesOnScavenge) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) {}, []() { CcTest::CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kDies); } TEST(WeakHandleToUnmodifiedJSApiObjectSurvivesScavengeWhenInHandle) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) { v8::Local handle = v8::Local::New(CcTest::isolate(), fp->handle); USE(handle); }, []() { CcTest::CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kSurvives); } TEST(WeakHandleToUnmodifiedJSApiObjectDiesOnMarkCompact) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) {}, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kDies); } TEST(WeakHandleToUnmodifiedJSApiObjectSurvivesMarkCompactWhenInHandle) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) { v8::Local handle = v8::Local::New(CcTest::isolate(), fp->handle); USE(handle); }, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kSurvives); } TEST(WeakHandleToActiveUnmodifiedJSApiObjectSurvivesScavenge) { CcTest::InitializeVM(); WeakHandleTest(CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) { fp->handle.MarkActive(); }, []() { CcTest::CollectGarbage(i::NEW_SPACE); }, SurvivalMode::kSurvives); } TEST(WeakHandleToActiveUnmodifiedJSApiObjectDiesOnMarkCompact) { CcTest::InitializeVM(); WeakHandleTest(CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) { fp->handle.MarkActive(); }, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kDies); } TEST(WeakHandleToActiveUnmodifiedJSApiObjectSurvivesMarkCompactWhenInHandle) { CcTest::InitializeVM(); WeakHandleTest( CcTest::isolate(), &ConstructJSApiObject, [](FlagAndPersistent* fp) { fp->handle.MarkActive(); v8::Local handle = v8::Local::New(CcTest::isolate(), fp->handle); USE(handle); }, []() { CcTest::CollectGarbage(i::OLD_SPACE); }, SurvivalMode::kSurvives); } namespace { void ConstructFinalizerPointingPhantomHandle( v8::Isolate* isolate, v8::Global* g1, v8::Global* g2, typename v8::WeakCallbackInfo>::Callback finalizer_for_g1) { v8::HandleScope scope(isolate); v8::Local o1 = v8::Local::New(isolate, v8::Object::New(isolate)); v8::Local o2 = v8::Local::New(isolate, v8::Object::New(isolate)); o1->Set(isolate->GetCurrentContext(), v8_str("link"), o2).FromJust(); g1->Reset(isolate, o1); g2->Reset(isolate, o2); // g1 will be finalized but resurrected. g1->SetWeak(g1, finalizer_for_g1, v8::WeakCallbackType::kFinalizer); // g2 will be a phantom handle that is dependent on the finalizer handle // g1 as it is in its subgraph. g2->SetWeak(); } } // namespace TEST(FinalizerResurrectsAndKeepsPhantomAliveOnMarkCompact) { // See crbug.com/772299. CcTest::InitializeVM(); v8::Global g1, g2; ConstructFinalizerPointingPhantomHandle(CcTest::isolate(), &g1, &g2, ResurrectingFinalizer); CcTest::CollectGarbage(i::OLD_SPACE); // Both, g1 and g2, should stay alive as the finalizer resurrects the root // object that transitively keeps the other one alive. CHECK(!g1.IsEmpty()); CHECK(!g2.IsEmpty()); CcTest::CollectGarbage(i::OLD_SPACE); // The finalizer handle is now strong, so it should keep the objects alive. CHECK(!g1.IsEmpty()); CHECK(!g2.IsEmpty()); } TEST(FinalizerDiesAndKeepsPhantomAliveOnMarkCompact) { CcTest::InitializeVM(); v8::Global g1, g2; ConstructFinalizerPointingPhantomHandle(CcTest::isolate(), &g1, &g2, ResettingFinalizer); CcTest::CollectGarbage(i::OLD_SPACE); // Finalizer (g1) dies but the phantom handle (g2) is kept alive for one // more round as the underlying object only dies on the next GC. CHECK(g1.IsEmpty()); CHECK(!g2.IsEmpty()); CcTest::CollectGarbage(i::OLD_SPACE); // Phantom handle dies after one more round. CHECK(g1.IsEmpty()); CHECK(g2.IsEmpty()); } namespace { void InvokeScavenge() { CcTest::CollectGarbage(i::NEW_SPACE); } void InvokeMarkSweep() { CcTest::CollectAllGarbage(); } void ForceScavenge2(const v8::WeakCallbackInfo& data) { data.GetParameter()->flag = true; InvokeScavenge(); } void ForceScavenge1(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.SetSecondPassCallback(ForceScavenge2); } void ForceMarkSweep2(const v8::WeakCallbackInfo& data) { data.GetParameter()->flag = true; InvokeMarkSweep(); } void ForceMarkSweep1(const v8::WeakCallbackInfo& data) { data.GetParameter()->handle.Reset(); data.SetSecondPassCallback(ForceMarkSweep2); } } // namespace TEST(GCFromWeakCallbacks) { v8::Isolate* isolate = CcTest::isolate(); v8::Locker locker(CcTest::isolate()); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); static const int kNumberOfGCTypes = 2; typedef v8::WeakCallbackInfo::Callback Callback; Callback gc_forcing_callback[kNumberOfGCTypes] = {&ForceScavenge1, &ForceMarkSweep1}; typedef void (*GCInvoker)(); GCInvoker invoke_gc[kNumberOfGCTypes] = {&InvokeScavenge, &InvokeMarkSweep}; for (int outer_gc = 0; outer_gc < kNumberOfGCTypes; outer_gc++) { for (int inner_gc = 0; inner_gc < kNumberOfGCTypes; inner_gc++) { FlagAndPersistent fp; ConstructJSApiObject(isolate, context, &fp); { v8::HandleScope scope(isolate); v8::Local tmp = v8::Local::New(isolate, fp.handle); CHECK(i::Heap::InNewSpace(*v8::Utils::OpenHandle(*tmp))); } fp.flag = false; fp.handle.SetWeak(&fp, gc_forcing_callback[inner_gc], v8::WeakCallbackType::kParameter); invoke_gc[outer_gc](); EmptyMessageQueues(isolate); 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(SecondPassPhantomCallbacks) { v8::Isolate* isolate = CcTest::isolate(); v8::Locker locker(CcTest::isolate()); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); FlagAndPersistent fp; ConstructJSApiObject(isolate, context, &fp); fp.flag = false; fp.handle.SetWeak(&fp, FirstPassCallback, v8::WeakCallbackType::kParameter); CHECK(!fp.flag); CcTest::CollectGarbage(i::OLD_SPACE); CcTest::CollectGarbage(i::OLD_SPACE); CHECK(fp.flag); } } // namespace internal } // namespace v8