| // 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/api-inl.h" |
| #include "src/execution/isolate.h" |
| #include "src/handles/global-handles.h" |
| #include "src/heap/factory.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/objects/objects-inl.h" |
| #include "test/cctest/cctest.h" |
| #include "test/cctest/heap/heap-utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| // Empty v8::EmbedderHeapTracer that never keeps objects alive on Scavenge. See |
| // |IsRootForNonTracingGC|. |
| class NonRootingEmbedderHeapTracer final : public v8::EmbedderHeapTracer { |
| public: |
| NonRootingEmbedderHeapTracer() = default; |
| |
| void RegisterV8References( |
| const std::vector<std::pair<void*, void*>>& embedder_fields) final {} |
| bool AdvanceTracing(double deadline_in_ms) final { return true; } |
| bool IsTracingDone() final { return true; } |
| void TracePrologue(TraceFlags) final {} |
| void TraceEpilogue(TraceSummary*) final {} |
| void EnterFinalPause(EmbedderStackState) final {} |
| |
| bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final { |
| return false; |
| } |
| }; |
| |
| void InvokeScavenge() { CcTest::CollectGarbage(i::NEW_SPACE); } |
| |
| void InvokeMarkSweep() { CcTest::CollectAllGarbage(); } |
| |
| void SimpleCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| info.GetReturnValue().Set(v8_num(0)); |
| } |
| |
| struct FlagAndGlobal { |
| bool flag; |
| v8::Global<v8::Object> handle; |
| }; |
| |
| struct TracedGlobalWrapper { |
| v8::TracedGlobal<v8::Object> handle; |
| }; |
| |
| void ResetHandleAndSetFlag(const v8::WeakCallbackInfo<FlagAndGlobal>& data) { |
| data.GetParameter()->handle.Reset(); |
| data.GetParameter()->flag = true; |
| } |
| |
| template <typename HandleContainer> |
| void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context, |
| HandleContainer* flag_and_persistent) { |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::Object> 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<v8::Object>* global) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> object(v8::Object::New(isolate)); |
| CHECK(!object.IsEmpty()); |
| *global = v8::Global<v8::Object>(isolate, object); |
| CHECK(!global->IsEmpty()); |
| } |
| |
| void ConstructJSObject(v8::Isolate* isolate, |
| v8::TracedGlobal<v8::Object>* traced) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> object(v8::Object::New(isolate)); |
| CHECK(!object.IsEmpty()); |
| *traced = v8::TracedGlobal<v8::Object>(isolate, object); |
| CHECK(!traced->IsEmpty()); |
| } |
| |
| template <typename HandleContainer> |
| void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context, |
| HandleContainer* flag_and_persistent) { |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::FunctionTemplate> fun = |
| v8::FunctionTemplate::New(isolate, SimpleCallback); |
| v8::Local<v8::Object> 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 <typename ConstructFunction, typename ModifierFunction, |
| typename GCFunction> |
| void WeakHandleTest(v8::Isolate* isolate, ConstructFunction construct_function, |
| ModifierFunction modifier_function, GCFunction gc_function, |
| SurvivalMode survives) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| FlagAndGlobal fp; |
| construct_function(isolate, context, &fp); |
| CHECK(heap::InCorrectGeneration(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); |
| } |
| |
| template <typename ConstructFunction, typename ModifierFunction, |
| typename GCFunction> |
| void TracedGlobalTest(v8::Isolate* isolate, |
| ConstructFunction construct_function, |
| ModifierFunction modifier_function, |
| GCFunction gc_function, SurvivalMode survives) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| NonRootingEmbedderHeapTracer tracer; |
| heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); |
| |
| auto fp = std::make_unique<TracedGlobalWrapper>(); |
| construct_function(isolate, context, fp.get()); |
| CHECK(heap::InCorrectGeneration(isolate, fp->handle)); |
| modifier_function(fp.get()); |
| gc_function(); |
| CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !fp->handle.IsEmpty()); |
| CHECK_IMPLIES(survives == SurvivalMode::kDies, fp->handle.IsEmpty()); |
| } |
| |
| void ResurrectingFinalizer( |
| const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) { |
| data.GetParameter()->ClearWeak(); |
| } |
| |
| void ResettingFinalizer( |
| const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) { |
| data.GetParameter()->Reset(); |
| } |
| |
| void EmptyWeakCallback(const v8::WeakCallbackInfo<void>& data) {} |
| |
| void ResurrectingFinalizerSettingProperty( |
| const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) { |
| data.GetParameter()->ClearWeak(); |
| v8::Local<v8::Object> o = |
| v8::Local<v8::Object>::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<v8::Isolate*>(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<v8::Value> eternals[kArrayLength]; |
| |
| CHECK_EQ(0, eternal_handles->handles_count()); |
| for (int i = 0; i < kArrayLength; i++) { |
| indices[i] = -1; |
| HandleScope scope(isolate); |
| v8::Local<v8::Object> 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<v8::Value> 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<v8::Object> object = v8::Local<v8::Object>::Cast(local); |
| v8::Local<v8::Value> 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<v8::Object> object = v8::Object::New(v8_isolate); |
| v8::Eternal<v8::Object> eternal(v8_isolate, object); |
| CHECK(!eternal.IsEmpty()); |
| CHECK(object == eternal.Get(v8_isolate)); |
| } |
| |
| CHECK_EQ(2 * kArrayLength + 1, eternal_handles->handles_count()); |
| } |
| |
| |
| TEST(PersistentBaseGetLocal) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> o = v8::Object::New(isolate); |
| CHECK(!o.IsEmpty()); |
| v8::Persistent<v8::Object> p(isolate, o); |
| CHECK(o == p.Get(isolate)); |
| CHECK(v8::Local<v8::Object>::New(isolate, p) == p.Get(isolate)); |
| |
| v8::Global<v8::Object> g(isolate, o); |
| CHECK(o == g.Get(isolate)); |
| CHECK(v8::Local<v8::Object>::New(isolate, g) == g.Get(isolate)); |
| } |
| |
| TEST(WeakPersistentSmi) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Number> n = v8::Number::New(isolate, 0); |
| v8::Global<v8::Number> g(isolate, n); |
| |
| // Should not crash. |
| g.SetWeak<void>(nullptr, &EmptyWeakCallback, |
| v8::WeakCallbackType::kParameter); |
| } |
| |
| TEST(FinalizerWeakness) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| |
| v8::Global<v8::Object> g; |
| int identity; |
| |
| { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> 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<v8::Object> o = v8::Local<v8::Object>::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<v8::Object> 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(WeakHandleToUnmodifiedJSObjectDiesOnScavenge) { |
| if (FLAG_single_generation) return; |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) {}, []() { InvokeScavenge(); }, |
| SurvivalMode::kDies); |
| } |
| |
| TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavenge) { |
| if (FLAG_single_generation) return; |
| ManualGCScope manual_gc; |
| CcTest::InitializeVM(); |
| TracedGlobalTest( |
| CcTest::isolate(), &ConstructJSObject<TracedGlobalWrapper>, |
| [](TracedGlobalWrapper* fp) {}, []() { InvokeScavenge(); }, |
| SurvivalMode::kSurvives); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSObjectDiesOnMarkCompact) { |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) {}, []() { InvokeMarkSweep(); }, |
| SurvivalMode::kDies); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSObjectSurvivesMarkCompactWhenInHandle) { |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) { |
| v8::Local<v8::Object> handle = |
| v8::Local<v8::Object>::New(CcTest::isolate(), fp->handle); |
| USE(handle); |
| }, |
| []() { InvokeMarkSweep(); }, SurvivalMode::kSurvives); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSApiObjectDiesOnScavenge) { |
| if (FLAG_single_generation) return; |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSApiObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) {}, []() { InvokeScavenge(); }, |
| SurvivalMode::kDies); |
| } |
| |
| TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavenge) { |
| if (FLAG_single_generation) return; |
| ManualGCScope manual_gc; |
| CcTest::InitializeVM(); |
| TracedGlobalTest( |
| CcTest::isolate(), &ConstructJSApiObject<TracedGlobalWrapper>, |
| [](TracedGlobalWrapper* fp) {}, []() { InvokeScavenge(); }, |
| SurvivalMode::kDies); |
| } |
| |
| TEST(TracedGlobalToJSApiObjectWithIdentityHashSurvivesScavenge) { |
| ManualGCScope manual_gc; |
| CcTest::InitializeVM(); |
| Isolate* i_isolate = CcTest::i_isolate(); |
| HandleScope scope(i_isolate); |
| Handle<JSWeakMap> weakmap = i_isolate->factory()->NewJSWeakMap(); |
| |
| TracedGlobalTest( |
| CcTest::isolate(), &ConstructJSApiObject<TracedGlobalWrapper>, |
| [&weakmap, i_isolate](TracedGlobalWrapper* fp) { |
| v8::HandleScope scope(CcTest::isolate()); |
| Handle<JSReceiver> key = |
| Utils::OpenHandle(*fp->handle.Get(CcTest::isolate())); |
| Handle<Smi> smi(Smi::FromInt(23), i_isolate); |
| int32_t hash = key->GetOrCreateHash(i_isolate).value(); |
| JSWeakCollection::Set(weakmap, key, smi, hash); |
| }, |
| []() { InvokeScavenge(); }, SurvivalMode::kSurvives); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSApiObjectSurvivesScavengeWhenInHandle) { |
| if (FLAG_single_generation) return; |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSApiObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) { |
| v8::Local<v8::Object> handle = |
| v8::Local<v8::Object>::New(CcTest::isolate(), fp->handle); |
| USE(handle); |
| }, |
| []() { InvokeScavenge(); }, SurvivalMode::kSurvives); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSApiObjectDiesOnMarkCompact) { |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSApiObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) {}, []() { InvokeMarkSweep(); }, |
| SurvivalMode::kDies); |
| } |
| |
| TEST(WeakHandleToUnmodifiedJSApiObjectSurvivesMarkCompactWhenInHandle) { |
| CcTest::InitializeVM(); |
| WeakHandleTest( |
| CcTest::isolate(), &ConstructJSApiObject<FlagAndGlobal>, |
| [](FlagAndGlobal* fp) { |
| v8::Local<v8::Object> handle = |
| v8::Local<v8::Object>::New(CcTest::isolate(), fp->handle); |
| USE(handle); |
| }, |
| []() { InvokeMarkSweep(); }, SurvivalMode::kSurvives); |
| } |
| |
| TEST(TracedGlobalToJSApiObjectWithModifiedMapSurvivesScavenge) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| LocalContext context; |
| |
| TracedGlobal<v8::Object> 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(context.local()).ToLocalChecked(); |
| auto i = function->NewInstance(context.local()).ToLocalChecked(); |
| handle.Reset(isolate, i); |
| } |
| InvokeScavenge(); |
| CHECK(!handle.IsEmpty()); |
| } |
| |
| TEST(TracedGlobalTOJsApiObjectWithElementsSurvivesScavenge) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| LocalContext context; |
| |
| TracedGlobal<v8::Object> 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(context.local()).ToLocalChecked(); |
| auto i = function->NewInstance(context.local()).ToLocalChecked(); |
| handle.Reset(isolate, i); |
| } |
| InvokeScavenge(); |
| CHECK(!handle.IsEmpty()); |
| } |
| |
| TEST(FinalizerOnUnmodifiedJSApiObjectDoesNotCrash) { |
| // See crbug.com/v8/8586. |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| FlagAndGlobal fp; |
| ConstructJSApiObject(isolate, context, &fp); |
| fp.handle.SetWeak(&fp, &ResetHandleAndSetFlag, |
| v8::WeakCallbackType::kFinalizer); |
| fp.flag = false; |
| { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> tmp = v8::Local<v8::Object>::New(isolate, fp.handle); |
| USE(tmp); |
| InvokeScavenge(); |
| } |
| } |
| |
| namespace { |
| |
| void ConstructFinalizerPointingPhantomHandle( |
| v8::Isolate* isolate, v8::Global<v8::Object>* g1, |
| v8::Global<v8::Object>* g2, |
| typename v8::WeakCallbackInfo<v8::Global<v8::Object>>::Callback |
| finalizer_for_g1) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Object> o1 = |
| v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate)); |
| v8::Local<v8::Object> o2 = |
| v8::Local<v8::Object>::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<v8::Object> g1, g2; |
| ConstructFinalizerPointingPhantomHandle(CcTest::isolate(), &g1, &g2, |
| ResurrectingFinalizer); |
| InvokeMarkSweep(); |
| // 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()); |
| InvokeMarkSweep(); |
| // 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<v8::Object> g1, g2; |
| ConstructFinalizerPointingPhantomHandle(CcTest::isolate(), &g1, &g2, |
| ResettingFinalizer); |
| InvokeMarkSweep(); |
| // 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()); |
| InvokeMarkSweep(); |
| // Phantom handle dies after one more round. |
| CHECK(g1.IsEmpty()); |
| CHECK(g2.IsEmpty()); |
| } |
| |
| namespace { |
| |
| void ForceScavenge2(const v8::WeakCallbackInfo<FlagAndGlobal>& data) { |
| data.GetParameter()->flag = true; |
| InvokeScavenge(); |
| } |
| |
| void ForceScavenge1(const v8::WeakCallbackInfo<FlagAndGlobal>& data) { |
| data.GetParameter()->handle.Reset(); |
| data.SetSecondPassCallback(ForceScavenge2); |
| } |
| |
| void ForceMarkSweep2(const v8::WeakCallbackInfo<FlagAndGlobal>& data) { |
| data.GetParameter()->flag = true; |
| InvokeMarkSweep(); |
| } |
| |
| void ForceMarkSweep1(const v8::WeakCallbackInfo<FlagAndGlobal>& 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<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| if (FLAG_single_generation) { |
| FlagAndGlobal fp; |
| ConstructJSApiObject(isolate, context, &fp); |
| CHECK(!heap::InYoungGeneration(isolate, fp.handle)); |
| fp.flag = false; |
| fp.handle.SetWeak(&fp, &ForceMarkSweep1, v8::WeakCallbackType::kParameter); |
| InvokeMarkSweep(); |
| EmptyMessageQueues(isolate); |
| CHECK(fp.flag); |
| return; |
| } |
| |
| static const int kNumberOfGCTypes = 2; |
| using Callback = v8::WeakCallbackInfo<FlagAndGlobal>::Callback; |
| Callback gc_forcing_callback[kNumberOfGCTypes] = {&ForceScavenge1, |
| &ForceMarkSweep1}; |
| |
| using GCInvoker = void (*)(); |
| 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++) { |
| FlagAndGlobal fp; |
| ConstructJSApiObject(isolate, context, &fp); |
| CHECK(heap::InYoungGeneration(isolate, fp.handle)); |
| 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<FlagAndGlobal>& data) { |
| data.GetParameter()->flag = true; |
| } |
| |
| void FirstPassCallback(const v8::WeakCallbackInfo<FlagAndGlobal>& 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<v8::Context> 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); |
| InvokeMarkSweep(); |
| InvokeMarkSweep(); |
| CHECK(fp.flag); |
| } |
| |
| TEST(MoveStrongGlobal) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::Global<v8::Object>* global = new Global<v8::Object>(); |
| ConstructJSObject(isolate, global); |
| InvokeMarkSweep(); |
| v8::Global<v8::Object> global2(std::move(*global)); |
| delete global; |
| InvokeMarkSweep(); |
| } |
| |
| TEST(MoveWeakGlobal) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::Global<v8::Object>* global = new Global<v8::Object>(); |
| ConstructJSObject(isolate, global); |
| InvokeMarkSweep(); |
| global->SetWeak(); |
| v8::Global<v8::Object> global2(std::move(*global)); |
| delete global; |
| InvokeMarkSweep(); |
| } |
| |
| TEST(TotalSizeRegularNode) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| Isolate* i_isolate = CcTest::i_isolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::Global<v8::Object>* global = new Global<v8::Object>(); |
| 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(TotalSizeTracedNode) { |
| CcTest::InitializeVM(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| Isolate* i_isolate = CcTest::i_isolate(); |
| v8::HandleScope scope(isolate); |
| |
| v8::TracedGlobal<v8::Object>* global = new TracedGlobal<v8::Object>(); |
| 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); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |