1 /*
2  * Copyright (C) 2017 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 "compact_dex_writer.h"
18 
19 #include "android-base/stringprintf.h"
20 #include "base/logging.h"
21 #include "base/time_utils.h"
22 #include "dex/compact_dex_file.h"
23 #include "dex/compact_offset_table.h"
24 #include "dexlayout.h"
25 
26 namespace art {
27 
CompactDexWriter(DexLayout * dex_layout)28 CompactDexWriter::CompactDexWriter(DexLayout* dex_layout)
29     : DexWriter(dex_layout, /*compute_offsets*/ true) {
30   CHECK(GetCompactDexLevel() != CompactDexLevel::kCompactDexLevelNone);
31 }
32 
GetCompactDexLevel() const33 CompactDexLevel CompactDexWriter::GetCompactDexLevel() const {
34   return dex_layout_->GetOptions().compact_dex_level_;
35 }
36 
Container(bool dedupe_code_items)37 CompactDexWriter::Container::Container(bool dedupe_code_items)
38     : code_item_dedupe_(dedupe_code_items, &data_section_),
39       data_item_dedupe_(/*dedupe*/ true, &data_section_) {}
40 
WriteDebugInfoOffsetTable(Stream * stream)41 uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(Stream* stream) {
42   const uint32_t start_offset = stream->Tell();
43   const dex_ir::Collections& collections = header_->GetCollections();
44   // Debug offsets for method indexes. 0 means no debug info.
45   std::vector<uint32_t> debug_info_offsets(collections.MethodIdsSize(), 0u);
46 
47   static constexpr InvokeType invoke_types[] = {
48     kDirect,
49     kVirtual
50   };
51 
52   for (InvokeType invoke_type : invoke_types) {
53     for (const std::unique_ptr<dex_ir::ClassDef>& class_def : collections.ClassDefs()) {
54       // Skip classes that are not defined in this dex file.
55       dex_ir::ClassData* class_data = class_def->GetClassData();
56       if (class_data == nullptr) {
57         continue;
58       }
59       for (auto& method : *(invoke_type == InvokeType::kDirect
60                                 ? class_data->DirectMethods()
61                                 : class_data->VirtualMethods())) {
62         const dex_ir::MethodId* method_id = method->GetMethodId();
63         dex_ir::CodeItem* code_item = method->GetCodeItem();
64         if (code_item != nullptr && code_item->DebugInfo() != nullptr) {
65           const uint32_t debug_info_offset = code_item->DebugInfo()->GetOffset();
66           const uint32_t method_idx = method_id->GetIndex();
67           if (debug_info_offsets[method_idx] != 0u) {
68             CHECK_EQ(debug_info_offset, debug_info_offsets[method_idx]);
69           }
70           debug_info_offsets[method_idx] = debug_info_offset;
71         }
72       }
73     }
74   }
75 
76   std::vector<uint8_t> data;
77   debug_info_base_ = 0u;
78   debug_info_offsets_table_offset_ = 0u;
79   CompactOffsetTable::Build(debug_info_offsets,
80                             &data,
81                             &debug_info_base_,
82                             &debug_info_offsets_table_offset_);
83   // Align the table and write it out.
84   stream->AlignTo(CompactOffsetTable::kAlignment);
85   debug_info_offsets_pos_ = stream->Tell();
86   stream->Write(data.data(), data.size());
87 
88   // Verify that the whole table decodes as expected and measure average performance.
89   const bool kMeasureAndTestOutput = dex_layout_->GetOptions().verify_output_;
90   if (kMeasureAndTestOutput && !debug_info_offsets.empty()) {
91     uint64_t start_time = NanoTime();
92     stream->Begin();
93     CompactOffsetTable::Accessor accessor(stream->Begin() + debug_info_offsets_pos_,
94                                           debug_info_base_,
95                                           debug_info_offsets_table_offset_);
96 
97     for (size_t i = 0; i < debug_info_offsets.size(); ++i) {
98       CHECK_EQ(accessor.GetOffset(i), debug_info_offsets[i]);
99     }
100     uint64_t end_time = NanoTime();
101     VLOG(dex) << "Average lookup time (ns) for debug info offsets: "
102               << (end_time - start_time) / debug_info_offsets.size();
103   }
104 
105   return stream->Tell() - start_offset;
106 }
107 
ScopedDataSectionItem(Stream * stream,dex_ir::Item * item,size_t alignment,Deduper * deduper)108 CompactDexWriter::ScopedDataSectionItem::ScopedDataSectionItem(Stream* stream,
109                                                                dex_ir::Item* item,
110                                                                size_t alignment,
111                                                                Deduper* deduper)
112     : stream_(stream),
113       item_(item),
114       alignment_(alignment),
115       deduper_(deduper),
116       start_offset_(stream->Tell()) {
117   stream_->AlignTo(alignment_);
118 }
119 
~ScopedDataSectionItem()120 CompactDexWriter::ScopedDataSectionItem::~ScopedDataSectionItem() {
121   // After having written, maybe dedupe the whole code item (excluding padding).
122   const uint32_t deduped_offset = deduper_->Dedupe(start_offset_,
123                                                    stream_->Tell(),
124                                                    item_->GetOffset());
125   // If we deduped, only use the deduped offset if the alignment matches the required alignment.
126   // Otherwise, return without deduping.
127   if (deduped_offset != Deduper::kDidNotDedupe && IsAlignedParam(deduped_offset, alignment_)) {
128     // Update the IR offset to the offset of the deduped item.
129     item_->SetOffset(deduped_offset);
130     // Clear the written data for the item so that the stream write doesn't abort in the future.
131     stream_->Clear(start_offset_, stream_->Tell() - start_offset_);
132     // Since we deduped, restore the offset to the original position.
133     stream_->Seek(start_offset_);
134   }
135 }
136 
Written() const137 size_t CompactDexWriter::ScopedDataSectionItem::Written() const {
138   return stream_->Tell() - start_offset_;
139 }
140 
WriteCodeItem(Stream * stream,dex_ir::CodeItem * code_item,bool reserve_only)141 void CompactDexWriter::WriteCodeItem(Stream* stream,
142                                      dex_ir::CodeItem* code_item,
143                                      bool reserve_only) {
144   DCHECK(code_item != nullptr);
145   DCHECK(!reserve_only) << "Not supported because of deduping.";
146   ScopedDataSectionItem data_item(stream,
147                                   code_item,
148                                   CompactDexFile::CodeItem::kAlignment,
149                                   code_item_dedupe_);
150 
151   CompactDexFile::CodeItem disk_code_item;
152 
153   uint16_t preheader_storage[CompactDexFile::CodeItem::kMaxPreHeaderSize] = {};
154   uint16_t* preheader_end = preheader_storage + CompactDexFile::CodeItem::kMaxPreHeaderSize;
155   const uint16_t* preheader = disk_code_item.Create(
156       code_item->RegistersSize(),
157       code_item->InsSize(),
158       code_item->OutsSize(),
159       code_item->TriesSize(),
160       code_item->InsnsSize(),
161       preheader_end);
162   const size_t preheader_bytes = (preheader_end - preheader) * sizeof(preheader[0]);
163 
164   static constexpr size_t kPayloadInstructionRequiredAlignment = 4;
165   const uint32_t current_code_item_start = stream->Tell() + preheader_bytes;
166   if (!IsAlignedParam(current_code_item_start, kPayloadInstructionRequiredAlignment) ||
167       kIsDebugBuild) {
168     // If the preheader is going to make the code unaligned, consider adding 2 bytes of padding
169     // before if required.
170     IterationRange<DexInstructionIterator> instructions = code_item->Instructions();
171     SafeDexInstructionIterator it(instructions.begin(), instructions.end());
172     for (; !it.IsErrorState() && it < instructions.end(); ++it) {
173       // In case the instruction goes past the end of the code item, make sure to not process it.
174       if (std::next(it).IsErrorState()) {
175         break;
176       }
177       const Instruction::Code opcode = it->Opcode();
178       // Payload instructions possibly require special alignment for their data.
179       if (opcode == Instruction::FILL_ARRAY_DATA ||
180           opcode == Instruction::PACKED_SWITCH ||
181           opcode == Instruction::SPARSE_SWITCH) {
182         stream->Skip(
183             RoundUp(current_code_item_start, kPayloadInstructionRequiredAlignment) -
184                 current_code_item_start);
185         break;
186       }
187     }
188   }
189 
190   // Write preheader first.
191   stream->Write(reinterpret_cast<const uint8_t*>(preheader), preheader_bytes);
192   // Registered offset is after the preheader.
193   ProcessOffset(stream, code_item);
194   // Avoid using sizeof so that we don't write the fake instruction array at the end of the code
195   // item.
196   stream->Write(&disk_code_item, OFFSETOF_MEMBER(CompactDexFile::CodeItem, insns_));
197   // Write the instructions.
198   stream->Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t));
199   // Write the post instruction data.
200   WriteCodeItemPostInstructionData(stream, code_item, reserve_only);
201 }
202 
WriteDebugInfoItem(Stream * stream,dex_ir::DebugInfoItem * debug_info)203 void CompactDexWriter::WriteDebugInfoItem(Stream* stream, dex_ir::DebugInfoItem* debug_info) {
204   ScopedDataSectionItem data_item(stream,
205                                   debug_info,
206                                   SectionAlignment(DexFile::kDexTypeDebugInfoItem),
207                                   data_item_dedupe_);
208   ProcessOffset(stream, debug_info);
209   stream->Write(debug_info->GetDebugInfo(), debug_info->GetDebugInfoSize());
210 }
211 
212 
Deduper(bool enabled,DexContainer::Section * section)213 CompactDexWriter::Deduper::Deduper(bool enabled, DexContainer::Section* section)
214     : enabled_(enabled),
215       dedupe_map_(/*bucket_count*/ 32,
216                   HashedMemoryRange::HashEqual(section),
217                   HashedMemoryRange::HashEqual(section)) {}
218 
Dedupe(uint32_t data_start,uint32_t data_end,uint32_t item_offset)219 uint32_t CompactDexWriter::Deduper::Dedupe(uint32_t data_start,
220                                            uint32_t data_end,
221                                            uint32_t item_offset) {
222   if (!enabled_) {
223     return kDidNotDedupe;
224   }
225   HashedMemoryRange range {data_start, data_end - data_start};
226   auto existing = dedupe_map_.emplace(range, item_offset);
227   if (!existing.second) {
228     // Failed to insert means we deduped, return the existing item offset.
229     return existing.first->second;
230   }
231   return kDidNotDedupe;
232 }
233 
SortDebugInfosByMethodIndex()234 void CompactDexWriter::SortDebugInfosByMethodIndex() {
235   dex_ir::Collections& collections = header_->GetCollections();
236   static constexpr InvokeType invoke_types[] = {
237     kDirect,
238     kVirtual
239   };
240   std::map<const dex_ir::DebugInfoItem*, uint32_t> method_idx_map;
241   for (InvokeType invoke_type : invoke_types) {
242     for (std::unique_ptr<dex_ir::ClassDef>& class_def : collections.ClassDefs()) {
243       // Skip classes that are not defined in this dex file.
244       dex_ir::ClassData* class_data = class_def->GetClassData();
245       if (class_data == nullptr) {
246         continue;
247       }
248       for (auto& method : *(invoke_type == InvokeType::kDirect
249                                 ? class_data->DirectMethods()
250                                 : class_data->VirtualMethods())) {
251         const dex_ir::MethodId* method_id = method->GetMethodId();
252         dex_ir::CodeItem* code_item = method->GetCodeItem();
253         if (code_item != nullptr && code_item->DebugInfo() != nullptr) {
254           const dex_ir::DebugInfoItem* debug_item = code_item->DebugInfo();
255           method_idx_map.insert(std::make_pair(debug_item, method_id->GetIndex()));
256         }
257       }
258     }
259   }
260   std::sort(collections.DebugInfoItems().begin(),
261             collections.DebugInfoItems().end(),
262             [&](const std::unique_ptr<dex_ir::DebugInfoItem>& a,
263                 const std::unique_ptr<dex_ir::DebugInfoItem>& b) {
264     auto it_a = method_idx_map.find(a.get());
265     auto it_b = method_idx_map.find(b.get());
266     uint32_t idx_a = it_a != method_idx_map.end() ? it_a->second : 0u;
267     uint32_t idx_b = it_b != method_idx_map.end() ? it_b->second : 0u;
268     return idx_a < idx_b;
269   });
270 }
271 
WriteHeader(Stream * stream)272 void CompactDexWriter::WriteHeader(Stream* stream) {
273   CompactDexFile::Header header;
274   CompactDexFile::WriteMagic(&header.magic_[0]);
275   CompactDexFile::WriteCurrentVersion(&header.magic_[0]);
276   header.checksum_ = header_->Checksum();
277   std::copy_n(header_->Signature(), DexFile::kSha1DigestSize, header.signature_);
278   header.file_size_ = header_->FileSize();
279   // Since we are not necessarily outputting the same format as the input, avoid using the stored
280   // header size.
281   header.header_size_ = GetHeaderSize();
282   header.endian_tag_ = header_->EndianTag();
283   header.link_size_ = header_->LinkSize();
284   header.link_off_ = header_->LinkOffset();
285   const dex_ir::Collections& collections = header_->GetCollections();
286   header.map_off_ = collections.MapListOffset();
287   header.string_ids_size_ = collections.StringIdsSize();
288   header.string_ids_off_ = collections.StringIdsOffset();
289   header.type_ids_size_ = collections.TypeIdsSize();
290   header.type_ids_off_ = collections.TypeIdsOffset();
291   header.proto_ids_size_ = collections.ProtoIdsSize();
292   header.proto_ids_off_ = collections.ProtoIdsOffset();
293   header.field_ids_size_ = collections.FieldIdsSize();
294   header.field_ids_off_ = collections.FieldIdsOffset();
295   header.method_ids_size_ = collections.MethodIdsSize();
296   header.method_ids_off_ = collections.MethodIdsOffset();
297   header.class_defs_size_ = collections.ClassDefsSize();
298   header.class_defs_off_ = collections.ClassDefsOffset();
299   header.data_size_ = header_->DataSize();
300   header.data_off_ = header_->DataOffset();
301   header.owned_data_begin_ = owned_data_begin_;
302   header.owned_data_end_ = owned_data_end_;
303 
304   // Compact dex specific flags.
305   header.debug_info_offsets_pos_ = debug_info_offsets_pos_;
306   header.debug_info_offsets_table_offset_ = debug_info_offsets_table_offset_;
307   header.debug_info_base_ = debug_info_base_;
308   header.feature_flags_ = 0u;
309   // In cases where apps are converted to cdex during install, maintain feature flags so that
310   // the verifier correctly verifies apps that aren't targetting default methods.
311   if (header_->SupportDefaultMethods()) {
312     header.feature_flags_ |= static_cast<uint32_t>(CompactDexFile::FeatureFlags::kDefaultMethods);
313   }
314   stream->Seek(0);
315   stream->Overwrite(reinterpret_cast<uint8_t*>(&header), sizeof(header));
316 }
317 
GetHeaderSize() const318 size_t CompactDexWriter::GetHeaderSize() const {
319   return sizeof(CompactDexFile::Header);
320 }
321 
WriteStringData(Stream * stream,dex_ir::StringData * string_data)322 void CompactDexWriter::WriteStringData(Stream* stream, dex_ir::StringData* string_data) {
323   ScopedDataSectionItem data_item(stream,
324                                   string_data,
325                                   SectionAlignment(DexFile::kDexTypeStringDataItem),
326                                   data_item_dedupe_);
327   ProcessOffset(stream, string_data);
328   stream->WriteUleb128(CountModifiedUtf8Chars(string_data->Data()));
329   stream->Write(string_data->Data(), strlen(string_data->Data()));
330   // Skip null terminator (already zeroed out, no need to write).
331   stream->Skip(1);
332 }
333 
CanGenerateCompactDex(std::string * error_msg)334 bool CompactDexWriter::CanGenerateCompactDex(std::string* error_msg) {
335   dex_ir::Collections& collections = header_->GetCollections();
336   static constexpr InvokeType invoke_types[] = {
337     kDirect,
338     kVirtual
339   };
340   std::vector<bool> saw_method_id(collections.MethodIdsSize(), false);
341   std::vector<dex_ir::CodeItem*> method_id_code_item(collections.MethodIdsSize(), nullptr);
342   std::vector<dex_ir::DebugInfoItem*> method_id_debug_info(collections.MethodIdsSize(), nullptr);
343   for (InvokeType invoke_type : invoke_types) {
344     for (std::unique_ptr<dex_ir::ClassDef>& class_def : collections.ClassDefs()) {
345       // Skip classes that are not defined in this dex file.
346       dex_ir::ClassData* class_data = class_def->GetClassData();
347       if (class_data == nullptr) {
348         continue;
349       }
350       for (auto& method : *(invoke_type == InvokeType::kDirect
351                                 ? class_data->DirectMethods()
352                                 : class_data->VirtualMethods())) {
353         const uint32_t idx = method->GetMethodId()->GetIndex();
354         dex_ir::CodeItem* code_item = method->GetCodeItem();
355         dex_ir:: DebugInfoItem* debug_info_item = nullptr;
356         if (code_item != nullptr) {
357           debug_info_item = code_item->DebugInfo();
358         }
359         if (saw_method_id[idx]) {
360           if (method_id_code_item[idx] != code_item) {
361             *error_msg = android::base::StringPrintf("Conflicting code item for method id %u",
362                                                      idx);
363             // Conflicting info, abort generation.
364             return false;
365           }
366           if (method_id_debug_info[idx] != debug_info_item) {
367             *error_msg = android::base::StringPrintf("Conflicting debug info for method id %u",
368                                                      idx);
369             // Conflicting info, abort generation.
370             return false;
371           }
372         }
373         method_id_code_item[idx] = code_item;
374         method_id_debug_info[idx] = debug_info_item;
375         saw_method_id[idx] = true;
376       }
377     }
378   }
379   return true;
380 }
381 
Write(DexContainer * output,std::string * error_msg)382 bool CompactDexWriter::Write(DexContainer* output, std::string* error_msg)  {
383   DCHECK(error_msg != nullptr);
384   CHECK(compute_offsets_);
385   CHECK(output->IsCompactDexContainer());
386 
387   if (!CanGenerateCompactDex(error_msg)) {
388     return false;
389   }
390 
391   Container* const container = down_cast<Container*>(output);
392   // For now, use the same stream for both data and metadata.
393   Stream temp_main_stream(output->GetMainSection());
394   CHECK_EQ(output->GetMainSection()->Size(), 0u);
395   Stream temp_data_stream(output->GetDataSection());
396   Stream* main_stream = &temp_main_stream;
397   Stream* data_stream = &temp_data_stream;
398 
399   // We want offset 0 to be reserved for null, seek to the data section alignment or the end of the
400   // section.
401   data_stream->Seek(std::max(
402       static_cast<uint32_t>(output->GetDataSection()->Size()),
403       kDataSectionAlignment));
404   code_item_dedupe_ = &container->code_item_dedupe_;
405   data_item_dedupe_ = &container->data_item_dedupe_;
406 
407   // Starting offset is right after the header.
408   main_stream->Seek(GetHeaderSize());
409 
410   dex_ir::Collections& collection = header_->GetCollections();
411 
412   // Based on: https://source.android.com/devices/tech/dalvik/dex-format
413   // Since the offsets may not be calculated already, the writing must be done in the correct order.
414   const uint32_t string_ids_offset = main_stream->Tell();
415   WriteStringIds(main_stream, /*reserve_only*/ true);
416   WriteTypeIds(main_stream);
417   const uint32_t proto_ids_offset = main_stream->Tell();
418   WriteProtoIds(main_stream, /*reserve_only*/ true);
419   WriteFieldIds(main_stream);
420   WriteMethodIds(main_stream);
421   const uint32_t class_defs_offset = main_stream->Tell();
422   WriteClassDefs(main_stream, /*reserve_only*/ true);
423   const uint32_t call_site_ids_offset = main_stream->Tell();
424   WriteCallSiteIds(main_stream, /*reserve_only*/ true);
425   WriteMethodHandles(main_stream);
426 
427   if (compute_offsets_) {
428     // Data section.
429     data_stream->AlignTo(kDataSectionAlignment);
430   }
431   owned_data_begin_ = data_stream->Tell();
432 
433   // Write code item first to minimize the space required for encoded methods.
434   // For cdex, the code items don't depend on the debug info.
435   WriteCodeItems(data_stream, /*reserve_only*/ false);
436 
437   // Sort the debug infos by method index order, this reduces size by ~0.1% by reducing the size of
438   // the debug info offset table.
439   SortDebugInfosByMethodIndex();
440   WriteDebugInfoItems(data_stream);
441 
442   WriteEncodedArrays(data_stream);
443   WriteAnnotations(data_stream);
444   WriteAnnotationSets(data_stream);
445   WriteAnnotationSetRefs(data_stream);
446   WriteAnnotationsDirectories(data_stream);
447   WriteTypeLists(data_stream);
448   WriteClassDatas(data_stream);
449   WriteStringDatas(data_stream);
450 
451   // Write delayed id sections that depend on data sections.
452   {
453     Stream::ScopedSeek seek(main_stream, string_ids_offset);
454     WriteStringIds(main_stream, /*reserve_only*/ false);
455   }
456   {
457     Stream::ScopedSeek seek(main_stream, proto_ids_offset);
458     WriteProtoIds(main_stream, /*reserve_only*/ false);
459   }
460   {
461     Stream::ScopedSeek seek(main_stream, class_defs_offset);
462     WriteClassDefs(main_stream, /*reserve_only*/ false);
463   }
464   {
465     Stream::ScopedSeek seek(main_stream, call_site_ids_offset);
466     WriteCallSiteIds(main_stream, /*reserve_only*/ false);
467   }
468 
469   // Write the map list.
470   if (compute_offsets_) {
471     data_stream->AlignTo(SectionAlignment(DexFile::kDexTypeMapList));
472     collection.SetMapListOffset(data_stream->Tell());
473   } else {
474     data_stream->Seek(collection.MapListOffset());
475   }
476 
477   // Map items are included in the data section.
478   GenerateAndWriteMapItems(data_stream);
479 
480   // Write link data if it exists.
481   const std::vector<uint8_t>& link_data = collection.LinkData();
482   if (link_data.size() > 0) {
483     CHECK_EQ(header_->LinkSize(), static_cast<uint32_t>(link_data.size()));
484     if (compute_offsets_) {
485       header_->SetLinkOffset(data_stream->Tell());
486     } else {
487       data_stream->Seek(header_->LinkOffset());
488     }
489     data_stream->Write(&link_data[0], link_data.size());
490   }
491 
492   // Write debug info offset table last to make dex file verifier happy.
493   WriteDebugInfoOffsetTable(data_stream);
494 
495   data_stream->AlignTo(kDataSectionAlignment);
496   owned_data_end_ = data_stream->Tell();
497   if (compute_offsets_) {
498     header_->SetDataSize(data_stream->Tell());
499     if (header_->DataSize() != 0) {
500       // Offset must be zero when the size is zero.
501       main_stream->AlignTo(kDataSectionAlignment);
502       // For now, default to saying the data is right after the main stream.
503       header_->SetDataOffset(main_stream->Tell());
504     } else {
505       header_->SetDataOffset(0u);
506     }
507   }
508 
509   // Write header last.
510   if (compute_offsets_) {
511     header_->SetFileSize(main_stream->Tell());
512   }
513   WriteHeader(main_stream);
514 
515   // Trim sections to make sure they are sized properly.
516   output->GetMainSection()->Resize(header_->FileSize());
517   output->GetDataSection()->Resize(data_stream->Tell());
518 
519   if (dex_layout_->GetOptions().update_checksum_) {
520     // Compute the cdex section (also covers the used part of the data section).
521     header_->SetChecksum(CompactDexFile::CalculateChecksum(output->GetMainSection()->Begin(),
522                                                            output->GetMainSection()->Size(),
523                                                            output->GetDataSection()->Begin(),
524                                                            output->GetDataSection()->Size()));
525     // Rewrite the header with the calculated checksum.
526     WriteHeader(main_stream);
527   }
528 
529   // Clear the dedupe to prevent interdex code item deduping. This does not currently work well with
530   // dex2oat's class unloading. The issue is that verification encounters quickened opcodes after
531   // the first dex gets unloaded.
532   code_item_dedupe_->Clear();
533 
534   return true;
535 }
536 
CreateDexContainer() const537 std::unique_ptr<DexContainer> CompactDexWriter::CreateDexContainer() const {
538   return std::unique_ptr<DexContainer>(
539       new CompactDexWriter::Container(dex_layout_->GetOptions().dedupe_code_items_));
540 }
541 
542 }  // namespace art
543