1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "local_reference_table-inl.h"
18
19 #include "android-base/stringprintf.h"
20
21 #include "class_root-inl.h"
22 #include "common_runtime_test.h"
23 #include "mirror/class-alloc-inl.h"
24 #include "mirror/object-inl.h"
25 #include "scoped_thread_state_change-inl.h"
26
27 namespace art HIDDEN {
28 namespace jni {
29
30 using android::base::StringPrintf;
31
32 class LocalReferenceTableTest : public CommonRuntimeTest {
33 protected:
LocalReferenceTableTest()34 LocalReferenceTableTest() {
35 use_boot_image_ = true; // Make the Runtime creation cheaper.
36 }
37
38 static void CheckDump(LocalReferenceTable* lrt, size_t num_objects, size_t num_unique)
39 REQUIRES_SHARED(Locks::mutator_lock_);
40
41 void BasicTest(bool check_jni, size_t max_count);
42 void BasicHolesTest(bool check_jni, size_t max_count);
43 void BasicResizeTest(bool check_jni, size_t max_count);
44 void TestAddRemove(bool check_jni, size_t max_count, size_t fill_count = 0u);
45 void TestAddRemoveMixed(bool start_check_jni);
46 };
47
CheckDump(LocalReferenceTable * lrt,size_t num_objects,size_t num_unique)48 void LocalReferenceTableTest::CheckDump(
49 LocalReferenceTable* lrt, size_t num_objects, size_t num_unique) {
50 std::ostringstream oss;
51 lrt->Dump(oss);
52 if (num_objects == 0) {
53 EXPECT_EQ(oss.str().find("java.lang.Object"), std::string::npos) << oss.str();
54 } else if (num_objects == 1) {
55 EXPECT_NE(oss.str().find("1 of java.lang.Object"), std::string::npos) << oss.str();
56 } else {
57 EXPECT_NE(oss.str().find(StringPrintf("%zd of java.lang.Object (%zd unique instances)",
58 num_objects, num_unique)),
59 std::string::npos)
60 << "\n Expected number of objects: " << num_objects
61 << "\n Expected unique objects: " << num_unique << "\n"
62 << oss.str();
63 }
64 }
65
BasicTest(bool check_jni,size_t max_count)66 void LocalReferenceTableTest::BasicTest(bool check_jni, size_t max_count) {
67 // This will lead to error messages in the log.
68 ScopedLogSeverity sls(LogSeverity::FATAL);
69
70 ScopedObjectAccess soa(Thread::Current());
71 StackHandleScope<5> hs(soa.Self());
72 Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
73 ASSERT_TRUE(c != nullptr);
74 Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
75 ASSERT_TRUE(obj0 != nullptr);
76 Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
77 ASSERT_TRUE(obj1 != nullptr);
78 Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
79 ASSERT_TRUE(obj2 != nullptr);
80 Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
81 ASSERT_TRUE(obj3 != nullptr);
82
83 std::string error_msg;
84 LocalReferenceTable lrt(check_jni);
85 bool success = lrt.Initialize(max_count, &error_msg);
86 ASSERT_TRUE(success) << error_msg;
87
88 CheckDump(&lrt, 0, 0);
89
90 if (check_jni) {
91 IndirectRef bad_iref = (IndirectRef) 0x11110;
92 EXPECT_FALSE(lrt.Remove(bad_iref)) << "unexpectedly successful removal";
93 }
94
95 // Add three, check, remove in the order in which they were added.
96 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
97 EXPECT_TRUE(iref0 != nullptr);
98 CheckDump(&lrt, 1, 1);
99 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
100 EXPECT_TRUE(iref1 != nullptr);
101 CheckDump(&lrt, 2, 2);
102 IndirectRef iref2 = lrt.Add(obj2.Get(), &error_msg);
103 EXPECT_TRUE(iref2 != nullptr);
104 CheckDump(&lrt, 3, 3);
105
106 EXPECT_OBJ_PTR_EQ(obj0.Get(), lrt.Get(iref0));
107 EXPECT_OBJ_PTR_EQ(obj1.Get(), lrt.Get(iref1));
108 EXPECT_OBJ_PTR_EQ(obj2.Get(), lrt.Get(iref2));
109
110 EXPECT_TRUE(lrt.Remove(iref0));
111 CheckDump(&lrt, 2, 2);
112 EXPECT_TRUE(lrt.Remove(iref1));
113 CheckDump(&lrt, 1, 1);
114 EXPECT_TRUE(lrt.Remove(iref2));
115 CheckDump(&lrt, 0, 0);
116
117 // Table should be empty now.
118 EXPECT_EQ(0U, lrt.Capacity());
119
120 // Check that the entry off the end of the list is not valid.
121 // (CheckJNI shall abort for such entries.)
122 EXPECT_FALSE(lrt.IsValidReference(iref0, &error_msg));
123
124 // Add three, remove in the opposite order.
125 iref0 = lrt.Add(obj0.Get(), &error_msg);
126 EXPECT_TRUE(iref0 != nullptr);
127 iref1 = lrt.Add(obj1.Get(), &error_msg);
128 EXPECT_TRUE(iref1 != nullptr);
129 iref2 = lrt.Add(obj2.Get(), &error_msg);
130 EXPECT_TRUE(iref2 != nullptr);
131 CheckDump(&lrt, 3, 3);
132
133 ASSERT_TRUE(lrt.Remove(iref2));
134 CheckDump(&lrt, 2, 2);
135 ASSERT_TRUE(lrt.Remove(iref1));
136 CheckDump(&lrt, 1, 1);
137 ASSERT_TRUE(lrt.Remove(iref0));
138 CheckDump(&lrt, 0, 0);
139
140 // Table should be empty now.
141 ASSERT_EQ(0U, lrt.Capacity());
142
143 // Add three, remove middle / middle / bottom / top. (Second attempt
144 // to remove middle should fail.)
145 iref0 = lrt.Add(obj0.Get(), &error_msg);
146 EXPECT_TRUE(iref0 != nullptr);
147 iref1 = lrt.Add(obj1.Get(), &error_msg);
148 EXPECT_TRUE(iref1 != nullptr);
149 iref2 = lrt.Add(obj2.Get(), &error_msg);
150 EXPECT_TRUE(iref2 != nullptr);
151 CheckDump(&lrt, 3, 3);
152
153 ASSERT_EQ(3U, lrt.Capacity());
154
155 ASSERT_TRUE(lrt.Remove(iref1));
156 CheckDump(&lrt, 2, 2);
157 if (check_jni) {
158 ASSERT_FALSE(lrt.Remove(iref1));
159 CheckDump(&lrt, 2, 2);
160 }
161
162 // Check that the reference to the hole is not valid.
163 EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
164
165 ASSERT_TRUE(lrt.Remove(iref2));
166 CheckDump(&lrt, 1, 1);
167 ASSERT_TRUE(lrt.Remove(iref0));
168 CheckDump(&lrt, 0, 0);
169
170 // Table should be empty now.
171 ASSERT_EQ(0U, lrt.Capacity());
172
173 // Add four entries. Remove #1, add new entry, verify that table size
174 // is still 4 (i.e. holes are getting filled). Remove #1 and #3, verify
175 // that we delete one and don't hole-compact the other.
176 iref0 = lrt.Add(obj0.Get(), &error_msg);
177 EXPECT_TRUE(iref0 != nullptr);
178 iref1 = lrt.Add(obj1.Get(), &error_msg);
179 EXPECT_TRUE(iref1 != nullptr);
180 iref2 = lrt.Add(obj2.Get(), &error_msg);
181 EXPECT_TRUE(iref2 != nullptr);
182 IndirectRef iref3 = lrt.Add(obj3.Get(), &error_msg);
183 EXPECT_TRUE(iref3 != nullptr);
184 CheckDump(&lrt, 4, 4);
185
186 ASSERT_TRUE(lrt.Remove(iref1));
187 CheckDump(&lrt, 3, 3);
188
189 iref1 = lrt.Add(obj1.Get(), &error_msg);
190 EXPECT_TRUE(iref1 != nullptr);
191
192 ASSERT_EQ(4U, lrt.Capacity()) << "hole not filled";
193 CheckDump(&lrt, 4, 4);
194
195 ASSERT_TRUE(lrt.Remove(iref1));
196 CheckDump(&lrt, 3, 3);
197 ASSERT_TRUE(lrt.Remove(iref3));
198 CheckDump(&lrt, 2, 2);
199
200 ASSERT_EQ(3U, lrt.Capacity()) << "should be 3 after two deletions";
201
202 ASSERT_TRUE(lrt.Remove(iref2));
203 CheckDump(&lrt, 1, 1);
204 ASSERT_TRUE(lrt.Remove(iref0));
205 CheckDump(&lrt, 0, 0);
206
207 ASSERT_EQ(0U, lrt.Capacity()) << "not empty after split remove";
208
209 // Add an entry, remove it, add a new entry, and try to use the original
210 // iref. They have the same slot number but are for different objects.
211 // With the extended checks in place, this should fail.
212 iref0 = lrt.Add(obj0.Get(), &error_msg);
213 EXPECT_TRUE(iref0 != nullptr);
214 CheckDump(&lrt, 1, 1);
215 ASSERT_TRUE(lrt.Remove(iref0));
216 CheckDump(&lrt, 0, 0);
217 iref1 = lrt.Add(obj1.Get(), &error_msg);
218 EXPECT_TRUE(iref1 != nullptr);
219 CheckDump(&lrt, 1, 1);
220 if (check_jni) {
221 ASSERT_FALSE(lrt.Remove(iref0)) << "mismatched del succeeded";
222 CheckDump(&lrt, 1, 1);
223 }
224 ASSERT_TRUE(lrt.Remove(iref1)) << "switched del failed";
225 ASSERT_EQ(0U, lrt.Capacity()) << "switching del not empty";
226 CheckDump(&lrt, 0, 0);
227
228 // Same as above, but with the same object. A more rigorous checker
229 // (e.g. with slot serialization) will catch this.
230 iref0 = lrt.Add(obj0.Get(), &error_msg);
231 EXPECT_TRUE(iref0 != nullptr);
232 CheckDump(&lrt, 1, 1);
233 ASSERT_TRUE(lrt.Remove(iref0));
234 CheckDump(&lrt, 0, 0);
235 iref1 = lrt.Add(obj0.Get(), &error_msg);
236 EXPECT_TRUE(iref1 != nullptr);
237 CheckDump(&lrt, 1, 1);
238 if (iref0 != iref1) {
239 // Try 0, should not work.
240 ASSERT_FALSE(lrt.Remove(iref0)) << "temporal del succeeded";
241 }
242 ASSERT_TRUE(lrt.Remove(iref1)) << "temporal cleanup failed";
243 ASSERT_EQ(0U, lrt.Capacity()) << "temporal del not empty";
244 CheckDump(&lrt, 0, 0);
245
246 // Stale reference is not valid.
247 iref0 = lrt.Add(obj0.Get(), &error_msg);
248 EXPECT_TRUE(iref0 != nullptr);
249 CheckDump(&lrt, 1, 1);
250 ASSERT_TRUE(lrt.Remove(iref0));
251 EXPECT_FALSE(lrt.IsValidReference(iref0, &error_msg)) << "stale lookup succeeded";
252 CheckDump(&lrt, 0, 0);
253
254 // Test table resizing.
255 // These ones fit...
256 static const size_t kTableInitial = max_count / 2;
257 IndirectRef manyRefs[kTableInitial];
258 for (size_t i = 0; i < kTableInitial; i++) {
259 manyRefs[i] = lrt.Add(obj0.Get(), &error_msg);
260 ASSERT_TRUE(manyRefs[i] != nullptr) << "Failed adding " << i;
261 CheckDump(&lrt, i + 1, 1);
262 }
263 // ...this one causes overflow.
264 iref0 = lrt.Add(obj0.Get(), &error_msg);
265 ASSERT_TRUE(iref0 != nullptr);
266 ASSERT_EQ(kTableInitial + 1, lrt.Capacity());
267 CheckDump(&lrt, kTableInitial + 1, 1);
268
269 for (size_t i = 0; i < kTableInitial; i++) {
270 ASSERT_TRUE(lrt.Remove(manyRefs[i])) << "failed removing " << i;
271 CheckDump(&lrt, kTableInitial - i, 1);
272 }
273 // Because of removal order, should have 11 entries, 10 of them holes.
274 ASSERT_EQ(kTableInitial + 1, lrt.Capacity());
275
276 ASSERT_TRUE(lrt.Remove(iref0)) << "multi-remove final failed";
277
278 ASSERT_EQ(0U, lrt.Capacity()) << "multi-del not empty";
279 CheckDump(&lrt, 0, 0);
280 }
281
TEST_F(LocalReferenceTableTest,BasicTest)282 TEST_F(LocalReferenceTableTest, BasicTest) {
283 BasicTest(/*check_jni=*/ false, /*max_count=*/ 20u);
284 BasicTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
285 BasicTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
286 }
287
TEST_F(LocalReferenceTableTest,BasicTestCheckJNI)288 TEST_F(LocalReferenceTableTest, BasicTestCheckJNI) {
289 BasicTest(/*check_jni=*/ true, /*max_count=*/ 20u);
290 BasicTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
291 BasicTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
292 }
293
BasicHolesTest(bool check_jni,size_t max_count)294 void LocalReferenceTableTest::BasicHolesTest(bool check_jni, size_t max_count) {
295 // Test the explicitly named cases from the LRT implementation:
296 //
297 // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference
298 // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
299 // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
300 // reference
301 // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference
302 // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
303 // reference
304
305 ScopedObjectAccess soa(Thread::Current());
306 StackHandleScope<6> hs(soa.Self());
307 Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
308 ASSERT_TRUE(c != nullptr);
309 Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
310 ASSERT_TRUE(obj0 != nullptr);
311 Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
312 ASSERT_TRUE(obj1 != nullptr);
313 Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
314 ASSERT_TRUE(obj2 != nullptr);
315 Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
316 ASSERT_TRUE(obj3 != nullptr);
317 Handle<mirror::Object> obj4 = hs.NewHandle(c->AllocObject(soa.Self()));
318 ASSERT_TRUE(obj4 != nullptr);
319
320 std::string error_msg;
321
322 // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference.
323 {
324 LocalReferenceTable lrt(check_jni);
325 bool success = lrt.Initialize(max_count, &error_msg);
326 ASSERT_TRUE(success) << error_msg;
327
328 CheckDump(&lrt, 0, 0);
329
330 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
331 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
332 IndirectRef iref2 = lrt.Add(obj2.Get(), &error_msg);
333
334 EXPECT_TRUE(lrt.Remove(iref1));
335 EXPECT_EQ(lrt.Capacity(), 3u);
336
337 // New segment.
338 const LRTSegmentState cookie = lrt.PushFrame();
339
340 IndirectRef iref3 = lrt.Add(obj3.Get(), &error_msg);
341
342 // Must not have filled the previous hole.
343 EXPECT_EQ(lrt.Capacity(), 4u);
344 EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
345 CheckDump(&lrt, 3, 3);
346
347 lrt.PopFrame(cookie);
348 EXPECT_EQ(lrt.Capacity(), 3u);
349
350 UNUSED(iref0, iref1, iref2, iref3);
351 }
352
353 // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
354 {
355 LocalReferenceTable lrt(check_jni);
356 bool success = lrt.Initialize(max_count, &error_msg);
357 ASSERT_TRUE(success) << error_msg;
358
359 CheckDump(&lrt, 0, 0);
360
361 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
362
363 // New segment.
364 const LRTSegmentState cookie = lrt.PushFrame();
365
366 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
367 IndirectRef iref2 = lrt.Add(obj2.Get(), &error_msg);
368 IndirectRef iref3 = lrt.Add(obj3.Get(), &error_msg);
369
370 EXPECT_TRUE(lrt.Remove(iref2));
371
372 // Pop segment.
373 lrt.PopFrame(cookie);
374
375 IndirectRef iref4 = lrt.Add(obj4.Get(), &error_msg);
376
377 EXPECT_EQ(lrt.Capacity(), 2u);
378 EXPECT_FALSE(lrt.IsValidReference(iref2, &error_msg));
379 CheckDump(&lrt, 2, 2);
380
381 UNUSED(iref0, iref1, iref2, iref3, iref4);
382 }
383
384 // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
385 // reference.
386 {
387 LocalReferenceTable lrt(check_jni);
388 bool success = lrt.Initialize(max_count, &error_msg);
389 ASSERT_TRUE(success) << error_msg;
390
391 CheckDump(&lrt, 0, 0);
392
393 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
394
395 // New segment.
396 const LRTSegmentState cookie0 = lrt.PushFrame();
397
398 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
399 IndirectRef iref2 = lrt.Add(obj2.Get(), &error_msg);
400
401 EXPECT_TRUE(lrt.Remove(iref1));
402
403 // New segment.
404 const LRTSegmentState cookie1 = lrt.PushFrame();
405
406 IndirectRef iref3 = lrt.Add(obj3.Get(), &error_msg);
407
408 // Pop segment.
409 lrt.PopFrame(cookie1);
410
411 IndirectRef iref4 = lrt.Add(obj4.Get(), &error_msg);
412
413 EXPECT_EQ(lrt.Capacity(), 3u);
414 if (check_jni) {
415 EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
416 }
417 CheckDump(&lrt, 3, 3);
418
419 lrt.PopFrame(cookie0);
420 CheckDump(&lrt, 1, 1);
421
422 UNUSED(iref0, iref1, iref2, iref3, iref4);
423 }
424
425 // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference.
426 {
427 LocalReferenceTable lrt(check_jni);
428 bool success = lrt.Initialize(max_count, &error_msg);
429 ASSERT_TRUE(success) << error_msg;
430
431 CheckDump(&lrt, 0, 0);
432
433 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
434
435 // New segment.
436 const LRTSegmentState cookie0 = lrt.PushFrame();
437
438 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
439 EXPECT_TRUE(lrt.Remove(iref1));
440
441 // Emptied segment, push new one.
442 const LRTSegmentState cookie1 = lrt.PushFrame();
443
444 IndirectRef iref2 = lrt.Add(obj1.Get(), &error_msg);
445 IndirectRef iref3 = lrt.Add(obj2.Get(), &error_msg);
446 IndirectRef iref4 = lrt.Add(obj3.Get(), &error_msg);
447
448 EXPECT_TRUE(lrt.Remove(iref3));
449
450 // Pop segment.
451 lrt.PopFrame(cookie1);
452
453 IndirectRef iref5 = lrt.Add(obj4.Get(), &error_msg);
454
455 EXPECT_EQ(lrt.Capacity(), 2u);
456 EXPECT_FALSE(lrt.IsValidReference(iref3, &error_msg));
457 CheckDump(&lrt, 2, 2);
458
459 // Pop segment.
460 lrt.PopFrame(cookie0);
461 CheckDump(&lrt, 1, 1);
462
463 UNUSED(iref0, iref1, iref2, iref3, iref4, iref5);
464 }
465
466 // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
467 // reference
468 {
469 LocalReferenceTable lrt(check_jni);
470 bool success = lrt.Initialize(max_count, &error_msg);
471 ASSERT_TRUE(success) << error_msg;
472
473 CheckDump(&lrt, 0, 0);
474
475 IndirectRef iref0 = lrt.Add(obj0.Get(), &error_msg);
476
477 // New segment.
478 const LRTSegmentState cookie0 = lrt.PushFrame();
479
480 IndirectRef iref1 = lrt.Add(obj1.Get(), &error_msg);
481 IndirectRef iref2 = lrt.Add(obj1.Get(), &error_msg);
482 IndirectRef iref3 = lrt.Add(obj2.Get(), &error_msg);
483
484 EXPECT_TRUE(lrt.Remove(iref2));
485
486 // Pop segment.
487 lrt.PopFrame(cookie0);
488
489 // Push segment.
490 const LRTSegmentState cookie0_second = lrt.PushFrame();
491 EXPECT_EQ(cookie0.top_index, cookie0_second.top_index);
492
493 IndirectRef iref4 = lrt.Add(obj3.Get(), &error_msg);
494
495 EXPECT_EQ(lrt.Capacity(), 2u);
496 EXPECT_FALSE(lrt.IsValidReference(iref3, &error_msg));
497 CheckDump(&lrt, 2, 2);
498
499 UNUSED(iref0, iref1, iref2, iref3, iref4);
500 }
501 }
502
TEST_F(LocalReferenceTableTest,BasicHolesTest)503 TEST_F(LocalReferenceTableTest, BasicHolesTest) {
504 BasicHolesTest(/*check_jni=*/ false, 20u);
505 BasicHolesTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
506 BasicHolesTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
507 }
508
TEST_F(LocalReferenceTableTest,BasicHolesTestCheckJNI)509 TEST_F(LocalReferenceTableTest, BasicHolesTestCheckJNI) {
510 BasicHolesTest(/*check_jni=*/ true, 20u);
511 BasicHolesTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
512 BasicHolesTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
513 }
514
BasicResizeTest(bool check_jni,size_t max_count)515 void LocalReferenceTableTest::BasicResizeTest(bool check_jni, size_t max_count) {
516 ScopedObjectAccess soa(Thread::Current());
517 StackHandleScope<2> hs(soa.Self());
518 Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
519 ASSERT_TRUE(c != nullptr);
520 Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
521 ASSERT_TRUE(obj0 != nullptr);
522
523 std::string error_msg;
524 LocalReferenceTable lrt(check_jni);
525 bool success = lrt.Initialize(max_count, &error_msg);
526 ASSERT_TRUE(success) << error_msg;
527
528 CheckDump(&lrt, 0, 0);
529
530 for (size_t i = 0; i != max_count + 1; ++i) {
531 lrt.Add(obj0.Get(), &error_msg);
532 }
533
534 EXPECT_EQ(lrt.Capacity(), max_count + 1);
535 }
536
TEST_F(LocalReferenceTableTest,BasicResizeTest)537 TEST_F(LocalReferenceTableTest, BasicResizeTest) {
538 BasicResizeTest(/*check_jni=*/ false, 20u);
539 BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
540 BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
541 BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ gPageSize / sizeof(LrtEntry));
542 }
543
TEST_F(LocalReferenceTableTest,BasicResizeTestCheckJNI)544 TEST_F(LocalReferenceTableTest, BasicResizeTestCheckJNI) {
545 BasicResizeTest(/*check_jni=*/ true, 20u);
546 BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
547 BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
548 BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ gPageSize / sizeof(LrtEntry));
549 }
550
TestAddRemove(bool check_jni,size_t max_count,size_t fill_count)551 void LocalReferenceTableTest::TestAddRemove(bool check_jni, size_t max_count, size_t fill_count) {
552 // This will lead to error messages in the log.
553 ScopedLogSeverity sls(LogSeverity::FATAL);
554
555 ScopedObjectAccess soa(Thread::Current());
556 StackHandleScope<9> hs(soa.Self());
557 Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
558 ASSERT_TRUE(c != nullptr);
559 Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
560 ASSERT_TRUE(obj0 != nullptr);
561 Handle<mirror::Object> obj0x = hs.NewHandle(c->AllocObject(soa.Self()));
562 ASSERT_TRUE(obj0x != nullptr);
563 Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
564 ASSERT_TRUE(obj1 != nullptr);
565 Handle<mirror::Object> obj1x = hs.NewHandle(c->AllocObject(soa.Self()));
566 ASSERT_TRUE(obj1x != nullptr);
567 Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
568 ASSERT_TRUE(obj2 != nullptr);
569 Handle<mirror::Object> obj2x = hs.NewHandle(c->AllocObject(soa.Self()));
570 ASSERT_TRUE(obj2x != nullptr);
571 Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
572 ASSERT_TRUE(obj3 != nullptr);
573 Handle<mirror::Object> obj3x = hs.NewHandle(c->AllocObject(soa.Self()));
574 ASSERT_TRUE(obj3x != nullptr);
575
576 std::string error_msg;
577 LocalReferenceTable lrt(check_jni);
578 bool success = lrt.Initialize(max_count, &error_msg);
579 ASSERT_TRUE(success) << error_msg;
580
581 for (size_t i = 0; i != fill_count; ++i) {
582 IndirectRef iref = lrt.Add(c.Get(), &error_msg);
583 ASSERT_TRUE(iref != nullptr) << error_msg;
584 ASSERT_EQ(i + 1u, lrt.Capacity());
585 EXPECT_OBJ_PTR_EQ(c.Get(), lrt.Get(iref));
586 }
587
588 IndirectRef iref0, iref1, iref2, iref3;
589
590 #define ADD_REF(iref, obj, expected_capacity) \
591 do { \
592 (iref) = lrt.Add((obj).Get(), &error_msg); \
593 ASSERT_TRUE((iref) != nullptr) << error_msg; \
594 ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity()); \
595 EXPECT_OBJ_PTR_EQ((obj).Get(), lrt.Get(iref)); \
596 } while (false)
597 #define REMOVE_REF(iref, expected_capacity) \
598 do { \
599 ASSERT_TRUE(lrt.Remove(iref)); \
600 ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity()); \
601 } while (false)
602 #define POP_SEGMENT(cookie, expected_capacity) \
603 do { \
604 lrt.PopFrame(cookie); \
605 ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity()); \
606 } while (false)
607
608 const LRTSegmentState cookie0 = lrt.PushFrame();
609 ADD_REF(iref0, obj0, 1u);
610 ADD_REF(iref1, obj1, 2u);
611 REMOVE_REF(iref1, 1u); // Remove top entry.
612 if (check_jni) {
613 ASSERT_FALSE(lrt.Remove(iref1));
614 }
615 ADD_REF(iref1, obj1x, 2u);
616 REMOVE_REF(iref0, 2u); // Create hole.
617 IndirectRef obsolete_iref0 = iref0;
618 if (check_jni) {
619 ASSERT_FALSE(lrt.Remove(iref0));
620 }
621 ADD_REF(iref0, obj0x, 2u); // Reuse hole
622 if (check_jni) {
623 ASSERT_FALSE(lrt.Remove(obsolete_iref0));
624 }
625
626 // Test addition to the second segment without a hole in the first segment.
627 // Also test removal from the wrong segment here.
628 LRTSegmentState cookie1 = lrt.PushFrame(); // Create second segment.
629 ASSERT_FALSE(lrt.Remove(iref0)); // Cannot remove from inactive segment.
630 ADD_REF(iref2, obj2, 3u);
631 POP_SEGMENT(cookie1, 2u); // Pop the second segment.
632 if (check_jni) {
633 ASSERT_FALSE(lrt.Remove(iref2)); // Cannot remove from popped segment.
634 }
635
636 // Test addition to the second segment with a hole in the first.
637 // Use one more reference in the first segment to allow hitting the small table
638 // overflow path either above or here, based on the provided `fill_count`.
639 ADD_REF(iref2, obj2x, 3u);
640 REMOVE_REF(iref1, 3u); // Create hole.
641 cookie1 = lrt.PushFrame(); // Create second segment.
642 ADD_REF(iref3, obj3, 4u);
643 POP_SEGMENT(cookie1, 3u); // Pop the second segment.
644 REMOVE_REF(iref2, 1u); // Remove top entry, prune previous entry.
645 ADD_REF(iref1, obj1, 2u);
646
647 cookie1 = lrt.PushFrame(); // Create second segment.
648 ADD_REF(iref2, obj2, 3u);
649 ADD_REF(iref3, obj3, 4u);
650 REMOVE_REF(iref2, 4u); // Create hole in second segment.
651 POP_SEGMENT(cookie1, 2u); // Pop the second segment with hole.
652 ADD_REF(iref2, obj2x, 3u); // Prune free list, use new entry.
653 REMOVE_REF(iref2, 2u);
654
655 REMOVE_REF(iref0, 2u); // Create hole.
656 cookie1 = lrt.PushFrame(); // Create second segment.
657 ADD_REF(iref2, obj2, 3u);
658 ADD_REF(iref3, obj3x, 4u);
659 REMOVE_REF(iref2, 4u); // Create hole in second segment.
660 POP_SEGMENT(cookie1, 2u); // Pop the second segment with hole.
661 ADD_REF(iref0, obj0, 2u); // Prune free list, use remaining entry from free list.
662
663 REMOVE_REF(iref0, 2u); // Create hole.
664 cookie1 = lrt.PushFrame(); // Create second segment.
665 ADD_REF(iref2, obj2x, 3u);
666 ADD_REF(iref3, obj3, 4u);
667 REMOVE_REF(iref2, 4u); // Create hole in second segment.
668 REMOVE_REF(iref3, 2u); // Remove top entry, prune previous entry, keep hole above.
669 POP_SEGMENT(cookie1, 2u); // Pop the empty second segment.
670 ADD_REF(iref0, obj0x, 2u); // Reuse hole.
671
672 POP_SEGMENT(cookie0, 0u); // Pop the first segment.
673
674 #undef REMOVE_REF
675 #undef ADD_REF
676 }
677
TEST_F(LocalReferenceTableTest,TestAddRemove)678 TEST_F(LocalReferenceTableTest, TestAddRemove) {
679 TestAddRemove(/*check_jni=*/ false, /*max_count=*/ 20u);
680 TestAddRemove(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
681 TestAddRemove(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
682 static_assert(kSmallLrtEntries >= 4u);
683 for (size_t fill_count = kSmallLrtEntries - 4u; fill_count != kSmallLrtEntries; ++fill_count) {
684 TestAddRemove(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries, fill_count);
685 }
686 }
687
TEST_F(LocalReferenceTableTest,TestAddRemoveCheckJNI)688 TEST_F(LocalReferenceTableTest, TestAddRemoveCheckJNI) {
689 TestAddRemove(/*check_jni=*/ true, /*max_count=*/ 20u);
690 TestAddRemove(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
691 TestAddRemove(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
692 static_assert(kSmallLrtEntries >= 4u);
693 for (size_t fill_count = kSmallLrtEntries - 4u; fill_count != kSmallLrtEntries; ++fill_count) {
694 TestAddRemove(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries, fill_count);
695 }
696 }
697
TestAddRemoveMixed(bool start_check_jni)698 void LocalReferenceTableTest::TestAddRemoveMixed(bool start_check_jni) {
699 // This will lead to error messages in the log.
700 ScopedLogSeverity sls(LogSeverity::FATAL);
701
702 ScopedObjectAccess soa(Thread::Current());
703 static constexpr size_t kMaxUniqueRefs = 16;
704 StackHandleScope<kMaxUniqueRefs + 1u> hs(soa.Self());
705 Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
706 ASSERT_TRUE(c != nullptr);
707 std::array<Handle<mirror::Object>, kMaxUniqueRefs> objs;
708 for (size_t i = 0u; i != kMaxUniqueRefs; ++i) {
709 objs[i] = hs.NewHandle(c->AllocObject(soa.Self()));
710 ASSERT_TRUE(objs[i] != nullptr);
711 }
712
713 std::string error_msg;
714 std::array<IndirectRef, kMaxUniqueRefs> irefs;
715
716 #define ADD_REF(iref, obj) \
717 do { \
718 (iref) = lrt.Add((obj).Get(), &error_msg); \
719 ASSERT_TRUE((iref) != nullptr) << error_msg; \
720 EXPECT_OBJ_PTR_EQ((obj).Get(), lrt.Get(iref)); \
721 } while (false)
722
723 for (size_t split = 1u; split < kMaxUniqueRefs - 1u; ++split) {
724 for (size_t total = split + 1u; total < kMaxUniqueRefs; ++total) {
725 for (size_t deleted_at_start = 0u; deleted_at_start + 1u < split; ++deleted_at_start) {
726 LocalReferenceTable lrt(/*check_jni=*/ start_check_jni);
727 bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
728 ASSERT_TRUE(success) << error_msg;
729 for (size_t i = 0; i != split; ++i) {
730 ADD_REF(irefs[i], objs[i]);
731 ASSERT_EQ(i + 1u, lrt.Capacity());
732 }
733 for (size_t i = 0; i != deleted_at_start; ++i) {
734 ASSERT_TRUE(lrt.Remove(irefs[i]));
735 if (lrt.IsCheckJniEnabled()) {
736 ASSERT_FALSE(lrt.Remove(irefs[i]));
737 }
738 ASSERT_EQ(split, lrt.Capacity());
739 }
740 lrt.SetCheckJniEnabled(!start_check_jni);
741 // Check top index instead of `Capacity()` after changing the CheckJNI setting.
742 auto get_segment_state = [&lrt]() {
743 LRTSegmentState cookie0 = lrt.PushFrame();
744 LRTSegmentState cookie1 = lrt.PushFrame();
745 uint32_t result = cookie1.top_index;
746 lrt.PopFrame(cookie1);
747 lrt.PopFrame(cookie0);
748 return result;
749 };
750 uint32_t split_top_index = get_segment_state();
751 uint32_t last_top_index = split_top_index;
752 for (size_t i = split; i != total; ++i) {
753 ADD_REF(irefs[i], objs[i]);
754 ASSERT_LT(last_top_index, get_segment_state());
755 last_top_index = get_segment_state();
756 }
757 for (size_t i = split; i != total; ++i) {
758 ASSERT_TRUE(lrt.Remove(irefs[i]));
759 if (lrt.IsCheckJniEnabled()) {
760 ASSERT_FALSE(lrt.Remove(irefs[i]));
761 }
762 if (i + 1u != total) {
763 ASSERT_LE(last_top_index, get_segment_state());
764 } else {
765 ASSERT_GT(last_top_index, get_segment_state());
766 ASSERT_LE(split_top_index, get_segment_state());
767 }
768 }
769 }
770 }
771 }
772
773 #undef ADD_REF
774 }
775
TEST_F(LocalReferenceTableTest,TestAddRemoveMixed)776 TEST_F(LocalReferenceTableTest, TestAddRemoveMixed) {
777 TestAddRemoveMixed(/*start_check_jni=*/ false);
778 TestAddRemoveMixed(/*start_check_jni=*/ true);
779 }
780
TEST_F(LocalReferenceTableTest,RegressionTestB276210372)781 TEST_F(LocalReferenceTableTest, RegressionTestB276210372) {
782 LocalReferenceTable lrt(/*check_jni=*/ false);
783 std::string error_msg;
784 bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
785 ASSERT_TRUE(success) << error_msg;
786 ScopedObjectAccess soa(Thread::Current());
787 ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
788
789 auto get_previous_state = [&lrt]() {
790 LRTSegmentState previous_state = lrt.PushFrame();
791 lrt.PopFrame(previous_state);
792 return previous_state;
793 };
794
795 // Create the first segment with two references.
796 IndirectRef ref0 = lrt.Add(c, &error_msg);
797 ASSERT_TRUE(ref0 != nullptr);
798 IndirectRef ref1 = lrt.Add(c, &error_msg);
799 ASSERT_TRUE(ref1 != nullptr);
800
801 // Create a second segment with a hole, then pop it.
802 const LRTSegmentState cookie0A = lrt.PushFrame();
803 const LRTSegmentState previous_state_A = get_previous_state();
804 IndirectRef ref2a = lrt.Add(c, &error_msg);
805 ASSERT_TRUE(ref2a != nullptr);
806 IndirectRef ref3a = lrt.Add(c, &error_msg);
807 ASSERT_TRUE(ref3a != nullptr);
808 EXPECT_TRUE(lrt.Remove(ref2a));
809 lrt.PopFrame(cookie0A);
810
811 // Create a hole in the first segment.
812 // There was previously a bug that `Remove()` would not prune the popped free entries,
813 // so the new free entry would point to the hole in the popped segment. The code below
814 // would then overwrite that hole with a new segment, pop that segment, reuse the good
815 // free entry and then crash trying to prune the overwritten hole. b/276210372
816 EXPECT_TRUE(lrt.Remove(ref0));
817
818 // Create a second segment again and overwite the old hole, then pop the segment.
819 const LRTSegmentState cookie0B = lrt.PushFrame();
820 const LRTSegmentState previous_state_B = get_previous_state();
821 ASSERT_EQ(cookie0B.top_index, cookie0A.top_index);
822 ASSERT_EQ(previous_state_B.top_index, previous_state_A.top_index);
823 IndirectRef ref2b = lrt.Add(c, &error_msg);
824 ASSERT_TRUE(ref2b != nullptr);
825 lrt.PopFrame(cookie0B);
826
827 // Reuse the hole in first segment.
828 IndirectRef reused0 = lrt.Add(c, &error_msg);
829 ASSERT_TRUE(reused0 != nullptr);
830
831 // Add a new reference.
832 IndirectRef new_ref = lrt.Add(c, &error_msg);
833 ASSERT_TRUE(new_ref != nullptr);
834 }
835
TEST_F(LocalReferenceTableTest,RegressionTestB276864369)836 TEST_F(LocalReferenceTableTest, RegressionTestB276864369) {
837 LocalReferenceTable lrt(/*check_jni=*/ false);
838 std::string error_msg;
839 bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
840 ASSERT_TRUE(success) << error_msg;
841 ScopedObjectAccess soa(Thread::Current());
842 ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
843
844 // Add refs to fill all small tables and one bigger table.
845 const size_t refs_per_page = gPageSize / sizeof(LrtEntry);
846 std::vector<IndirectRef> refs;
847 for (size_t i = 0; i != 2 * refs_per_page; ++i) {
848 refs.push_back(lrt.Add(c, &error_msg));
849 ASSERT_TRUE(refs.back() != nullptr);
850 }
851
852 // We had a bug in `Trim()` where we would try to skip one more table than available
853 // if the capacity was exactly at the end of table. If the next table was not allocated,
854 // we would hit a `DCHECK()` in `dchecked_vector<>` in debug mode but in release
855 // mode we would proceed to use memory outside the allocated chunk. b/276864369
856 lrt.Trim();
857 }
858
TEST_F(LocalReferenceTableTest,Trim)859 TEST_F(LocalReferenceTableTest, Trim) {
860 LocalReferenceTable lrt(/*check_jni=*/ false);
861 std::string error_msg;
862 bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
863 ASSERT_TRUE(success) << error_msg;
864 ScopedObjectAccess soa(Thread::Current());
865 ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
866
867 // Add refs to fill all small tables.
868 LRTSegmentState cookie0 = lrt.PushFrame();
869 const size_t refs_per_page = gPageSize / sizeof(LrtEntry);
870 std::vector<IndirectRef> refs0;
871 for (size_t i = 0; i != refs_per_page; ++i) {
872 refs0.push_back(lrt.Add(c, &error_msg));
873 ASSERT_TRUE(refs0.back() != nullptr);
874 }
875
876 // Nothing to trim.
877 lrt.Trim();
878 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs0.back())->IsNull());
879
880 // Add refs to fill the next, page-sized table.
881 std::vector<IndirectRef> refs1;
882 LRTSegmentState cookie1 = lrt.PushFrame();
883 for (size_t i = 0; i != refs_per_page; ++i) {
884 refs1.push_back(lrt.Add(c, &error_msg));
885 ASSERT_TRUE(refs1.back() != nullptr);
886 }
887
888 // Nothing to trim.
889 lrt.Trim();
890 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
891
892 // Pop one reference and try to trim, there is no page to trim.
893 ASSERT_TRUE(lrt.Remove(refs1.back()));
894 lrt.Trim();
895 ASSERT_FALSE(
896 IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1[refs1.size() - 2u])->IsNull());
897
898 // Pop the entire segment with the page-sized table and trim, clearing the page.
899 lrt.PopFrame(cookie1);
900 lrt.Trim();
901 for (IndirectRef ref : refs1) {
902 ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
903 }
904 refs1.clear();
905
906 // Add refs to fill the page-sized table and half of the next one.
907 cookie1 = lrt.PushFrame(); // Push a new segment.
908 for (size_t i = 0; i != 2 * refs_per_page; ++i) {
909 refs1.push_back(lrt.Add(c, &error_msg));
910 ASSERT_TRUE(refs1.back() != nullptr);
911 }
912
913 // Add refs to fill the other half of the table with two pages.
914 std::vector<IndirectRef> refs2;
915 const LRTSegmentState cookie2 = lrt.PushFrame();
916 for (size_t i = 0; i != refs_per_page; ++i) {
917 refs2.push_back(lrt.Add(c, &error_msg));
918 ASSERT_TRUE(refs2.back() != nullptr);
919 }
920
921 // Nothing to trim.
922 lrt.Trim();
923 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
924
925 // Pop the last segment with one page worth of references and trim that page.
926 lrt.PopFrame(cookie2);
927 lrt.Trim();
928 for (IndirectRef ref : refs2) {
929 ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
930 }
931 refs2.clear();
932 for (IndirectRef ref : refs1) {
933 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
934 }
935
936 // Pop the middle segment with two pages worth of references, and trim those pages.
937 lrt.PopFrame(cookie1);
938 lrt.Trim();
939 for (IndirectRef ref : refs1) {
940 ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
941 }
942 refs1.clear();
943
944 // Pop the first segment with small tables and try to trim. Small tables are never trimmed.
945 lrt.PopFrame(cookie0);
946 lrt.Trim();
947 for (IndirectRef ref : refs0) {
948 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
949 }
950 refs0.clear();
951
952 // Fill small tables and one more reference, then another segment up to 4 pages.
953 LRTSegmentState cookie0_second = lrt.PushFrame();
954 ASSERT_EQ(cookie0.top_index, cookie0_second.top_index);
955 for (size_t i = 0; i != refs_per_page + 1u; ++i) {
956 refs0.push_back(lrt.Add(c, &error_msg));
957 ASSERT_TRUE(refs0.back() != nullptr);
958 }
959 cookie1 = lrt.PushFrame(); // Push a new segment.
960 for (size_t i = 0; i != 3u * refs_per_page - 1u; ++i) {
961 refs1.push_back(lrt.Add(c, &error_msg));
962 ASSERT_TRUE(refs1.back() != nullptr);
963 }
964
965 // Nothing to trim.
966 lrt.Trim();
967 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
968
969 // Pop the middle segment, trim two pages.
970 lrt.PopFrame(cookie1);
971 lrt.Trim();
972 for (IndirectRef ref : refs0) {
973 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
974 }
975 ASSERT_EQ(refs0.size(), lrt.Capacity());
976 for (IndirectRef ref : ArrayRef<IndirectRef>(refs1).SubArray(0u, refs_per_page - 1u)) {
977 // Popped but not trimmed as these are at the same page as the last entry in `refs0`.
978 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
979 }
980 for (IndirectRef ref : ArrayRef<IndirectRef>(refs1).SubArray(refs_per_page - 1u)) {
981 ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
982 }
983 }
984
TEST_F(LocalReferenceTableTest,PruneBeforeTrim)985 TEST_F(LocalReferenceTableTest, PruneBeforeTrim) {
986 LocalReferenceTable lrt(/*check_jni=*/ false);
987 std::string error_msg;
988 bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
989 ASSERT_TRUE(success) << error_msg;
990 ScopedObjectAccess soa(Thread::Current());
991 ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
992
993 // Add refs to fill all small tables and one bigger table.
994 const LRTSegmentState cookie0 = lrt.PushFrame();
995 const size_t refs_per_page = gPageSize / sizeof(LrtEntry);
996 std::vector<IndirectRef> refs;
997 for (size_t i = 0; i != 2 * refs_per_page; ++i) {
998 refs.push_back(lrt.Add(c, &error_msg));
999 ASSERT_TRUE(refs.back() != nullptr);
1000 }
1001
1002 // Nothing to trim.
1003 lrt.Trim();
1004 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs.back())->IsNull());
1005
1006 // Create a hole in the last page.
1007 IndirectRef removed = refs[refs.size() - 2u];
1008 ASSERT_TRUE(lrt.Remove(removed));
1009
1010 // Pop the entire segment and trim. Small tables are not pruned.
1011 lrt.PopFrame(cookie0);
1012 lrt.Trim();
1013 for (IndirectRef ref : ArrayRef<IndirectRef>(refs).SubArray(0u, refs_per_page)) {
1014 ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
1015 }
1016 for (IndirectRef ref : ArrayRef<IndirectRef>(refs).SubArray(refs_per_page)) {
1017 ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
1018 }
1019
1020 // Add a new reference and check that it reused the first slot rather than the old hole.
1021 IndirectRef new_ref = lrt.Add(c, &error_msg);
1022 ASSERT_TRUE(new_ref != nullptr);
1023 ASSERT_NE(new_ref, removed);
1024 ASSERT_EQ(new_ref, refs[0]);
1025 }
1026
1027 } // namespace jni
1028 } // namespace art
1029