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