// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/object_id_ring.h" #include "platform/assert.h" #include "vm/dart_api_impl.h" #include "vm/service.h" #include "vm/symbols.h" #include "vm/unit_test.h" namespace dart { #ifndef PRODUCT class ObjectIdRingTestHelper { public: static void SetMaxSerial(ObjectIdRing& ring, int32_t max_serial) { ring.max_serial_ = max_serial; } static void ExpectIdIsValid(ObjectIdRing& ring, intptr_t id) { EXPECT(ring.IsValidId(id)); } static void ExpectIdIsInvalid(ObjectIdRing& ring, intptr_t id) { EXPECT(!ring.IsValidId(id)); } static void ExpectIndexId(ObjectIdRing& ring, intptr_t index, intptr_t id) { EXPECT_EQ(id, ring.IdOfIndex(index)); } static void ExpectInvalidIndex(ObjectIdRing& ring, intptr_t index) { EXPECT_EQ(-1, ring.IdOfIndex(index)); } static ObjectPtr MakeString(const char* s) { return Symbols::New(Thread::Current(), s); } static void ExpectString(ObjectPtr obj, const char* s) { String& str = String::Handle(); str ^= obj; EXPECT(str.Equals(s)); } }; // Test that serial number wrapping works. ISOLATE_UNIT_TEST_CASE(ObjectIdRingSerialWrapTest) { ObjectIdRing ring(/*capacity=*/2); ObjectIdRingTestHelper::SetMaxSerial(ring, 4); intptr_t id; ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("0")); EXPECT_EQ(0, id); ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); ObjectIdRingTestHelper::ExpectInvalidIndex(ring, 1); id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("1")); EXPECT_EQ(1, id); ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); // Test that id 1 gives us the "1" string. ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "1"); EXPECT_EQ(ObjectIdRing::kValid, kind); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); // We have wrapped, index 0 is being reused. id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("2")); EXPECT_EQ(2, id); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 0); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); // Index 0 has id 2. ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 2); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 2); // Index 1 has id 1. ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("3")); EXPECT_EQ(3, id); // Index 0 has id 2. ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 2); // Index 1 has id 3. ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 3); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 0); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 2); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3); id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("4")); EXPECT_EQ(0, id); // Index 0 has id 0. ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); // Index 1 has id 3. ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 3); ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "4"); EXPECT_EQ(ObjectIdRing::kValid, kind); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3); id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("5")); EXPECT_EQ(1, id); // Index 0 has id 0. ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); // Index 1 has id 1. ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "5"); EXPECT_EQ(ObjectIdRing::kValid, kind); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); } class ServiceIdZonePolicyOverrideScope : public ValueObject { public: explicit ServiceIdZonePolicyOverrideScope(ServiceIdZone& id_zone, ObjectIdRing::IdPolicy new_policy) : id_zone_(id_zone), original_policy_(id_zone.policy()) { id_zone_.policy_ = new_policy; } ~ServiceIdZonePolicyOverrideScope() { id_zone_.policy_ = original_policy_; } private: ServiceIdZone& id_zone_; ObjectIdRing::IdPolicy original_policy_; }; // Test that the ring table is updated when the scavenger moves an object. TEST_CASE(ObjectIdRingScavengeMoveTest) { const char* kScriptChars = "main() {\n" " return [1, 2, 3];\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, nullptr); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr); Dart_Handle moved_handle; intptr_t list_length = 0; EXPECT_VALID(result); EXPECT(!Dart_IsNull(result)); EXPECT(Dart_IsList(result)); EXPECT_VALID(Dart_ListLength(result, &list_length)); EXPECT_EQ(3, list_length); ServiceIdZone& default_id_zone = thread->isolate()->EnsureDefaultServiceIdZone(); ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; { TransitionNativeToVM to_vm(thread); const ObjectPtr raw_obj = Api::UnwrapHandle(result); // Located in new heap. EXPECT(raw_obj->IsNewObject()); EXPECT_NE(Object::null(), raw_obj); intptr_t raw_obj_id1 = default_id_zone.GetIdForObject(raw_obj); EXPECT_EQ(0, raw_obj_id1); { ServiceIdZonePolicyOverrideScope override( default_id_zone, ObjectIdRing::IdPolicy::kReuseId); // Get id 0 again. EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_obj)); } // Add to ring a second time. intptr_t raw_obj_id2 = default_id_zone.GetIdForObject(raw_obj); EXPECT_EQ(1, raw_obj_id2); { ServiceIdZonePolicyOverrideScope override( default_id_zone, ObjectIdRing::IdPolicy::kReuseId); // Get id 0 again. EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_obj)); } const ObjectPtr raw_obj1 = default_id_zone.GetObjectForId(raw_obj_id1, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); const ObjectPtr raw_obj2 = default_id_zone.GetObjectForId(raw_obj_id2, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT_NE(Object::null(), raw_obj1); EXPECT_NE(Object::null(), raw_obj2); EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), UntaggedObject::ToAddr(raw_obj1)); EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), UntaggedObject::ToAddr(raw_obj2)); // Force a scavenge. GCTestHelper::CollectNewSpace(); const ObjectPtr raw_object_moved1 = default_id_zone.GetObjectForId(raw_obj_id1, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); const ObjectPtr raw_object_moved2 = default_id_zone.GetObjectForId(raw_obj_id2, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT_NE(Object::null(), raw_object_moved1); EXPECT_NE(Object::null(), raw_object_moved2); EXPECT_EQ(UntaggedObject::ToAddr(raw_object_moved1), UntaggedObject::ToAddr(raw_object_moved2)); // Test that objects have moved. EXPECT_NE(UntaggedObject::ToAddr(raw_obj1), UntaggedObject::ToAddr(raw_object_moved1)); EXPECT_NE(UntaggedObject::ToAddr(raw_obj2), UntaggedObject::ToAddr(raw_object_moved2)); // Test that we still point at the same list. moved_handle = Api::NewHandle(thread, raw_object_moved1); // Test id reuse. { ServiceIdZonePolicyOverrideScope override( default_id_zone, ObjectIdRing::IdPolicy::kReuseId); EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_object_moved1)); } } EXPECT_VALID(moved_handle); EXPECT(!Dart_IsNull(moved_handle)); EXPECT(Dart_IsList(moved_handle)); EXPECT_VALID(Dart_ListLength(moved_handle, &list_length)); EXPECT_EQ(3, list_length); } // Test that the ring table is updated when major GC runs. ISOLATE_UNIT_TEST_CASE(ObjectIdRingOldGCTest) { ServiceIdZone& default_id_zone = thread->isolate()->EnsureDefaultServiceIdZone(); ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; intptr_t raw_obj_id1 = -1; intptr_t raw_obj_id2 = -1; { HandleScope handle_scope(thread); const String& str = String::Handle(String::New("old", Heap::kOld)); EXPECT(!str.IsNull()); EXPECT_EQ(3, str.Length()); const ObjectPtr raw_obj = Object::RawCast(str.ptr()); // Verify that it is located in old heap. EXPECT(raw_obj->IsOldObject()); EXPECT_NE(Object::null(), raw_obj); raw_obj_id1 = default_id_zone.GetIdForObject(raw_obj); EXPECT_EQ(0, raw_obj_id1); raw_obj_id2 = default_id_zone.GetIdForObject(raw_obj); EXPECT_EQ(1, raw_obj_id2); const ObjectPtr raw_obj1 = default_id_zone.GetObjectForId(raw_obj_id1, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); const ObjectPtr raw_obj2 = default_id_zone.GetObjectForId(raw_obj_id2, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT_NE(Object::null(), raw_obj1); EXPECT_NE(Object::null(), raw_obj2); EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), UntaggedObject::ToAddr(raw_obj1)); EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), UntaggedObject::ToAddr(raw_obj2)); // Exit scope. Freeing String handle. } // Force a GC. No other reference to the old string exists, but the service id // should keep it alive. GCTestHelper::CollectOldSpace(); const ObjectPtr raw_object_moved1 = default_id_zone.GetObjectForId(raw_obj_id1, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT(raw_object_moved1->IsOneByteString()); const ObjectPtr raw_object_moved2 = default_id_zone.GetObjectForId(raw_obj_id2, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT(raw_object_moved2->IsOneByteString()); EXPECT_EQ(raw_object_moved1, raw_object_moved2); } // Test that the ring table correctly reports an entry as expired when it is // overridden by new entries. ISOLATE_UNIT_TEST_CASE(ObjectIdRingExpiredEntryTest) { ObjectIdRing ring(RingServiceIdZone::kFallbackCapacityForCreateIdZone); // Insert an object and check we can look it up. String& obj = String::Handle(String::New("I will expire")); intptr_t obj_id = ring.GetIdForObject(obj.ptr()); ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; ObjectPtr obj_lookup = ring.GetObjectForId(obj_id, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT_EQ(obj.ptr(), obj_lookup); // Insert as many new objects as the ring size to bump out our first entry. Object& new_obj = Object::Handle(); for (intptr_t i = 0; i < RingServiceIdZone::kFallbackCapacityForCreateIdZone; i++) { new_obj = String::New("Bump"); intptr_t new_obj_id = ring.GetIdForObject(new_obj.ptr()); ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; ObjectPtr new_obj_lookup = ring.GetObjectForId(new_obj_id, &kind); EXPECT_EQ(ObjectIdRing::kValid, kind); EXPECT_EQ(new_obj.ptr(), new_obj_lookup); } // Check our first entry reports it has expired. obj_lookup = ring.GetObjectForId(obj_id, &kind); EXPECT_EQ(ObjectIdRing::kExpired, kind); EXPECT_NE(obj.ptr(), obj_lookup); EXPECT_EQ(Object::null(), obj_lookup); } #endif // !PRODUCT } // namespace dart