1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/object_graph.h" |
6 | #include "platform/assert.h" |
7 | #include "vm/unit_test.h" |
8 | |
9 | namespace dart { |
10 | |
11 | #if !defined(PRODUCT) |
12 | |
13 | class CounterVisitor : public ObjectGraph::Visitor { |
14 | public: |
15 | // Records the number of objects and total size visited, excluding 'skip' |
16 | // and any objects only reachable through 'skip'. |
17 | CounterVisitor(ObjectPtr skip, ObjectPtr expected_parent) |
18 | : count_(0), size_(0), skip_(skip), expected_parent_(expected_parent) {} |
19 | |
20 | virtual Direction VisitObject(ObjectGraph::StackIterator* it) { |
21 | ObjectPtr obj = it->Get(); |
22 | if (obj == skip_) { |
23 | EXPECT(it->MoveToParent()); |
24 | EXPECT_EQ(expected_parent_, it->Get()); |
25 | return kBacktrack; |
26 | } |
27 | ++count_; |
28 | size_ += obj->ptr()->HeapSize(); |
29 | return kProceed; |
30 | } |
31 | |
32 | int count() const { return count_; } |
33 | int size() const { return size_; } |
34 | |
35 | private: |
36 | int count_; |
37 | intptr_t size_; |
38 | ObjectPtr skip_; |
39 | ObjectPtr expected_parent_; |
40 | }; |
41 | |
42 | ISOLATE_UNIT_TEST_CASE(ObjectGraph) { |
43 | Isolate* isolate = thread->isolate(); |
44 | // Create a simple object graph with objects a, b, c, d: |
45 | // a+->b+->c |
46 | // + + |
47 | // | v |
48 | // +-->d |
49 | Array& a = Array::Handle(Array::New(12, Heap::kNew)); |
50 | Array& b = Array::Handle(Array::New(2, Heap::kOld)); |
51 | Array& c = Array::Handle(Array::New(0, Heap::kOld)); |
52 | Array& d = Array::Handle(Array::New(0, Heap::kOld)); |
53 | a.SetAt(10, b); |
54 | b.SetAt(0, c); |
55 | b.SetAt(1, d); |
56 | a.SetAt(11, d); |
57 | intptr_t a_size = a.raw()->ptr()->HeapSize(); |
58 | intptr_t b_size = b.raw()->ptr()->HeapSize(); |
59 | intptr_t c_size = c.raw()->ptr()->HeapSize(); |
60 | intptr_t d_size = d.raw()->ptr()->HeapSize(); |
61 | { |
62 | // No more allocation; raw pointers ahead. |
63 | SafepointOperationScope safepoint(thread); |
64 | ObjectPtr b_raw = b.raw(); |
65 | // Clear handles to cut unintended retained paths. |
66 | b = Array::null(); |
67 | c = Array::null(); |
68 | d = Array::null(); |
69 | ObjectGraph graph(thread); |
70 | { |
71 | HeapIterationScope iteration_scope(thread, true); |
72 | { |
73 | // Compare count and size when 'b' is/isn't skipped. |
74 | CounterVisitor with(Object::null(), Object::null()); |
75 | graph.IterateObjectsFrom(a, &with); |
76 | CounterVisitor without(b_raw, a.raw()); |
77 | graph.IterateObjectsFrom(a, &without); |
78 | // Only 'b' and 'c' were cut off. |
79 | EXPECT_EQ(2, with.count() - without.count()); |
80 | EXPECT_EQ(b_size + c_size, with.size() - without.size()); |
81 | } |
82 | { |
83 | // Like above, but iterate over the entire isolate. The counts and sizes |
84 | // are thus larger, but the difference should still be just 'b' and 'c'. |
85 | CounterVisitor with(Object::null(), Object::null()); |
86 | graph.IterateObjects(&with); |
87 | CounterVisitor without(b_raw, a.raw()); |
88 | graph.IterateObjects(&without); |
89 | EXPECT_EQ(2, with.count() - without.count()); |
90 | EXPECT_EQ(b_size + c_size, with.size() - without.size()); |
91 | } |
92 | } |
93 | EXPECT_EQ(a_size + b_size + c_size + d_size, |
94 | graph.SizeRetainedByInstance(a)); |
95 | } |
96 | { |
97 | // Get hold of c again. |
98 | b ^= a.At(10); |
99 | c ^= b.At(0); |
100 | b = Array::null(); |
101 | ObjectGraph graph(thread); |
102 | // A retaining path should end like this: c <- b <- a <- ... |
103 | { |
104 | HANDLESCOPE(thread); |
105 | // Test null, empty, and length 1 array. |
106 | intptr_t null_length = |
107 | graph.RetainingPath(&c, Object::null_array()).length; |
108 | intptr_t empty_length = |
109 | graph.RetainingPath(&c, Object::empty_array()).length; |
110 | Array& path = Array::Handle(Array::New(1, Heap::kNew)); |
111 | intptr_t one_length = graph.RetainingPath(&c, path).length; |
112 | EXPECT_EQ(null_length, empty_length); |
113 | EXPECT_EQ(null_length, one_length); |
114 | EXPECT_LE(3, null_length); |
115 | } |
116 | { |
117 | HANDLESCOPE(thread); |
118 | Array& path = Array::Handle(Array::New(6, Heap::kNew)); |
119 | // Trigger a full GC to increase probability of concurrent tasks. |
120 | isolate->heap()->CollectAllGarbage(); |
121 | intptr_t length = graph.RetainingPath(&c, path).length; |
122 | EXPECT_LE(3, length); |
123 | Array& expected_c = Array::Handle(); |
124 | expected_c ^= path.At(0); |
125 | // c is the first element in b. |
126 | Smi& offset_from_parent = Smi::Handle(); |
127 | offset_from_parent ^= path.At(1); |
128 | EXPECT_EQ(Array::element_offset(0), |
129 | offset_from_parent.Value() * kWordSize); |
130 | Array& expected_b = Array::Handle(); |
131 | expected_b ^= path.At(2); |
132 | // b is the element with index 10 in a. |
133 | offset_from_parent ^= path.At(3); |
134 | EXPECT_EQ(Array::element_offset(10), |
135 | offset_from_parent.Value() * kWordSize); |
136 | Array& expected_a = Array::Handle(); |
137 | expected_a ^= path.At(4); |
138 | EXPECT(expected_c.raw() == c.raw()); |
139 | EXPECT(expected_b.raw() == a.At(10)); |
140 | EXPECT(expected_a.raw() == a.raw()); |
141 | } |
142 | } |
143 | } |
144 | |
145 | static void WeakHandleFinalizer(void* isolate_callback_data, |
146 | Dart_WeakPersistentHandle handle, |
147 | void* peer) {} |
148 | |
149 | ISOLATE_UNIT_TEST_CASE(RetainingPathGCRoot) { |
150 | Dart_PersistentHandle persistent_handle; |
151 | Dart_WeakPersistentHandle weak_persistent_handle; |
152 | Array& path = Array::Handle(Array::New(1, Heap::kNew)); |
153 | ObjectGraph graph(thread); |
154 | Dart_Handle handle = Api::NewHandle(thread, path.raw()); |
155 | |
156 | // GC root should be a local handle |
157 | auto result = graph.RetainingPath(&path, path); |
158 | EXPECT_STREQ(result.gc_root_type, "local handle" ); |
159 | |
160 | // GC root should now be a weak persistent handle |
161 | { |
162 | TransitionVMToNative transition(thread); |
163 | weak_persistent_handle = Dart_NewWeakPersistentHandle( |
164 | handle, reinterpret_cast<void*>(0xdeadbeef), 128, WeakHandleFinalizer); |
165 | } |
166 | result = graph.RetainingPath(&path, path); |
167 | EXPECT_STREQ(result.gc_root_type, "weak persistent handle" ); |
168 | |
169 | // GC root should now be a persistent handle |
170 | { |
171 | TransitionVMToNative transition(thread); |
172 | persistent_handle = Dart_NewPersistentHandle(handle); |
173 | } |
174 | result = graph.RetainingPath(&path, path); |
175 | EXPECT_STREQ(result.gc_root_type, "persistent handle" ); |
176 | |
177 | // Delete the persistent handle. GC root should now be weak persistent handle |
178 | { |
179 | TransitionVMToNative transition(thread); |
180 | Dart_DeletePersistentHandle(persistent_handle); |
181 | persistent_handle = NULL; |
182 | } |
183 | result = graph.RetainingPath(&path, path); |
184 | EXPECT_STREQ(result.gc_root_type, "weak persistent handle" ); |
185 | |
186 | // Delete the weak persistent handle. GC root should now be local handle. |
187 | { |
188 | TransitionVMToNative transition(thread); |
189 | Dart_DeleteWeakPersistentHandle(weak_persistent_handle); |
190 | weak_persistent_handle = NULL; |
191 | } |
192 | result = graph.RetainingPath(&path, path); |
193 | EXPECT_STREQ(result.gc_root_type, "local handle" ); |
194 | } |
195 | |
196 | #endif // !defined(PRODUCT) |
197 | |
198 | } // namespace dart |
199 | |