2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.dx.merge;
19 import com.android.dex.Annotation;
20 import com.android.dex.ClassData;
21 import com.android.dex.ClassDef;
22 import com.android.dex.Code;
23 import com.android.dex.Dex;
24 import com.android.dex.DexException;
25 import com.android.dex.FieldId;
26 import com.android.dex.MethodId;
27 import com.android.dex.ProtoId;
28 import com.android.dex.SizeOf;
29 import com.android.dex.TableOfContents;
30 import com.android.dex.TypeList;
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.List;
39 * Combine two dex files into one.
41 public final class DexMerger {
42 private final Dex dexA;
43 private final Dex dexB;
44 private final CollisionPolicy collisionPolicy;
45 private final WriterSizes writerSizes;
47 private final Dex dexOut;
49 private final Dex.Section headerOut;
51 /** All IDs and definitions sections */
52 private final Dex.Section idsDefsOut;
54 private final Dex.Section mapListOut;
56 private final Dex.Section typeListOut;
58 private final Dex.Section classDataOut;
60 private final Dex.Section codeOut;
62 private final Dex.Section stringDataOut;
64 private final Dex.Section debugInfoOut;
66 private final Dex.Section encodedArrayOut;
68 /** annotations directory on a type */
69 private final Dex.Section annotationsDirectoryOut;
71 /** sets of annotations on a member, parameter or type */
72 private final Dex.Section annotationSetOut;
74 /** parameter lists */
75 private final Dex.Section annotationSetRefListOut;
77 /** individual annotations, each containing zero or more fields */
78 private final Dex.Section annotationOut;
80 private final TableOfContents contentsOut;
82 private final IndexMap aIndexMap;
83 private final IndexMap bIndexMap;
84 private final InstructionTransformer aInstructionTransformer;
85 private final InstructionTransformer bInstructionTransformer;
87 /** minimum number of wasted bytes before it's worthwhile to compact the result */
88 private int compactWasteThreshold = 1024 * 1024; // 1MiB
90 public DexMerger(Dex dexA, Dex dexB, CollisionPolicy collisionPolicy)
92 this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB));
95 private DexMerger(Dex dexA, Dex dexB, CollisionPolicy collisionPolicy,
96 WriterSizes writerSizes) throws IOException {
99 this.collisionPolicy = collisionPolicy;
100 this.writerSizes = writerSizes;
102 dexOut = new Dex(writerSizes.size());
104 TableOfContents aContents = dexA.getTableOfContents();
105 TableOfContents bContents = dexB.getTableOfContents();
106 aIndexMap = new IndexMap(dexOut, aContents);
107 bIndexMap = new IndexMap(dexOut, bContents);
108 aInstructionTransformer = new InstructionTransformer(aIndexMap);
109 bInstructionTransformer = new InstructionTransformer(bIndexMap);
111 headerOut = dexOut.appendSection(writerSizes.header, "header");
112 idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs");
114 contentsOut = dexOut.getTableOfContents();
115 contentsOut.dataOff = dexOut.getNextSectionStart();
117 contentsOut.mapList.off = dexOut.getNextSectionStart();
118 contentsOut.mapList.size = 1;
119 mapListOut = dexOut.appendSection(writerSizes.mapList, "map list");
121 contentsOut.typeLists.off = dexOut.getNextSectionStart();
122 typeListOut = dexOut.appendSection(writerSizes.typeList, "type list");
124 contentsOut.annotationSetRefLists.off = dexOut.getNextSectionStart();
125 annotationSetRefListOut = dexOut.appendSection(
126 writerSizes.annotationsSetRefList, "annotation set ref list");
128 contentsOut.annotationSets.off = dexOut.getNextSectionStart();
129 annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets");
131 contentsOut.classDatas.off = dexOut.getNextSectionStart();
132 classDataOut = dexOut.appendSection(writerSizes.classData, "class data");
134 contentsOut.codes.off = dexOut.getNextSectionStart();
135 codeOut = dexOut.appendSection(writerSizes.code, "code");
137 contentsOut.stringDatas.off = dexOut.getNextSectionStart();
138 stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data");
140 contentsOut.debugInfos.off = dexOut.getNextSectionStart();
141 debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info");
143 contentsOut.annotations.off = dexOut.getNextSectionStart();
144 annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation");
146 contentsOut.encodedArrays.off = dexOut.getNextSectionStart();
147 encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array");
149 contentsOut.annotationsDirectories.off = dexOut.getNextSectionStart();
150 annotationsDirectoryOut = dexOut.appendSection(
151 writerSizes.annotationsDirectory, "annotations directory");
153 contentsOut.dataSize = dexOut.getNextSectionStart() - contentsOut.dataOff;
156 public void setCompactWasteThreshold(int compactWasteThreshold) {
157 this.compactWasteThreshold = compactWasteThreshold;
160 private Dex mergeDexes() throws IOException {
168 unionAnnotationSetsAndDirectories();
172 contentsOut.header.off = 0;
173 contentsOut.header.size = 1;
174 contentsOut.fileSize = dexOut.getLength();
175 contentsOut.computeSizesFromOffsets();
176 contentsOut.writeHeader(headerOut);
177 contentsOut.writeMap(mapListOut);
179 // generate and write the hashes
180 dexOut.writeHashes();
185 public Dex merge() throws IOException {
186 long start = System.nanoTime();
187 Dex result = mergeDexes();
190 * We use pessimistic sizes when merging dex files. If those sizes
191 * result in too many bytes wasted, compact the result. To compact,
192 * simply merge the result with itself.
194 WriterSizes compactedSizes = new WriterSizes(this);
195 int wastedByteCount = writerSizes.size() - compactedSizes.size();
196 if (wastedByteCount > + compactWasteThreshold) {
197 DexMerger compacter = new DexMerger(
198 dexOut, new Dex(0), CollisionPolicy.FAIL, compactedSizes);
199 result = compacter.mergeDexes();
200 System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n",
201 dexOut.getLength() / 1024f,
202 result.getLength() / 1024f,
203 wastedByteCount / 1024f);
206 long elapsed = System.nanoTime() - start;
207 System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B "
208 + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n",
209 dexA.getTableOfContents().classDefs.size,
210 dexA.getLength() / 1024f,
211 dexB.getTableOfContents().classDefs.size,
212 dexB.getLength() / 1024f,
213 result.getTableOfContents().classDefs.size,
214 result.getLength() / 1024f,
215 elapsed / 1000000000f);
221 * Reads an IDs section of two dex files and writes an IDs section of a
222 * merged dex file. Populates maps from old to new indices in the process.
224 abstract class IdMerger<T extends Comparable<T>> {
225 private final Dex.Section out;
227 protected IdMerger(Dex.Section out) {
232 * Merges already-sorted sections, reading only two values into memory
235 public final void mergeSorted() {
236 TableOfContents.Section aSection = getSection(dexA.getTableOfContents());
237 TableOfContents.Section bSection = getSection(dexB.getTableOfContents());
238 getSection(contentsOut).off = out.getPosition();
240 Dex.Section inA = aSection.exists() ? dexA.open(aSection.off) : null;
241 Dex.Section inB = bSection.exists() ? dexB.open(bSection.off) : null;
251 if (a == null && aIndex < aSection.size) {
252 aOffset = inA.getPosition();
253 a = read(inA, aIndexMap, aIndex);
255 if (b == null && bIndex < bSection.size) {
256 bOffset = inB.getPosition();
257 b = read(inB, bIndexMap, bIndex);
260 // Write the smaller of a and b. If they're equal, write only once
263 if (a != null && b != null) {
264 int compare = a.compareTo(b);
265 advanceA = compare <= 0;
266 advanceB = compare >= 0;
268 advanceA = (a != null);
269 advanceB = (b != null);
275 updateIndex(aOffset, aIndexMap, aIndex++, outCount);
281 updateIndex(bOffset, bIndexMap, bIndex++, outCount);
285 if (toWrite == null) {
286 break; // advanceA == false && advanceB == false
292 getSection(contentsOut).size = outCount;
296 * Merges unsorted sections by reading them completely into memory and
299 public final void mergeUnsorted() {
300 getSection(contentsOut).off = out.getPosition();
302 List<UnsortedValue> all = new ArrayList<UnsortedValue>();
303 all.addAll(readUnsortedValues(dexA, aIndexMap));
304 all.addAll(readUnsortedValues(dexB, bIndexMap));
305 Collections.sort(all);
308 for (int i = 0; i < all.size(); ) {
309 UnsortedValue e1 = all.get(i++);
310 updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1);
312 while (i < all.size() && e1.compareTo(all.get(i)) == 0) {
313 UnsortedValue e2 = all.get(i++);
314 updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1);
321 getSection(contentsOut).size = outCount;
324 private List<UnsortedValue> readUnsortedValues(Dex source, IndexMap indexMap) {
325 TableOfContents.Section section = getSection(source.getTableOfContents());
326 if (!section.exists()) {
327 return Collections.emptyList();
330 List<UnsortedValue> result = new ArrayList<UnsortedValue>();
331 Dex.Section in = source.open(section.off);
332 for (int i = 0; i < section.size; i++) {
333 int offset = in.getPosition();
334 T value = read(in, indexMap, 0);
335 result.add(new UnsortedValue(source, indexMap, value, i, offset));
340 abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
341 abstract T read(Dex.Section in, IndexMap indexMap, int index);
342 abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex);
343 abstract void write(T value);
345 class UnsortedValue implements Comparable<UnsortedValue> {
347 final IndexMap indexMap;
352 UnsortedValue(Dex source, IndexMap indexMap, T value, int index, int offset) {
353 this.source = source;
354 this.indexMap = indexMap;
357 this.offset = offset;
360 public int compareTo(UnsortedValue unsortedValue) {
361 return value.compareTo(unsortedValue.value);
366 private IndexMap getIndexMap(Dex dex) {
369 } else if (dex == dexB) {
372 throw new IllegalArgumentException();
376 private void mergeStringIds() {
377 new IdMerger<String>(idsDefsOut) {
378 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
379 return tableOfContents.stringIds;
382 @Override String read(Dex.Section in, IndexMap indexMap, int index) {
383 return in.readString();
386 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
387 indexMap.stringIds[oldIndex] = newIndex;
390 @Override void write(String value) {
391 contentsOut.stringDatas.size++;
392 idsDefsOut.writeInt(stringDataOut.getPosition());
393 stringDataOut.writeStringData(value);
398 private void mergeTypeIds() {
399 new IdMerger<Integer>(idsDefsOut) {
400 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
401 return tableOfContents.typeIds;
404 @Override Integer read(Dex.Section in, IndexMap indexMap, int index) {
405 int stringIndex = in.readInt();
406 return indexMap.adjustString(stringIndex);
409 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
410 if (newIndex < 0 || newIndex > 0xffff) {
411 throw new IllegalArgumentException("type ID not in [0, 0xffff]: " + newIndex);
413 indexMap.typeIds[oldIndex] = (short) newIndex;
416 @Override void write(Integer value) {
417 idsDefsOut.writeInt(value);
422 private void mergeTypeLists() {
423 new IdMerger<TypeList>(typeListOut) {
424 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
425 return tableOfContents.typeLists;
428 @Override TypeList read(Dex.Section in, IndexMap indexMap, int index) {
429 return indexMap.adjustTypeList(in.readTypeList());
432 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
433 indexMap.putTypeListOffset(offset, typeListOut.getPosition());
436 @Override void write(TypeList value) {
437 typeListOut.writeTypeList(value);
442 private void mergeProtoIds() {
443 new IdMerger<ProtoId>(idsDefsOut) {
444 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
445 return tableOfContents.protoIds;
448 @Override ProtoId read(Dex.Section in, IndexMap indexMap, int index) {
449 return indexMap.adjust(in.readProtoId());
452 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
453 if (newIndex < 0 || newIndex > 0xffff) {
454 throw new IllegalArgumentException("proto ID not in [0, 0xffff]: " + newIndex);
456 indexMap.protoIds[oldIndex] = (short) newIndex;
459 @Override void write(ProtoId value) {
460 value.writeTo(idsDefsOut);
465 private void mergeFieldIds() {
466 new IdMerger<FieldId>(idsDefsOut) {
467 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
468 return tableOfContents.fieldIds;
471 @Override FieldId read(Dex.Section in, IndexMap indexMap, int index) {
472 return indexMap.adjust(in.readFieldId());
475 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
476 if (newIndex < 0 || newIndex > 0xffff) {
477 throw new IllegalArgumentException("field ID not in [0, 0xffff]: " + newIndex);
479 indexMap.fieldIds[oldIndex] = (short) newIndex;
482 @Override void write(FieldId value) {
483 value.writeTo(idsDefsOut);
488 private void mergeMethodIds() {
489 new IdMerger<MethodId>(idsDefsOut) {
490 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
491 return tableOfContents.methodIds;
494 @Override MethodId read(Dex.Section in, IndexMap indexMap, int index) {
495 return indexMap.adjust(in.readMethodId());
498 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
499 if (newIndex < 0 || newIndex > 0xffff) {
500 throw new IllegalArgumentException("method ID not in [0, 0xffff]: " + newIndex);
502 indexMap.methodIds[oldIndex] = (short) newIndex;
505 @Override void write(MethodId methodId) {
506 methodId.writeTo(idsDefsOut);
511 private void mergeAnnotations() {
512 new IdMerger<Annotation>(annotationOut) {
513 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
514 return tableOfContents.annotations;
517 @Override Annotation read(Dex.Section in, IndexMap indexMap, int index) {
518 return indexMap.adjust(in.readAnnotation());
521 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
522 indexMap.putAnnotationOffset(offset, annotationOut.getPosition());
525 @Override void write(Annotation value) {
526 value.writeTo(annotationOut);
531 private void mergeClassDefs() {
532 SortableType[] types = getSortedTypes();
533 contentsOut.classDefs.off = idsDefsOut.getPosition();
534 contentsOut.classDefs.size = types.length;
536 for (SortableType type : types) {
537 Dex in = type.getDex();
538 IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap;
539 transformClassDef(in, type.getClassDef(), indexMap);
544 * Returns the union of classes from both files, sorted in order such that
545 * a class is always preceded by its supertype and implemented interfaces.
547 private SortableType[] getSortedTypes() {
548 // size is pessimistic; doesn't include arrays
549 SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size];
550 readSortableTypes(sortableTypes, dexA, aIndexMap);
551 readSortableTypes(sortableTypes, dexB, bIndexMap);
554 * Populate the depths of each sortable type. This makes D iterations
555 * through all N types, where 'D' is the depth of the deepest type. For
556 * example, the deepest class in libcore is Xalan's KeyIterator, which
560 boolean allDone = true;
561 for (SortableType sortableType : sortableTypes) {
562 if (sortableType != null && !sortableType.isDepthAssigned()) {
563 allDone &= sortableType.tryAssignDepth(sortableTypes);
571 // Now that all types have depth information, the result can be sorted
572 Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER);
574 // Strip nulls from the end
575 int firstNull = Arrays.asList(sortableTypes).indexOf(null);
576 return firstNull != -1
577 ? Arrays.copyOfRange(sortableTypes, 0, firstNull)
582 * Reads just enough data on each class so that we can sort it and then find
585 private void readSortableTypes(SortableType[] sortableTypes, Dex buffer,
587 for (ClassDef classDef : buffer.classDefs()) {
588 SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef));
589 int t = sortableType.getTypeIndex();
590 if (sortableTypes[t] == null) {
591 sortableTypes[t] = sortableType;
592 } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) {
593 throw new DexException("Multiple dex files define "
594 + buffer.typeNames().get(classDef.getTypeIndex()));
600 * Copy annotation sets from each input to the output.
602 * TODO: this may write multiple copies of the same annotation set.
603 * We should shrink the output by merging rather than unioning
605 private void unionAnnotationSetsAndDirectories() {
606 transformAnnotationSets(dexA, aIndexMap);
607 transformAnnotationSets(dexB, bIndexMap);
608 transformAnnotationSetRefLists(dexA, aIndexMap);
609 transformAnnotationSetRefLists(dexB, bIndexMap);
610 transformAnnotationDirectories(dexA, aIndexMap);
611 transformAnnotationDirectories(dexB, bIndexMap);
612 transformStaticValues(dexA, aIndexMap);
613 transformStaticValues(dexB, bIndexMap);
616 private void transformAnnotationSets(Dex in, IndexMap indexMap) {
617 TableOfContents.Section section = in.getTableOfContents().annotationSets;
618 if (section.exists()) {
619 Dex.Section setIn = in.open(section.off);
620 for (int i = 0; i < section.size; i++) {
621 transformAnnotationSet(indexMap, setIn);
626 private void transformAnnotationSetRefLists(Dex in, IndexMap indexMap) {
627 TableOfContents.Section section = in.getTableOfContents().annotationSetRefLists;
628 if (section.exists()) {
629 Dex.Section setIn = in.open(section.off);
630 for (int i = 0; i < section.size; i++) {
631 transformAnnotationSetRefList(indexMap, setIn);
636 private void transformAnnotationDirectories(Dex in, IndexMap indexMap) {
637 TableOfContents.Section section = in.getTableOfContents().annotationsDirectories;
638 if (section.exists()) {
639 Dex.Section directoryIn = in.open(section.off);
640 for (int i = 0; i < section.size; i++) {
641 transformAnnotationDirectory(directoryIn, indexMap);
646 private void transformStaticValues(Dex in, IndexMap indexMap) {
647 TableOfContents.Section section = in.getTableOfContents().encodedArrays;
648 if (section.exists()) {
649 Dex.Section staticValuesIn = in.open(section.off);
650 for (int i = 0; i < section.size; i++) {
651 transformStaticValues(staticValuesIn, indexMap);
657 * Reads a class_def_item beginning at {@code in} and writes the index and
660 private void transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap) {
661 idsDefsOut.assertFourByteAligned();
662 idsDefsOut.writeInt(classDef.getTypeIndex());
663 idsDefsOut.writeInt(classDef.getAccessFlags());
664 idsDefsOut.writeInt(classDef.getSupertypeIndex());
665 idsDefsOut.writeInt(classDef.getInterfacesOffset());
667 int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex());
668 idsDefsOut.writeInt(sourceFileIndex);
670 int annotationsOff = classDef.getAnnotationsOffset();
671 idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff));
673 int classDataOff = classDef.getClassDataOffset();
674 if (classDataOff == 0) {
675 idsDefsOut.writeInt(0);
677 idsDefsOut.writeInt(classDataOut.getPosition());
678 ClassData classData = in.readClassData(classDef);
679 transformClassData(in, classData, indexMap);
682 int staticValuesOff = classDef.getStaticValuesOffset();
683 idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff));
687 * Transform all annotations on a class.
689 private void transformAnnotationDirectory(
690 Dex.Section directoryIn, IndexMap indexMap) {
691 contentsOut.annotationsDirectories.size++;
692 annotationsDirectoryOut.assertFourByteAligned();
693 indexMap.putAnnotationDirectoryOffset(
694 directoryIn.getPosition(), annotationsDirectoryOut.getPosition());
696 int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt());
697 annotationsDirectoryOut.writeInt(classAnnotationsOffset);
699 int fieldsSize = directoryIn.readInt();
700 annotationsDirectoryOut.writeInt(fieldsSize);
702 int methodsSize = directoryIn.readInt();
703 annotationsDirectoryOut.writeInt(methodsSize);
705 int parameterListSize = directoryIn.readInt();
706 annotationsDirectoryOut.writeInt(parameterListSize);
708 for (int i = 0; i < fieldsSize; i++) {
710 annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt()));
712 // annotations offset
713 annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
716 for (int i = 0; i < methodsSize; i++) {
718 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
720 // annotation set offset
721 annotationsDirectoryOut.writeInt(
722 indexMap.adjustAnnotationSet(directoryIn.readInt()));
725 for (int i = 0; i < parameterListSize; i++) {
727 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
729 // annotations offset
730 annotationsDirectoryOut.writeInt(
731 indexMap.adjustAnnotationSetRefList(directoryIn.readInt()));
736 * Transform all annotations on a single type, member or parameter.
738 private void transformAnnotationSet(IndexMap indexMap, Dex.Section setIn) {
739 contentsOut.annotationSets.size++;
740 annotationSetOut.assertFourByteAligned();
741 indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition());
743 int size = setIn.readInt();
744 annotationSetOut.writeInt(size);
746 for (int j = 0; j < size; j++) {
747 annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt()));
752 * Transform all annotation set ref lists.
754 private void transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn) {
755 contentsOut.annotationSetRefLists.size++;
756 annotationSetRefListOut.assertFourByteAligned();
757 indexMap.putAnnotationSetRefListOffset(
758 refListIn.getPosition(), annotationSetRefListOut.getPosition());
760 int parameterCount = refListIn.readInt();
761 annotationSetRefListOut.writeInt(parameterCount);
762 for (int p = 0; p < parameterCount; p++) {
763 annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt()));
767 private void transformClassData(Dex in, ClassData classData, IndexMap indexMap) {
768 contentsOut.classDatas.size++;
770 ClassData.Field[] staticFields = classData.getStaticFields();
771 ClassData.Field[] instanceFields = classData.getInstanceFields();
772 ClassData.Method[] directMethods = classData.getDirectMethods();
773 ClassData.Method[] virtualMethods = classData.getVirtualMethods();
775 classDataOut.writeUleb128(staticFields.length);
776 classDataOut.writeUleb128(instanceFields.length);
777 classDataOut.writeUleb128(directMethods.length);
778 classDataOut.writeUleb128(virtualMethods.length);
780 transformFields(indexMap, staticFields);
781 transformFields(indexMap, instanceFields);
782 transformMethods(in, indexMap, directMethods);
783 transformMethods(in, indexMap, virtualMethods);
786 private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
787 int lastOutFieldIndex = 0;
788 for (ClassData.Field field : fields) {
789 int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
790 classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex);
791 lastOutFieldIndex = outFieldIndex;
792 classDataOut.writeUleb128(field.getAccessFlags());
796 private void transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods) {
797 int lastOutMethodIndex = 0;
798 for (ClassData.Method method : methods) {
799 int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
800 classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex);
801 lastOutMethodIndex = outMethodIndex;
803 classDataOut.writeUleb128(method.getAccessFlags());
805 if (method.getCodeOffset() == 0) {
806 classDataOut.writeUleb128(0);
808 codeOut.alignToFourBytes();
809 classDataOut.writeUleb128(codeOut.getPosition());
810 transformCode(in, in.readCode(method), indexMap);
815 private void transformCode(Dex in, Code code, IndexMap indexMap) {
816 contentsOut.codes.size++;
817 codeOut.assertFourByteAligned();
819 codeOut.writeUnsignedShort(code.getRegistersSize());
820 codeOut.writeUnsignedShort(code.getInsSize());
821 codeOut.writeUnsignedShort(code.getOutsSize());
823 Code.Try[] tries = code.getTries();
824 Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
825 codeOut.writeUnsignedShort(tries.length);
827 int debugInfoOffset = code.getDebugInfoOffset();
828 if (debugInfoOffset != 0) {
829 codeOut.writeInt(debugInfoOut.getPosition());
830 transformDebugInfoItem(in.open(debugInfoOffset), indexMap);
835 short[] instructions = code.getInstructions();
836 InstructionTransformer transformer = (in == dexA)
837 ? aInstructionTransformer
838 : bInstructionTransformer;
839 short[] newInstructions = transformer.transform(instructions);
840 codeOut.writeInt(newInstructions.length);
841 codeOut.write(newInstructions);
843 if (tries.length > 0) {
844 if (newInstructions.length % 2 == 1) {
845 codeOut.writeShort((short) 0); // padding
849 * We can't write the tries until we've written the catch handlers.
850 * Unfortunately they're in the opposite order in the dex file so we
851 * need to transform them out-of-order.
853 Dex.Section triesSection = dexOut.open(codeOut.getPosition());
854 codeOut.skip(tries.length * SizeOf.TRY_ITEM);
855 int[] offsets = transformCatchHandlers(indexMap, catchHandlers);
856 transformTries(triesSection, tries, offsets);
861 * Writes the catch handlers to {@code codeOut} and returns their indices.
863 private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) {
864 int baseOffset = codeOut.getPosition();
865 codeOut.writeUleb128(catchHandlers.length);
866 int[] offsets = new int[catchHandlers.length];
867 for (int i = 0; i < catchHandlers.length; i++) {
868 offsets[i] = codeOut.getPosition() - baseOffset;
869 transformEncodedCatchHandler(catchHandlers[i], indexMap);
874 private void transformTries(Dex.Section out, Code.Try[] tries,
875 int[] catchHandlerOffsets) {
876 for (Code.Try tryItem : tries) {
877 out.writeInt(tryItem.getStartAddress());
878 out.writeUnsignedShort(tryItem.getInstructionCount());
879 out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]);
883 private static final byte DBG_END_SEQUENCE = 0x00;
884 private static final byte DBG_ADVANCE_PC = 0x01;
885 private static final byte DBG_ADVANCE_LINE = 0x02;
886 private static final byte DBG_START_LOCAL = 0x03;
887 private static final byte DBG_START_LOCAL_EXTENDED = 0x04;
888 private static final byte DBG_END_LOCAL = 0x05;
889 private static final byte DBG_RESTART_LOCAL = 0x06;
890 private static final byte DBG_SET_PROLOGUE_END = 0x07;
891 private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08;
892 private static final byte DBG_SET_FILE = 0x09;
894 private void transformDebugInfoItem(Dex.Section in, IndexMap indexMap) {
895 contentsOut.debugInfos.size++;
896 int lineStart = in.readUleb128();
897 debugInfoOut.writeUleb128(lineStart);
899 int parametersSize = in.readUleb128();
900 debugInfoOut.writeUleb128(parametersSize);
902 for (int p = 0; p < parametersSize; p++) {
903 int parameterName = in.readUleb128p1();
904 debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName));
907 int addrDiff; // uleb128 address delta.
908 int lineDiff; // sleb128 line delta.
909 int registerNum; // uleb128 register number.
910 int nameIndex; // uleb128p1 string index. Needs indexMap adjustment.
911 int typeIndex; // uleb128p1 type index. Needs indexMap adjustment.
912 int sigIndex; // uleb128p1 string index. Needs indexMap adjustment.
915 int opcode = in.readByte();
916 debugInfoOut.writeByte(opcode);
919 case DBG_END_SEQUENCE:
923 addrDiff = in.readUleb128();
924 debugInfoOut.writeUleb128(addrDiff);
927 case DBG_ADVANCE_LINE:
928 lineDiff = in.readSleb128();
929 debugInfoOut.writeSleb128(lineDiff);
932 case DBG_START_LOCAL:
933 case DBG_START_LOCAL_EXTENDED:
934 registerNum = in.readUleb128();
935 debugInfoOut.writeUleb128(registerNum);
936 nameIndex = in.readUleb128p1();
937 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
938 typeIndex = in.readUleb128p1();
939 debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex));
940 if (opcode == DBG_START_LOCAL_EXTENDED) {
941 sigIndex = in.readUleb128p1();
942 debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex));
947 case DBG_RESTART_LOCAL:
948 registerNum = in.readUleb128();
949 debugInfoOut.writeUleb128(registerNum);
953 nameIndex = in.readUleb128p1();
954 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
957 case DBG_SET_PROLOGUE_END:
958 case DBG_SET_EPILOGUE_BEGIN:
965 private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
966 int catchAllAddress = catchHandler.getCatchAllAddress();
967 int[] typeIndexes = catchHandler.getTypeIndexes();
968 int[] addresses = catchHandler.getAddresses();
970 if (catchAllAddress != -1) {
971 codeOut.writeSleb128(-typeIndexes.length);
973 codeOut.writeSleb128(typeIndexes.length);
976 for (int i = 0; i < typeIndexes.length; i++) {
977 codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i]));
978 codeOut.writeUleb128(addresses[i]);
981 if (catchAllAddress != -1) {
982 codeOut.writeUleb128(catchAllAddress);
986 private void transformStaticValues(Dex.Section in, IndexMap indexMap) {
987 contentsOut.encodedArrays.size++;
988 indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition());
989 indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut);
993 * Byte counts for the sections written when creating a dex. Target sizes
994 * are defined in one of two ways:
996 * <li>By pessimistically guessing how large the union of dex files will be.
997 * We're pessimistic because we can't predict the amount of duplication
998 * between dex files, nor can we predict the length of ULEB-encoded
999 * offsets or indices.
1000 * <li>By exactly measuring an existing dex.
1003 private static class WriterSizes {
1004 private int header = SizeOf.HEADER_ITEM;
1005 private int idsDefs;
1006 private int mapList;
1007 private int typeList;
1008 private int classData;
1010 private int stringData;
1011 private int debugInfo;
1012 private int encodedArray;
1013 private int annotationsDirectory;
1014 private int annotationsSet;
1015 private int annotationsSetRefList;
1016 private int annotation;
1019 * Compute sizes for merging a and b.
1021 public WriterSizes(Dex a, Dex b) {
1022 plus(a.getTableOfContents(), false);
1023 plus(b.getTableOfContents(), false);
1027 public WriterSizes(DexMerger dexMerger) {
1028 header = dexMerger.headerOut.used();
1029 idsDefs = dexMerger.idsDefsOut.used();
1030 mapList = dexMerger.mapListOut.used();
1031 typeList = dexMerger.typeListOut.used();
1032 classData = dexMerger.classDataOut.used();
1033 code = dexMerger.codeOut.used();
1034 stringData = dexMerger.stringDataOut.used();
1035 debugInfo = dexMerger.debugInfoOut.used();
1036 encodedArray = dexMerger.encodedArrayOut.used();
1037 annotationsDirectory = dexMerger.annotationsDirectoryOut.used();
1038 annotationsSet = dexMerger.annotationSetOut.used();
1039 annotationsSetRefList = dexMerger.annotationSetRefListOut.used();
1040 annotation = dexMerger.annotationOut.used();
1043 private void plus(TableOfContents contents, boolean exact) {
1044 idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM
1045 + contents.typeIds.size * SizeOf.TYPE_ID_ITEM
1046 + contents.protoIds.size * SizeOf.PROTO_ID_ITEM
1047 + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM
1048 + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM
1049 + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM;
1050 mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM);
1051 typeList += contents.typeLists.byteCount;
1052 stringData += contents.stringDatas.byteCount;
1053 annotationsDirectory += contents.annotationsDirectories.byteCount;
1054 annotationsSet += contents.annotationSets.byteCount;
1055 annotationsSetRefList += contents.annotationSetRefLists.byteCount;
1058 code += contents.codes.byteCount;
1059 classData += contents.classDatas.byteCount;
1060 encodedArray += contents.encodedArrays.byteCount;
1061 annotation += contents.annotations.byteCount;
1062 debugInfo += contents.debugInfos.byteCount;
1064 // at most 1/4 of the bytes in a code section are uleb/sleb
1065 code += (int) Math.ceil(contents.codes.byteCount * 1.25);
1066 // at most 1/3 of the bytes in a class data section are uleb/sleb
1067 classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34);
1068 // all of the bytes in an encoding arrays section may be uleb/sleb
1069 encodedArray += contents.encodedArrays.byteCount * 2;
1070 // all of the bytes in an annotations section may be uleb/sleb
1071 annotation += (int) Math.ceil(contents.annotations.byteCount * 2);
1072 // all of the bytes in a debug info section may be uleb/sleb
1073 debugInfo += contents.debugInfos.byteCount * 2;
1077 private void fourByteAlign() {
1078 header = fourByteAlign(header);
1079 idsDefs = fourByteAlign(idsDefs);
1080 mapList = fourByteAlign(mapList);
1081 typeList = fourByteAlign(typeList);
1082 classData = fourByteAlign(classData);
1083 code = fourByteAlign(code);
1084 stringData = fourByteAlign(stringData);
1085 debugInfo = fourByteAlign(debugInfo);
1086 encodedArray = fourByteAlign(encodedArray);
1087 annotationsDirectory = fourByteAlign(annotationsDirectory);
1088 annotationsSet = fourByteAlign(annotationsSet);
1089 annotationsSetRefList = fourByteAlign(annotationsSetRefList);
1090 annotation = fourByteAlign(annotation);
1093 private static int fourByteAlign(int position) {
1094 return (position + 3) & ~3;
1098 return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo
1099 + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList
1104 public static void main(String[] args) throws IOException {
1105 if (args.length != 3) {
1110 Dex dexA = new Dex(new File(args[1]));
1111 Dex dexB = new Dex(new File(args[2]));
1112 Dex merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
1113 merged.writeTo(new File(args[0]));
1116 private static void printUsage() {
1117 System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex>");
1118 System.out.println();
1119 System.out.println("If both a and b define the same classes, a's copy will be used.");