xref: /aosp_15_r20/art/runtime/jni/local_reference_table_test.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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