xref: /aosp_15_r20/art/runtime/verifier/reg_type_cache.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2012 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 "reg_type_cache-inl.h"
18 
19 #include <type_traits>
20 
21 #include "base/aborting.h"
22 #include "base/arena_bit_vector.h"
23 #include "base/bit_vector-inl.h"
24 #include "base/casts.h"
25 #include "base/scoped_arena_allocator.h"
26 #include "base/stl_util.h"
27 #include "class_linker-inl.h"
28 #include "class_root-inl.h"
29 #include "dex/descriptors_names.h"
30 #include "dex/dex_file-inl.h"
31 #include "mirror/class-inl.h"
32 #include "mirror/object-inl.h"
33 #include "reg_type-inl.h"
34 
35 namespace art HIDDEN {
36 namespace verifier {
37 
FillPrimitiveAndConstantTypes()38 void RegTypeCache::FillPrimitiveAndConstantTypes() {
39   entries_.resize(kNumberOfFixedCacheIds);
40   ArrayRef<const RegType*> entries(entries_);
41 
42   static constexpr UndefinedType constUndefinedType(kUndefinedCacheId);
43   entries[kUndefinedCacheId] = &constUndefinedType;
44   static constexpr ConflictType constConflictType(kConflictCacheId);
45   entries[kConflictCacheId] = &constConflictType;
46 
47 #define CREATE_PRIMITIVE_TYPE(name, descriptor) \
48   static constexpr name##Type const##name(descriptor, k##name##CacheId); \
49   entries[k##name##CacheId] = &const##name;
50   CREATE_PRIMITIVE_TYPE(Boolean, "Z");
51   CREATE_PRIMITIVE_TYPE(Byte, "B");
52   CREATE_PRIMITIVE_TYPE(Short, "S");
53   CREATE_PRIMITIVE_TYPE(Char, "C");
54   CREATE_PRIMITIVE_TYPE(Integer, "I");
55   CREATE_PRIMITIVE_TYPE(LongLo, "J");
56   CREATE_PRIMITIVE_TYPE(LongHi, "J");
57   CREATE_PRIMITIVE_TYPE(Float, "F");
58   CREATE_PRIMITIVE_TYPE(DoubleLo, "D");
59   CREATE_PRIMITIVE_TYPE(DoubleHi, "D");
60 #undef CREATE_PRIMITIVE_TYPE
61 
62 #define CREATE_CONSTANT_TYPE(name) \
63   static constexpr name##Type const##name(k##name##CacheId); \
64   entries[k##name##CacheId] = &const##name;
65   CREATE_CONSTANT_TYPE(Zero);
66   CREATE_CONSTANT_TYPE(BooleanConstant);
67   CREATE_CONSTANT_TYPE(PositiveByteConstant);
68   CREATE_CONSTANT_TYPE(PositiveShortConstant);
69   CREATE_CONSTANT_TYPE(CharConstant);
70   CREATE_CONSTANT_TYPE(ByteConstant);
71   CREATE_CONSTANT_TYPE(ShortConstant);
72   CREATE_CONSTANT_TYPE(IntegerConstant);
73   CREATE_CONSTANT_TYPE(ConstantLo);
74   CREATE_CONSTANT_TYPE(ConstantHi);
75   CREATE_CONSTANT_TYPE(Null);
76 #undef CREATE_CONSTANT_TYPE
77 
78   // `JavaLangObjectType` must be initialized together with its uninitialized type.
79   struct JavaLangObjectPair {
80     constexpr JavaLangObjectPair()
81         : initialized("Ljava/lang/Object;", kJavaLangObjectCacheId, &uninitialized),
82           uninitialized(kUninitializedJavaLangObjectCacheId, &initialized) {}
83     JavaLangObjectType initialized;
84     UninitializedReferenceType uninitialized;
85   };
86   static constexpr JavaLangObjectPair constJavaLangObject;
87   entries[kJavaLangObjectCacheId] = &constJavaLangObject.initialized;
88   entries[kUninitializedJavaLangObjectCacheId] = &constJavaLangObject.uninitialized;
89 }
90 
FromDescriptor(const char * descriptor)91 const RegType& RegTypeCache::FromDescriptor(const char* descriptor) {
92   if (descriptor[1] == '\0') {
93     switch (descriptor[0]) {
94       case 'Z':
95         return Boolean();
96       case 'B':
97         return Byte();
98       case 'S':
99         return Short();
100       case 'C':
101         return Char();
102       case 'I':
103         return Integer();
104       case 'J':
105         return LongLo();
106       case 'F':
107         return Float();
108       case 'D':
109         return DoubleLo();
110       case 'V':  // For void types, conflict types.
111       default:
112         return Conflict();
113     }
114   } else if (descriptor[0] == 'L' || descriptor[0] == '[') {
115     return From(descriptor);
116   } else {
117     return Conflict();
118   }
119 }
120 
FromTypeIndexUncached(dex::TypeIndex type_index)121 const RegType& RegTypeCache::FromTypeIndexUncached(dex::TypeIndex type_index) {
122   DCHECK_EQ(ids_for_type_index_[type_index.index_], kNoIdForTypeIndex);
123   const char* descriptor = dex_file_->GetTypeDescriptor(type_index);
124   const RegType& reg_type = FromDescriptor(descriptor);
125   DCHECK_NE(reg_type.GetId(), kNoIdForTypeIndex);
126   ids_for_type_index_[type_index.index_] = reg_type.GetId();
127   return reg_type;
128 }
129 
RegTypeFromPrimitiveType(Primitive::Type prim_type) const130 const RegType& RegTypeCache::RegTypeFromPrimitiveType(Primitive::Type prim_type) const {
131   switch (prim_type) {
132     case Primitive::kPrimBoolean:
133       return Boolean();
134     case Primitive::kPrimByte:
135       return Byte();
136     case Primitive::kPrimShort:
137       return Short();
138     case Primitive::kPrimChar:
139       return Char();
140     case Primitive::kPrimInt:
141       return Integer();
142     case Primitive::kPrimLong:
143       return LongLo();
144     case Primitive::kPrimFloat:
145       return Float();
146     case Primitive::kPrimDouble:
147       return DoubleLo();
148     case Primitive::kPrimVoid:
149     default:
150       return *entries_[kConflictCacheId];
151   }
152 }
153 
MatchDescriptor(size_t idx,const std::string_view & descriptor)154 bool RegTypeCache::MatchDescriptor(size_t idx, const std::string_view& descriptor) {
155   const RegType* entry = entries_[idx];
156   if (descriptor != entry->descriptor_) {
157     return false;
158   }
159   DCHECK(entry->IsJavaLangObject() || entry->IsReference() || entry->IsUnresolvedReference());
160   return true;
161 }
162 
ResolveClass(const char * descriptor,size_t descriptor_length)163 ObjPtr<mirror::Class> RegTypeCache::ResolveClass(const char* descriptor, size_t descriptor_length) {
164   // Class was not found, must create new type.
165   // Try resolving class
166   Thread* self = Thread::Current();
167   ObjPtr<mirror::Class> klass = nullptr;
168   if (can_load_classes_) {
169     klass = class_linker_->FindClass(self, descriptor, descriptor_length, class_loader_);
170   } else {
171     std::string_view sv_descriptor(descriptor, descriptor_length);
172     klass = class_linker_->LookupClass(self, sv_descriptor, class_loader_.Get());
173     if (klass != nullptr && !klass->IsResolved()) {
174       // We found the class but without it being loaded its not safe for use.
175       klass = nullptr;
176     }
177   }
178   return klass;
179 }
180 
AddString(const std::string_view & str)181 std::string_view RegTypeCache::AddString(const std::string_view& str) {
182   char* ptr = allocator_.AllocArray<char>(str.length());
183   memcpy(ptr, str.data(), str.length());
184   return std::string_view(ptr, str.length());
185 }
186 
From(const char * descriptor)187 const RegType& RegTypeCache::From(const char* descriptor) {
188   // TODO: Avoid the implicit `strlen()` call for ASCII descriptors from the dex file.
189   std::string_view sv_descriptor(descriptor);
190   // Try looking up the class in the cache first. We use a std::string_view to avoid
191   // repeated strlen operations on the descriptor.
192   if (MatchDescriptor(kJavaLangObjectCacheId, sv_descriptor)) {
193     return *(entries_[kJavaLangObjectCacheId]);
194   }
195   for (size_t i = kNumberOfFixedCacheIds; i < entries_.size(); i++) {
196     if (MatchDescriptor(i, sv_descriptor)) {
197       return *(entries_[i]);
198     }
199   }
200   // Class not found in the cache, will create a new type for that.
201   // Try resolving class.
202   ObjPtr<mirror::Class> klass = ResolveClass(descriptor, sv_descriptor.length());
203   // TODO: Avoid copying the `descriptor` with `AddString()` below if the `descriptor`
204   // comes from the dex file, for example through `FromTypeIndex()`.
205   if (klass != nullptr) {
206     DCHECK(!klass->IsPrimitive());
207     if (klass->IsObjectClass()) {
208       return JavaLangObject();
209     }
210     const RegType* entry = new (&allocator_) ReferenceType(
211         handles_.NewHandle(klass), AddString(sv_descriptor), entries_.size());
212     return AddEntry(entry);
213   } else {  // Class not resolved.
214     // We tried loading the class and failed, this might get an exception raised
215     // so we want to clear it before we go on.
216     if (can_load_classes_) {
217       DCHECK(Thread::Current()->IsExceptionPending());
218       Thread::Current()->ClearException();
219     } else {
220       DCHECK(!Thread::Current()->IsExceptionPending());
221     }
222     if (IsValidDescriptor(descriptor)) {
223       return AddEntry(new (&allocator_) UnresolvedReferenceType(AddString(sv_descriptor),
224                                                                 entries_.size()));
225     } else {
226       // The descriptor is broken return the unknown type as there's nothing sensible that
227       // could be done at runtime
228       return Conflict();
229     }
230   }
231 }
232 
MakeUnresolvedReference()233 const RegType& RegTypeCache::MakeUnresolvedReference() {
234   // The descriptor is intentionally invalid so nothing else will match this type.
235   return AddEntry(new (&allocator_) UnresolvedReferenceType(AddString("a"), entries_.size()));
236 }
237 
FromClass(ObjPtr<mirror::Class> klass)238 const RegType& RegTypeCache::FromClass(ObjPtr<mirror::Class> klass) {
239   DCHECK(klass != nullptr);
240   DCHECK(!klass->IsProxyClass());
241 
242   if (klass->IsPrimitive()) {
243     return RegTypeFromPrimitiveType(klass->GetPrimitiveType());
244   }
245   if (klass->IsObjectClass()) {
246     return JavaLangObject();
247   }
248   if (!klass->IsArrayClass() && &klass->GetDexFile() == dex_file_) {
249     // Go through the `TypeIndex`-based cache. If the entry is not there yet, we shall
250     // fill it in now to make sure it's available for subsequent lookups.
251     std::optional<StackHandleScope<1u>> hs(std::nullopt);
252     if (kIsDebugBuild) {
253       hs.emplace(Thread::Current());
254     }
255     Handle<mirror::Class> h_class =
256         kIsDebugBuild ? hs->NewHandle(klass) : Handle<mirror::Class>();
257     const RegType& reg_type = FromTypeIndex(klass->GetDexTypeIndex());
258     DCHECK(reg_type.IsReference());
259     DCHECK(reg_type.GetClass() == h_class.Get());
260     return reg_type;
261   }
262   for (auto& pair : klass_entries_) {
263     const Handle<mirror::Class> entry_klass = pair.first;
264     const RegType* entry_reg_type = pair.second;
265     if (entry_klass.Get() == klass) {
266       return *entry_reg_type;
267     }
268   }
269 
270   // No reference to the class was found, create new reference.
271   std::string_view descriptor;
272   if (klass->IsArrayClass()) {
273     std::string temp;
274     descriptor = AddString(std::string_view(klass->GetDescriptor(&temp)));
275   } else {
276     // Point `descriptor` to the string data in the dex file that defines the `klass`.
277     // That dex file cannot be unloaded while we hold a `Handle<>` to that `klass`.
278     descriptor = klass->GetDescriptorView();
279   }
280   Handle<mirror::Class> h_klass = handles_.NewHandle(klass);
281   const RegType* reg_type = new (&allocator_) ReferenceType(h_klass, descriptor, entries_.size());
282   return AddEntry(reg_type);
283 }
284 
RegTypeCache(Thread * self,ClassLinker * class_linker,ArenaPool * arena_pool,Handle<mirror::ClassLoader> class_loader,const DexFile * dex_file,bool can_load_classes,bool can_suspend)285 RegTypeCache::RegTypeCache(Thread* self,
286                            ClassLinker* class_linker,
287                            ArenaPool* arena_pool,
288                            Handle<mirror::ClassLoader> class_loader,
289                            const DexFile* dex_file,
290                            bool can_load_classes,
291                            bool can_suspend)
292     : allocator_(arena_pool),
293       entries_(allocator_.Adapter(kArenaAllocVerifier)),
294       klass_entries_(allocator_.Adapter(kArenaAllocVerifier)),
295       handles_(self),
296       class_linker_(class_linker),
297       class_loader_(class_loader),
298       dex_file_(dex_file),
299       ids_for_type_index_(allocator_.AllocArray<uint16_t>(dex_file->NumTypeIds())),
300       last_uninitialized_this_type_(nullptr),
301       can_load_classes_(can_load_classes),
302       can_suspend_(can_suspend) {
303   DCHECK(can_suspend || !can_load_classes) << "Cannot load classes if suspension is disabled!";
304   if (kIsDebugBuild && can_suspend) {
305     Thread::Current()->AssertThreadSuspensionIsAllowable(gAborting == 0);
306   }
307   // `ArenaAllocator` guarantees zero-initialization.
308   static_assert(kNoIdForTypeIndex == 0u);
309   DCHECK(std::all_of(ids_for_type_index_,
310                      ids_for_type_index_ + dex_file->NumTypeIds(),
311                      [](uint16_t id) { return id == kNoIdForTypeIndex; }));
312   // The klass_entries_ array does not have primitives or constants.
313   static constexpr size_t kNumReserveEntries = 32;
314   klass_entries_.reserve(kNumReserveEntries);
315   // We want to have room for additional entries after inserting primitives and constants.
316   entries_.reserve(kNumReserveEntries + kNumberOfFixedCacheIds);
317   FillPrimitiveAndConstantTypes();
318 }
319 
FromUnresolvedMerge(const RegType & left,const RegType & right,MethodVerifier * verifier)320 const RegType& RegTypeCache::FromUnresolvedMerge(const RegType& left,
321                                                  const RegType& right,
322                                                  MethodVerifier* verifier) {
323   ArenaBitVector types(&allocator_,
324                        kDefaultArenaBitVectorBytes * kBitsPerByte,  // Allocate at least 8 bytes.
325                        true);                                       // Is expandable.
326   const RegType* left_resolved;
327   bool left_unresolved_is_array;
328   if (left.IsUnresolvedMergedReference()) {
329     const UnresolvedMergedReferenceType& left_merge =
330         *down_cast<const UnresolvedMergedReferenceType*>(&left);
331 
332     types.Copy(&left_merge.GetUnresolvedTypes());
333     left_resolved = &left_merge.GetResolvedPart();
334     left_unresolved_is_array = left.IsArrayTypes();
335   } else if (left.IsUnresolvedTypes()) {
336     types.SetBit(left.GetId());
337     left_resolved = &Zero();
338     left_unresolved_is_array = left.IsArrayTypes();
339   } else {
340     left_resolved = &left;
341     left_unresolved_is_array = false;
342   }
343 
344   const RegType* right_resolved;
345   bool right_unresolved_is_array;
346   if (right.IsUnresolvedMergedReference()) {
347     const UnresolvedMergedReferenceType& right_merge =
348         *down_cast<const UnresolvedMergedReferenceType*>(&right);
349 
350     types.Union(&right_merge.GetUnresolvedTypes());
351     right_resolved = &right_merge.GetResolvedPart();
352     right_unresolved_is_array = right.IsArrayTypes();
353   } else if (right.IsUnresolvedTypes()) {
354     types.SetBit(right.GetId());
355     right_resolved = &Zero();
356     right_unresolved_is_array = right.IsArrayTypes();
357   } else {
358     right_resolved = &right;
359     right_unresolved_is_array = false;
360   }
361 
362   // Merge the resolved parts. Left and right might be equal, so use SafeMerge.
363   const RegType& resolved_parts_merged = left_resolved->SafeMerge(*right_resolved, this, verifier);
364   // If we get a conflict here, the merge result is a conflict, not an unresolved merge type.
365   if (resolved_parts_merged.IsConflict()) {
366     return Conflict();
367   }
368   if (resolved_parts_merged.IsJavaLangObject()) {
369     return resolved_parts_merged;
370   }
371 
372   bool resolved_merged_is_array = resolved_parts_merged.IsArrayTypes();
373   if (left_unresolved_is_array || right_unresolved_is_array || resolved_merged_is_array) {
374     // Arrays involved, see if we need to merge to Object.
375 
376     // Is the resolved part a primitive array?
377     if (resolved_merged_is_array && !resolved_parts_merged.IsObjectArrayTypes()) {
378       return JavaLangObject();
379     }
380 
381     // Is any part not an array (but exists)?
382     if ((!left_unresolved_is_array && left_resolved != &left) ||
383         (!right_unresolved_is_array && right_resolved != &right) ||
384         !resolved_merged_is_array) {
385       return JavaLangObject();
386     }
387   }
388 
389   // Check if entry already exists.
390   for (size_t i = kNumberOfFixedCacheIds; i < entries_.size(); i++) {
391     const RegType* cur_entry = entries_[i];
392     if (cur_entry->IsUnresolvedMergedReference()) {
393       const UnresolvedMergedReferenceType* cmp_type =
394           down_cast<const UnresolvedMergedReferenceType*>(cur_entry);
395       const RegType& resolved_part = cmp_type->GetResolvedPart();
396       const BitVector& unresolved_part = cmp_type->GetUnresolvedTypes();
397       // Use SameBitsSet. "types" is expandable to allow merging in the components, but the
398       // BitVector in the final RegType will be made non-expandable.
399       if (&resolved_part == &resolved_parts_merged && types.SameBitsSet(&unresolved_part)) {
400         return *cur_entry;
401       }
402     }
403   }
404   return AddEntry(new (&allocator_) UnresolvedMergedReferenceType(resolved_parts_merged,
405                                                                   types,
406                                                                   this,
407                                                                   entries_.size()));
408 }
409 
Uninitialized(const RegType & type)410 const UninitializedType& RegTypeCache::Uninitialized(const RegType& type) {
411   auto get_or_create_uninitialized_type =
412     [&](auto& ref_type) REQUIRES_SHARED(Locks::mutator_lock_) {
413       using RefType = std::remove_const_t<std::remove_reference_t<decltype(ref_type)>>;
414       static_assert(std::is_same_v<RefType, ReferenceType> ||
415                     std::is_same_v<RefType, UnresolvedReferenceType>);
416       using UninitRefType =
417           std::remove_const_t<std::remove_pointer_t<decltype(ref_type.GetUninitializedType())>>;
418       static_assert(std::is_same_v<RefType, ReferenceType>
419           ? std::is_same_v<UninitRefType, UninitializedReferenceType>
420           : std::is_same_v<UninitRefType, UnresolvedUninitializedReferenceType>);
421       const UninitRefType* uninit_ref_type = ref_type.GetUninitializedType();
422       if (uninit_ref_type == nullptr) {
423         uninit_ref_type = new (&allocator_) UninitRefType(entries_.size(), &ref_type);
424         AddEntry(uninit_ref_type);
425         ref_type.SetUninitializedType(uninit_ref_type);
426       }
427       return uninit_ref_type;
428     };
429 
430   if (type.IsReference()) {
431     return *get_or_create_uninitialized_type(down_cast<const ReferenceType&>(type));
432   } else if (type.IsUnresolvedReference()) {
433     return *get_or_create_uninitialized_type(down_cast<const UnresolvedReferenceType&>(type));
434   } else {
435     DCHECK(type.IsJavaLangObject());
436     return *down_cast<const JavaLangObjectType&>(type).GetUninitializedType();
437   }
438 }
439 
FromUninitialized(const RegType & uninit_type)440 const RegType& RegTypeCache::FromUninitialized(const RegType& uninit_type) {
441   if (uninit_type.IsUninitializedReference()) {
442     return *down_cast<const UninitializedReferenceType&>(uninit_type).GetInitializedType();
443   } else if (uninit_type.IsUnresolvedUninitializedReference()) {
444     return *down_cast<const UnresolvedUninitializedReferenceType&>(
445         uninit_type).GetInitializedType();
446   } else if (uninit_type.IsUninitializedThisReference()) {
447     return *down_cast<const UninitializedThisReferenceType&>(uninit_type).GetInitializedType();
448   } else {
449     DCHECK(uninit_type.IsUnresolvedUninitializedThisReference()) << uninit_type;
450     return *down_cast<const UnresolvedUninitializedThisReferenceType&>(
451         uninit_type).GetInitializedType();
452   }
453 }
454 
UninitializedThisArgument(const RegType & type)455 const UninitializedType& RegTypeCache::UninitializedThisArgument(const RegType& type) {
456   if (last_uninitialized_this_type_ != nullptr && last_uninitialized_this_type_->Equals(type)) {
457     return *last_uninitialized_this_type_;
458   }
459 
460   UninitializedType* entry;
461   const std::string_view& descriptor(type.GetDescriptor());
462   if (type.IsUnresolvedReference()) {
463     for (size_t i = kNumberOfFixedCacheIds; i < entries_.size(); i++) {
464       const RegType* cur_entry = entries_[i];
465       if (cur_entry->IsUnresolvedUninitializedThisReference() &&
466           down_cast<const UnresolvedUninitializedThisReferenceType*>(cur_entry)
467               ->GetInitializedType() == &type) {
468         DCHECK_EQ(cur_entry->GetDescriptor(), type.GetDescriptor());
469         return *down_cast<const UninitializedType*>(cur_entry);
470       }
471     }
472     entry = new (&allocator_) UnresolvedUninitializedThisReferenceType(
473         entries_.size(), down_cast<const UnresolvedReferenceType*>(&type));
474   } else {
475     DCHECK(type.IsJavaLangObject() || type.IsReference());
476     for (size_t i = kNumberOfFixedCacheIds; i < entries_.size(); i++) {
477       const RegType* cur_entry = entries_[i];
478       if (cur_entry->IsUninitializedThisReference() &&
479           down_cast<const UninitializedThisReferenceType*>(cur_entry)
480               ->GetInitializedType() == &type) {
481         DCHECK_EQ(cur_entry->GetDescriptor(), type.GetDescriptor());
482         return *down_cast<const UninitializedType*>(cur_entry);
483       }
484     }
485     entry = new (&allocator_) UninitializedThisReferenceType(
486         entries_.size(), down_cast<const ReferenceType*>(&type));
487   }
488   last_uninitialized_this_type_ = entry;
489   return AddEntry(entry);
490 }
491 
GetComponentType(const RegType & array)492 const RegType& RegTypeCache::GetComponentType(const RegType& array) {
493   if (!array.IsArrayTypes()) {
494     return Conflict();
495   } else if (array.IsUnresolvedTypes()) {
496     DCHECK(!array.IsUnresolvedMergedReference());  // Caller must make sure not to ask for this.
497     const std::string descriptor(array.GetDescriptor());
498     return FromDescriptor(descriptor.c_str() + 1);
499   } else {
500     ObjPtr<mirror::Class> klass = array.GetClass()->GetComponentType();
501     if (klass->IsErroneous()) {
502       // Arrays may have erroneous component types, use unresolved in that case.
503       // We assume that the primitive classes are not erroneous, so we know it is a
504       // reference type.
505       std::string temp;
506       const char* descriptor = klass->GetDescriptor(&temp);
507       return FromDescriptor(descriptor);
508     } else {
509       return FromClass(klass);
510     }
511   }
512 }
513 
Dump(std::ostream & os)514 void RegTypeCache::Dump(std::ostream& os) {
515   for (size_t i = 0; i < entries_.size(); i++) {
516     const RegType* cur_entry = entries_[i];
517     if (cur_entry != nullptr) {
518       os << i << ": " << cur_entry->Dump() << "\n";
519     }
520   }
521 }
522 
523 }  // namespace verifier
524 }  // namespace art
525