1 /*
2 * Copyright (C) 2016 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 "vdex_file.h"
18
19 #include <sys/mman.h> // For the PROT_* and MAP_* constants.
20 #include <sys/stat.h> // for mkdir()
21
22 #include <memory>
23 #include <unordered_set>
24
25 #include <android-base/logging.h>
26 #include <android-base/stringprintf.h>
27 #include <log/log.h>
28
29 #include "base/bit_utils.h"
30 #include "base/leb128.h"
31 #include "base/stl_util.h"
32 #include "base/systrace.h"
33 #include "base/unix_file/fd_file.h"
34 #include "base/zip_archive.h"
35 #include "class_linker.h"
36 #include "class_loader_context.h"
37 #include "dex/art_dex_file_loader.h"
38 #include "dex/class_accessor-inl.h"
39 #include "dex/dex_file_loader.h"
40 #include "gc/heap.h"
41 #include "gc/space/image_space.h"
42 #include "mirror/class-inl.h"
43 #include "handle_scope-inl.h"
44 #include "runtime.h"
45 #include "verifier/verifier_deps.h"
46
47 namespace art HIDDEN {
48
49 using android::base::StringPrintf;
50
IsMagicValid() const51 bool VdexFile::VdexFileHeader::IsMagicValid() const {
52 return (memcmp(magic_, kVdexMagic, sizeof(kVdexMagic)) == 0);
53 }
54
IsVdexVersionValid() const55 bool VdexFile::VdexFileHeader::IsVdexVersionValid() const {
56 return (memcmp(vdex_version_, kVdexVersion, sizeof(kVdexVersion)) == 0);
57 }
58
VdexFileHeader(bool has_dex_section)59 VdexFile::VdexFileHeader::VdexFileHeader([[maybe_unused]] bool has_dex_section)
60 : number_of_sections_(static_cast<uint32_t>(VdexSection::kNumberOfSections)) {
61 memcpy(magic_, kVdexMagic, sizeof(kVdexMagic));
62 memcpy(vdex_version_, kVdexVersion, sizeof(kVdexVersion));
63 DCHECK(IsMagicValid());
64 DCHECK(IsVdexVersionValid());
65 }
66
OpenAtAddress(uint8_t * mmap_addr,size_t mmap_size,bool mmap_reuse,const std::string & vdex_filename,bool writable,bool low_4gb,std::string * error_msg)67 std::unique_ptr<VdexFile> VdexFile::OpenAtAddress(uint8_t* mmap_addr,
68 size_t mmap_size,
69 bool mmap_reuse,
70 const std::string& vdex_filename,
71 bool writable,
72 bool low_4gb,
73 std::string* error_msg) {
74 ScopedTrace trace(("VdexFile::OpenAtAddress " + vdex_filename).c_str());
75 if (!OS::FileExists(vdex_filename.c_str())) {
76 *error_msg = "File " + vdex_filename + " does not exist.";
77 return nullptr;
78 }
79
80 std::unique_ptr<File> vdex_file;
81 if (writable) {
82 vdex_file.reset(OS::OpenFileReadWrite(vdex_filename.c_str()));
83 } else {
84 vdex_file.reset(OS::OpenFileForReading(vdex_filename.c_str()));
85 }
86 if (vdex_file == nullptr) {
87 *error_msg = "Could not open file " + vdex_filename +
88 (writable ? " for read/write" : "for reading");
89 return nullptr;
90 }
91
92 int64_t vdex_length = vdex_file->GetLength();
93 if (vdex_length == -1) {
94 *error_msg = "Could not read the length of file " + vdex_filename;
95 return nullptr;
96 }
97
98 return OpenAtAddress(mmap_addr,
99 mmap_size,
100 mmap_reuse,
101 vdex_file->Fd(),
102 vdex_length,
103 vdex_filename,
104 writable,
105 low_4gb,
106 error_msg);
107 }
108
OpenAtAddress(uint8_t * mmap_addr,size_t mmap_size,bool mmap_reuse,int file_fd,size_t vdex_length,const std::string & vdex_filename,bool writable,bool low_4gb,std::string * error_msg)109 std::unique_ptr<VdexFile> VdexFile::OpenAtAddress(uint8_t* mmap_addr,
110 size_t mmap_size,
111 bool mmap_reuse,
112 int file_fd,
113 size_t vdex_length,
114 const std::string& vdex_filename,
115 bool writable,
116 bool low_4gb,
117 std::string* error_msg) {
118 if (mmap_addr != nullptr && mmap_size < vdex_length) {
119 *error_msg = StringPrintf("Insufficient pre-allocated space to mmap vdex: %zu and %zu",
120 mmap_size,
121 vdex_length);
122 return nullptr;
123 }
124 CHECK_IMPLIES(mmap_reuse, mmap_addr != nullptr);
125 // Start as PROT_WRITE so we can mprotect back to it if we want to.
126 MemMap mmap = MemMap::MapFileAtAddress(
127 mmap_addr,
128 vdex_length,
129 PROT_READ | PROT_WRITE,
130 writable ? MAP_SHARED : MAP_PRIVATE,
131 file_fd,
132 /* start= */ 0u,
133 low_4gb,
134 vdex_filename.c_str(),
135 mmap_reuse,
136 /* reservation= */ nullptr,
137 error_msg);
138 if (!mmap.IsValid()) {
139 *error_msg = "Failed to mmap file " + vdex_filename + " : " + *error_msg;
140 return nullptr;
141 }
142
143 std::unique_ptr<VdexFile> vdex(new VdexFile(std::move(mmap)));
144 if (!vdex->IsValid()) {
145 *error_msg = "Vdex file is not valid";
146 return nullptr;
147 }
148
149 return vdex;
150 }
151
OpenFromDm(const std::string & filename,const ZipArchive & archive)152 std::unique_ptr<VdexFile> VdexFile::OpenFromDm(const std::string& filename,
153 const ZipArchive& archive) {
154 std::string error_msg;
155 std::unique_ptr<ZipEntry> zip_entry(archive.Find(VdexFile::kVdexNameInDmFile, &error_msg));
156 if (zip_entry == nullptr) {
157 LOG(INFO) << "No " << VdexFile::kVdexNameInDmFile << " file in DexMetadata archive. "
158 << "Not doing fast verification.";
159 return nullptr;
160 }
161 MemMap input_file = zip_entry->MapDirectlyOrExtract(
162 filename.c_str(),
163 VdexFile::kVdexNameInDmFile,
164 &error_msg,
165 alignof(VdexFile));
166 if (!input_file.IsValid()) {
167 LOG(WARNING) << "Could not open vdex file in DexMetadata archive: " << error_msg;
168 return nullptr;
169 }
170 std::unique_ptr<VdexFile> vdex_file = std::make_unique<VdexFile>(std::move(input_file));
171 if (!vdex_file->IsValid()) {
172 LOG(WARNING) << "The dex metadata .vdex is not valid. Ignoring it.";
173 return nullptr;
174 }
175 if (vdex_file->HasDexSection()) {
176 LOG(ERROR) << "The dex metadata is not allowed to contain dex files";
177 android_errorWriteLog(0x534e4554, "178055795"); // Report to SafetyNet.
178 return nullptr;
179 }
180 return vdex_file;
181 }
182
GetNextDexFileData(const uint8_t * cursor,uint32_t dex_file_index) const183 const uint8_t* VdexFile::GetNextDexFileData(const uint8_t* cursor, uint32_t dex_file_index) const {
184 DCHECK(cursor == nullptr || (cursor > Begin() && cursor <= End()));
185 if (cursor == nullptr) {
186 // Beginning of the iteration, return the first dex file if there is one.
187 return HasDexSection() ? DexBegin() : nullptr;
188 } else if (dex_file_index >= GetNumberOfDexFiles()) {
189 return nullptr;
190 } else {
191 // Fetch the next dex file. Return null if there is none.
192 const uint8_t* data = cursor + reinterpret_cast<const DexFile::Header*>(cursor)->file_size_;
193 // Dex files are required to be 4 byte aligned. the OatWriter makes sure they are, see
194 // OatWriter::SeekToDexFiles.
195 return AlignUp(data, 4);
196 }
197 }
198
GetNextTypeLookupTableData(const uint8_t * cursor,uint32_t dex_file_index) const199 const uint8_t* VdexFile::GetNextTypeLookupTableData(const uint8_t* cursor,
200 uint32_t dex_file_index) const {
201 if (cursor == nullptr) {
202 // Beginning of the iteration, return the first dex file if there is one.
203 return HasTypeLookupTableSection() ? TypeLookupTableDataBegin() : nullptr;
204 } else if (dex_file_index >= GetNumberOfDexFiles()) {
205 return nullptr;
206 } else {
207 const uint8_t* data = cursor + sizeof(uint32_t) + reinterpret_cast<const uint32_t*>(cursor)[0];
208 // TypeLookupTables are required to be 4 byte aligned. the OatWriter makes sure they are.
209 // We don't check this here to be defensive against corrupted vdex files.
210 // Callers should check the returned value matches their expectations.
211 return data;
212 }
213 }
214
OpenAllDexFiles(std::vector<std::unique_ptr<const DexFile>> * dex_files,std::string * error_msg) const215 bool VdexFile::OpenAllDexFiles(std::vector<std::unique_ptr<const DexFile>>* dex_files,
216 std::string* error_msg) const {
217 size_t i = 0;
218 auto dex_file_container = std::make_shared<MemoryDexFileContainer>(Begin(), End());
219 for (const uint8_t* dex_file_start = GetNextDexFileData(nullptr, i);
220 dex_file_start != nullptr;
221 dex_file_start = GetNextDexFileData(dex_file_start, ++i)) {
222 // TODO: Supply the location information for a vdex file.
223 static constexpr char kVdexLocation[] = "";
224 std::string location = DexFileLoader::GetMultiDexLocation(i, kVdexLocation);
225 ArtDexFileLoader dex_file_loader(dex_file_container, location);
226 std::unique_ptr<const DexFile> dex(dex_file_loader.OpenOne(dex_file_start - Begin(),
227 GetLocationChecksum(i),
228 /*oat_dex_file=*/nullptr,
229 /*verify=*/false,
230 /*verify_checksum=*/false,
231 error_msg));
232 if (dex == nullptr) {
233 return false;
234 }
235 dex_files->push_back(std::move(dex));
236 }
237 return true;
238 }
239
CreateDirectories(const std::string & child_path,std::string * error_msg)240 static bool CreateDirectories(const std::string& child_path, /* out */ std::string* error_msg) {
241 size_t last_slash_pos = child_path.find_last_of('/');
242 CHECK_NE(last_slash_pos, std::string::npos) << "Invalid path: " << child_path;
243 std::string parent_path = child_path.substr(0, last_slash_pos);
244 if (OS::DirectoryExists(parent_path.c_str())) {
245 return true;
246 } else if (CreateDirectories(parent_path, error_msg)) {
247 if (mkdir(parent_path.c_str(), 0700) == 0) {
248 return true;
249 }
250 *error_msg = "Could not create directory " + parent_path;
251 return false;
252 } else {
253 return false;
254 }
255 }
256
WriteToDisk(const std::string & path,const std::vector<const DexFile * > & dex_files,const verifier::VerifierDeps & verifier_deps,std::string * error_msg)257 bool VdexFile::WriteToDisk(const std::string& path,
258 const std::vector<const DexFile*>& dex_files,
259 const verifier::VerifierDeps& verifier_deps,
260 std::string* error_msg) {
261 std::vector<uint8_t> verifier_deps_data;
262 verifier_deps.Encode(dex_files, &verifier_deps_data);
263 uint32_t verifier_deps_size = verifier_deps_data.size();
264 // Add padding so the type lookup tables are 4 byte aligned.
265 uint32_t verifier_deps_with_padding_size = RoundUp(verifier_deps_data.size(), 4);
266 DCHECK_GE(verifier_deps_with_padding_size, verifier_deps_data.size());
267 verifier_deps_data.resize(verifier_deps_with_padding_size, 0);
268
269 size_t type_lookup_table_size = 0u;
270 for (const DexFile* dex_file : dex_files) {
271 type_lookup_table_size +=
272 sizeof(uint32_t) + TypeLookupTable::RawDataLength(dex_file->NumClassDefs());
273 }
274
275 VdexFile::VdexFileHeader vdex_header(/* has_dex_section= */ false);
276 VdexFile::VdexSectionHeader sections[static_cast<uint32_t>(VdexSection::kNumberOfSections)];
277
278 // Set checksum section.
279 sections[VdexSection::kChecksumSection].section_kind = VdexSection::kChecksumSection;
280 sections[VdexSection::kChecksumSection].section_offset = GetChecksumsOffset();
281 sections[VdexSection::kChecksumSection].section_size =
282 sizeof(VdexFile::VdexChecksum) * dex_files.size();
283
284 // Set dex section.
285 sections[VdexSection::kDexFileSection].section_kind = VdexSection::kDexFileSection;
286 sections[VdexSection::kDexFileSection].section_offset = 0u;
287 sections[VdexSection::kDexFileSection].section_size = 0u;
288
289 // Set VerifierDeps section.
290 sections[VdexSection::kVerifierDepsSection].section_kind = VdexSection::kVerifierDepsSection;
291 sections[VdexSection::kVerifierDepsSection].section_offset =
292 GetChecksumsOffset() + sections[kChecksumSection].section_size;
293 sections[VdexSection::kVerifierDepsSection].section_size = verifier_deps_size;
294
295 // Set TypeLookupTable section.
296 sections[VdexSection::kTypeLookupTableSection].section_kind =
297 VdexSection::kTypeLookupTableSection;
298 sections[VdexSection::kTypeLookupTableSection].section_offset =
299 sections[VdexSection::kVerifierDepsSection].section_offset + verifier_deps_with_padding_size;
300 sections[VdexSection::kTypeLookupTableSection].section_size = type_lookup_table_size;
301
302 if (!CreateDirectories(path, error_msg)) {
303 return false;
304 }
305
306 std::unique_ptr<File> out(OS::CreateEmptyFileWriteOnly(path.c_str()));
307 if (out == nullptr) {
308 *error_msg = "Could not open " + path + " for writing";
309 return false;
310 }
311
312 // Write header.
313 if (!out->WriteFully(reinterpret_cast<const char*>(&vdex_header), sizeof(vdex_header))) {
314 *error_msg = "Could not write vdex header to " + path;
315 out->Unlink();
316 return false;
317 }
318
319 // Write section infos.
320 if (!out->WriteFully(reinterpret_cast<const char*>(§ions), sizeof(sections))) {
321 *error_msg = "Could not write vdex sections to " + path;
322 out->Unlink();
323 return false;
324 }
325
326 // Write checksum section.
327 for (const DexFile* dex_file : dex_files) {
328 uint32_t checksum = dex_file->GetLocationChecksum();
329 const uint32_t* checksum_ptr = &checksum;
330 static_assert(sizeof(*checksum_ptr) == sizeof(VdexFile::VdexChecksum));
331 if (!out->WriteFully(reinterpret_cast<const char*>(checksum_ptr),
332 sizeof(VdexFile::VdexChecksum))) {
333 *error_msg = "Could not write dex checksums to " + path;
334 out->Unlink();
335 return false;
336 }
337 }
338
339 if (!out->WriteFully(reinterpret_cast<const char*>(verifier_deps_data.data()),
340 verifier_deps_with_padding_size)) {
341 *error_msg = "Could not write verifier deps to " + path;
342 out->Unlink();
343 return false;
344 }
345
346 size_t written_type_lookup_table_size = 0;
347 for (const DexFile* dex_file : dex_files) {
348 TypeLookupTable type_lookup_table = TypeLookupTable::Create(*dex_file);
349 uint32_t size = type_lookup_table.RawDataLength();
350 DCHECK_ALIGNED(size, 4);
351 if (!out->WriteFully(reinterpret_cast<const char*>(&size), sizeof(uint32_t)) ||
352 !out->WriteFully(reinterpret_cast<const char*>(type_lookup_table.RawData()), size)) {
353 *error_msg = "Could not write type lookup table " + path;
354 out->Unlink();
355 return false;
356 }
357 written_type_lookup_table_size += sizeof(uint32_t) + size;
358 }
359 DCHECK_EQ(written_type_lookup_table_size, type_lookup_table_size);
360
361 if (out->FlushClose() != 0) {
362 *error_msg = "Could not flush and close " + path;
363 out->Unlink();
364 return false;
365 }
366
367 return true;
368 }
369
MatchesDexFileChecksums(const std::vector<const DexFile::Header * > & dex_headers) const370 bool VdexFile::MatchesDexFileChecksums(const std::vector<const DexFile::Header*>& dex_headers)
371 const {
372 if (dex_headers.size() != GetNumberOfDexFiles()) {
373 LOG(WARNING) << "Mismatch of number of dex files in vdex (expected="
374 << GetNumberOfDexFiles() << ", actual=" << dex_headers.size() << ")";
375 return false;
376 }
377 const VdexChecksum* checksums = GetDexChecksumsArray();
378 for (size_t i = 0; i < dex_headers.size(); ++i) {
379 if (checksums[i] != dex_headers[i]->checksum_) {
380 LOG(WARNING) << "Mismatch of dex file checksum in vdex (index=" << i << ")";
381 return false;
382 }
383 }
384 return true;
385 }
386
HasOnlyStandardDexFiles() const387 bool VdexFile::HasOnlyStandardDexFiles() const {
388 // All are the same so it's enough to check the first one.
389 const uint8_t* dex_file_start = GetNextDexFileData(nullptr, 0);
390 return dex_file_start == nullptr || StandardDexFile::IsMagicValid(dex_file_start);
391 }
392
FindClassAndClearException(ClassLinker * class_linker,Thread * self,const char * name,Handle<mirror::ClassLoader> class_loader)393 static ObjPtr<mirror::Class> FindClassAndClearException(ClassLinker* class_linker,
394 Thread* self,
395 const char* name,
396 Handle<mirror::ClassLoader> class_loader)
397 REQUIRES_SHARED(Locks::mutator_lock_) {
398 ObjPtr<mirror::Class> result = class_linker->FindClass(self, name, class_loader);
399 if (result == nullptr) {
400 DCHECK(self->IsExceptionPending());
401 self->ClearException();
402 }
403 return result;
404 }
405
GetStringFromId(const DexFile & dex_file,dex::StringIndex string_id,uint32_t number_of_extra_strings,const uint32_t * extra_strings_offsets,const uint8_t * verifier_deps)406 static const char* GetStringFromId(const DexFile& dex_file,
407 dex::StringIndex string_id,
408 uint32_t number_of_extra_strings,
409 const uint32_t* extra_strings_offsets,
410 const uint8_t* verifier_deps) {
411 uint32_t num_ids_in_dex = dex_file.NumStringIds();
412 if (string_id.index_ < num_ids_in_dex) {
413 return dex_file.GetStringData(string_id);
414 } else {
415 CHECK_LT(string_id.index_ - num_ids_in_dex, number_of_extra_strings);
416 uint32_t offset = extra_strings_offsets[string_id.index_ - num_ids_in_dex];
417 return reinterpret_cast<const char*>(verifier_deps) + offset;
418 }
419 }
420
421 // Returns an array of offsets where the assignability checks for each class
422 // definition are stored.
GetDexFileClassDefs(const uint8_t * verifier_deps,uint32_t index)423 static const uint32_t* GetDexFileClassDefs(const uint8_t* verifier_deps, uint32_t index) {
424 uint32_t dex_file_offset = reinterpret_cast<const uint32_t*>(verifier_deps)[index];
425 return reinterpret_cast<const uint32_t*>(verifier_deps + dex_file_offset);
426 }
427
428 // Returns an array of offsets where extra strings are stored.
GetExtraStringsOffsets(const DexFile & dex_file,const uint8_t * verifier_deps,const uint32_t * dex_file_class_defs,uint32_t * number_of_extra_strings)429 static const uint32_t* GetExtraStringsOffsets(const DexFile& dex_file,
430 const uint8_t* verifier_deps,
431 const uint32_t* dex_file_class_defs,
432 /*out*/ uint32_t* number_of_extra_strings) {
433 // The information for strings is right after dex_file_class_defs, 4-byte
434 // aligned
435 uint32_t end_of_assignability_types = dex_file_class_defs[dex_file.NumClassDefs()];
436 const uint8_t* strings_data_start =
437 AlignUp(verifier_deps + end_of_assignability_types, sizeof(uint32_t));
438 // First entry is the number of extra strings for this dex file.
439 *number_of_extra_strings = *reinterpret_cast<const uint32_t*>(strings_data_start);
440 // Then an array of offsets in `verifier_deps` for the extra strings.
441 return reinterpret_cast<const uint32_t*>(strings_data_start + sizeof(uint32_t));
442 }
443
ComputeClassStatus(Thread * self,Handle<mirror::Class> cls) const444 ClassStatus VdexFile::ComputeClassStatus(Thread* self, Handle<mirror::Class> cls) const {
445 const DexFile& dex_file = cls->GetDexFile();
446 uint16_t class_def_index = cls->GetDexClassDefIndex();
447
448 // Find which dex file index from within the vdex file.
449 uint32_t index = 0;
450 for (; index < GetNumberOfDexFiles(); ++index) {
451 if (dex_file.GetLocationChecksum() == GetLocationChecksum(index)) {
452 break;
453 }
454 }
455
456 DCHECK_NE(index, GetNumberOfDexFiles());
457
458 const uint8_t* verifier_deps = GetVerifierDepsData().data();
459 const uint32_t* dex_file_class_defs = GetDexFileClassDefs(verifier_deps, index);
460
461 // Fetch type checks offsets.
462 uint32_t class_def_offset = dex_file_class_defs[class_def_index];
463 if (class_def_offset == verifier::VerifierDeps::kNotVerifiedMarker) {
464 // Return a status that needs re-verification.
465 return ClassStatus::kResolved;
466 }
467 // End offset for this class's type checks. We know there is one and the loop
468 // will terminate.
469 uint32_t end_offset = verifier::VerifierDeps::kNotVerifiedMarker;
470 for (uint32_t i = class_def_index + 1; i < dex_file.NumClassDefs() + 1; ++i) {
471 end_offset = dex_file_class_defs[i];
472 if (end_offset != verifier::VerifierDeps::kNotVerifiedMarker) {
473 break;
474 }
475 }
476 DCHECK_NE(end_offset, verifier::VerifierDeps::kNotVerifiedMarker);
477
478 uint32_t number_of_extra_strings = 0;
479 // Offset where extra strings are stored.
480 const uint32_t* extra_strings_offsets = GetExtraStringsOffsets(dex_file,
481 verifier_deps,
482 dex_file_class_defs,
483 &number_of_extra_strings);
484
485 // Loop over and perform each assignability check.
486 StackHandleScope<3> hs(self);
487 ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
488 Handle<mirror::ClassLoader> class_loader(hs.NewHandle(cls->GetClassLoader()));
489 MutableHandle<mirror::Class> source(hs.NewHandle<mirror::Class>(nullptr));
490 MutableHandle<mirror::Class> destination(hs.NewHandle<mirror::Class>(nullptr));
491
492 const uint8_t* cursor = verifier_deps + class_def_offset;
493 const uint8_t* end = verifier_deps + end_offset;
494 while (cursor < end) {
495 uint32_t destination_index;
496 uint32_t source_index;
497 if (UNLIKELY(!DecodeUnsignedLeb128Checked(&cursor, end, &destination_index) ||
498 !DecodeUnsignedLeb128Checked(&cursor, end, &source_index))) {
499 // Error parsing the data, just return that we are not verified.
500 return ClassStatus::kResolved;
501 }
502 const char* destination_desc = GetStringFromId(dex_file,
503 dex::StringIndex(destination_index),
504 number_of_extra_strings,
505 extra_strings_offsets,
506 verifier_deps);
507 destination.Assign(
508 FindClassAndClearException(class_linker, self, destination_desc, class_loader));
509
510 const char* source_desc = GetStringFromId(dex_file,
511 dex::StringIndex(source_index),
512 number_of_extra_strings,
513 extra_strings_offsets,
514 verifier_deps);
515 source.Assign(FindClassAndClearException(class_linker, self, source_desc, class_loader));
516
517 if (destination == nullptr || source == nullptr) {
518 // The interpreter / compiler can handle a missing class.
519 continue;
520 }
521
522 DCHECK(destination->IsResolved() && source->IsResolved());
523 if (!destination->IsAssignableFrom(source.Get())) {
524 VLOG(verifier) << "Vdex checking failed for " << cls->PrettyClass()
525 << ": expected " << destination->PrettyClass()
526 << " to be assignable from " << source->PrettyClass();
527 // An implicit assignability check is failing in the code, return that the
528 // class is not verified.
529 return ClassStatus::kResolved;
530 }
531 }
532
533 return ClassStatus::kVerifiedNeedsAccessChecks;
534 }
535
536 } // namespace art
537