1 /*
2  * Copyright (C) 2011 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 package com.android.dx.merge;
18 
19 import com.android.dex.Annotation;
20 import com.android.dex.CallSiteId;
21 import com.android.dex.ClassData;
22 import com.android.dex.ClassDef;
23 import com.android.dex.Code;
24 import com.android.dex.Dex;
25 import com.android.dex.DexException;
26 import com.android.dex.DexIndexOverflowException;
27 import com.android.dex.FieldId;
28 import com.android.dex.MethodHandle;
29 import com.android.dex.MethodId;
30 import com.android.dex.ProtoId;
31 import com.android.dex.SizeOf;
32 import com.android.dex.TableOfContents;
33 import com.android.dex.TypeList;
34 import com.android.dx.command.dexer.DxContext;
35 import java.io.File;
36 import java.io.IOException;
37 import java.util.*;
38 
39 /**
40  * Combine two dex files into one.
41  */
42 public final class DexMerger {
43     private final Dex[] dexes;
44     private final IndexMap[] indexMaps;
45 
46     private final CollisionPolicy collisionPolicy;
47     private final DxContext context;
48     private final WriterSizes writerSizes;
49 
50     private final Dex dexOut;
51 
52     private final Dex.Section headerOut;
53 
54     /** All IDs and definitions sections */
55     private final Dex.Section idsDefsOut;
56 
57     private final Dex.Section mapListOut;
58 
59     private final Dex.Section typeListOut;
60 
61     private final Dex.Section classDataOut;
62 
63     private final Dex.Section codeOut;
64 
65     private final Dex.Section stringDataOut;
66 
67     private final Dex.Section debugInfoOut;
68 
69     private final Dex.Section encodedArrayOut;
70 
71     /** annotations directory on a type */
72     private final Dex.Section annotationsDirectoryOut;
73 
74     /** sets of annotations on a member, parameter or type */
75     private final Dex.Section annotationSetOut;
76 
77     /** parameter lists */
78     private final Dex.Section annotationSetRefListOut;
79 
80     /** individual annotations, each containing zero or more fields */
81     private final Dex.Section annotationOut;
82 
83     private final TableOfContents contentsOut;
84 
85     private final InstructionTransformer instructionTransformer;
86 
87     /** minimum number of wasted bytes before it's worthwhile to compact the result */
88     private int compactWasteThreshold = 1024 * 1024; // 1MiB
89 
DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context)90     public DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context)
91             throws IOException {
92         this(dexes, collisionPolicy, context, new WriterSizes(dexes));
93     }
94 
DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context, WriterSizes writerSizes)95     private DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context,
96             WriterSizes writerSizes) throws IOException {
97         this.dexes = dexes;
98         this.collisionPolicy = collisionPolicy;
99         this.context = context;
100         this.writerSizes = writerSizes;
101 
102         dexOut = new Dex(writerSizes.size());
103 
104         indexMaps = new IndexMap[dexes.length];
105         for (int i = 0; i < dexes.length; i++) {
106             indexMaps[i] = new IndexMap(dexOut, dexes[i].getTableOfContents());
107         }
108         instructionTransformer = new InstructionTransformer();
109 
110         headerOut = dexOut.appendSection(writerSizes.header, "header");
111         idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs");
112 
113         contentsOut = dexOut.getTableOfContents();
114         contentsOut.dataOff = dexOut.getNextSectionStart();
115 
116         contentsOut.mapList.off = dexOut.getNextSectionStart();
117         contentsOut.mapList.size = 1;
118         mapListOut = dexOut.appendSection(writerSizes.mapList, "map list");
119 
120         contentsOut.typeLists.off = dexOut.getNextSectionStart();
121         typeListOut = dexOut.appendSection(writerSizes.typeList, "type list");
122 
123         contentsOut.annotationSetRefLists.off = dexOut.getNextSectionStart();
124         annotationSetRefListOut = dexOut.appendSection(
125                 writerSizes.annotationsSetRefList, "annotation set ref list");
126 
127         contentsOut.annotationSets.off = dexOut.getNextSectionStart();
128         annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets");
129 
130         contentsOut.classDatas.off = dexOut.getNextSectionStart();
131         classDataOut = dexOut.appendSection(writerSizes.classData, "class data");
132 
133         contentsOut.codes.off = dexOut.getNextSectionStart();
134         codeOut = dexOut.appendSection(writerSizes.code, "code");
135 
136         contentsOut.stringDatas.off = dexOut.getNextSectionStart();
137         stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data");
138 
139         contentsOut.debugInfos.off = dexOut.getNextSectionStart();
140         debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info");
141 
142         contentsOut.annotations.off = dexOut.getNextSectionStart();
143         annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation");
144 
145         contentsOut.encodedArrays.off = dexOut.getNextSectionStart();
146         encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array");
147 
148         contentsOut.annotationsDirectories.off = dexOut.getNextSectionStart();
149         annotationsDirectoryOut = dexOut.appendSection(
150                 writerSizes.annotationsDirectory, "annotations directory");
151 
152         contentsOut.dataSize = dexOut.getNextSectionStart() - contentsOut.dataOff;
153     }
154 
setCompactWasteThreshold(int compactWasteThreshold)155     public void setCompactWasteThreshold(int compactWasteThreshold) {
156         this.compactWasteThreshold = compactWasteThreshold;
157     }
158 
mergeDexes()159     private Dex mergeDexes() throws IOException {
160         mergeStringIds();
161         mergeTypeIds();
162         mergeTypeLists();
163         mergeProtoIds();
164         mergeFieldIds();
165         mergeMethodIds();
166         mergeMethodHandles();
167         mergeAnnotations();
168         unionAnnotationSetsAndDirectories();
169         mergeCallSiteIds();
170         mergeClassDefs();
171 
172         // computeSizesFromOffsets expects sections sorted by offset, so make it so
173         Arrays.sort(contentsOut.sections);
174 
175         // write the header
176         contentsOut.header.off = 0;
177         contentsOut.header.size = 1;
178         contentsOut.fileSize = dexOut.getLength();
179         contentsOut.computeSizesFromOffsets();
180         contentsOut.writeHeader(headerOut, mergeApiLevels());
181         contentsOut.writeMap(mapListOut);
182 
183         // generate and write the hashes
184         dexOut.writeHashes();
185 
186         return dexOut;
187     }
188 
merge()189     public Dex merge() throws IOException {
190         if (dexes.length == 1) {
191             return dexes[0];
192         } else if (dexes.length == 0) {
193             return null;
194         }
195 
196         long start = System.nanoTime();
197         Dex result = mergeDexes();
198 
199         /*
200          * We use pessimistic sizes when merging dex files. If those sizes
201          * result in too many bytes wasted, compact the result. To compact,
202          * simply merge the result with itself.
203          */
204         WriterSizes compactedSizes = new WriterSizes(this);
205         int wastedByteCount = writerSizes.size() - compactedSizes.size();
206         if (wastedByteCount >  + compactWasteThreshold) {
207             DexMerger compacter = new DexMerger(
208                     new Dex[] {dexOut, new Dex(0)}, CollisionPolicy.FAIL, context, compactedSizes);
209             result = compacter.mergeDexes();
210             context.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n",
211                     dexOut.getLength() / 1024f,
212                     result.getLength() / 1024f,
213                     wastedByteCount / 1024f);
214         }
215 
216         long elapsed = System.nanoTime() - start;
217         for (int i = 0; i < dexes.length; i++) {
218             context.out.printf("Merged dex #%d (%d defs/%.1fKiB)%n",
219                 i + 1,
220                 dexes[i].getTableOfContents().classDefs.size,
221                 dexes[i].getLength() / 1024f);
222         }
223         context.out.printf("Result is %d defs/%.1fKiB. Took %.1fs%n",
224                 result.getTableOfContents().classDefs.size,
225                 result.getLength() / 1024f,
226                 elapsed / 1000000000f);
227 
228         return result;
229     }
230 
231     /**
232      * Reads an IDs section of two dex files and writes an IDs section of a
233      * merged dex file. Populates maps from old to new indices in the process.
234      */
235     abstract class IdMerger<T extends Comparable<T>> {
236         private final Dex.Section out;
237 
IdMerger(Dex.Section out)238         protected IdMerger(Dex.Section out) {
239             this.out = out;
240         }
241 
242         /**
243          * Merges already-sorted sections, reading one value from each dex into memory
244          * at a time.
245          */
mergeSorted()246         public final void mergeSorted() {
247             TableOfContents.Section[] sections = new TableOfContents.Section[dexes.length];
248             Dex.Section[] dexSections = new Dex.Section[dexes.length];
249             int[] offsets = new int[dexes.length];
250             int[] indexes = new int[dexes.length];
251 
252             // values contains one value from each dex, sorted for fast retrieval of
253             // the smallest value. The list associated with a value has the indexes
254             // of the dexes that had that value.
255             TreeMap<T, List<Integer>> values = new TreeMap<T, List<Integer>>();
256 
257             for (int i = 0; i < dexes.length; i++) {
258                 sections[i] = getSection(dexes[i].getTableOfContents());
259                 dexSections[i] = sections[i].exists() ? dexes[i].open(sections[i].off) : null;
260                 // Fill in values with the first value of each dex.
261                 offsets[i] = readIntoMap(
262                         dexSections[i], sections[i], indexMaps[i], indexes[i], values, i);
263             }
264             if (values.isEmpty()) {
265                 getSection(contentsOut).off = 0;
266                 getSection(contentsOut).size = 0;
267                 return;
268             }
269             getSection(contentsOut).off = out.getPosition();
270 
271             int outCount = 0;
272             while (!values.isEmpty()) {
273                 Map.Entry<T, List<Integer>> first = values.pollFirstEntry();
274                 for (Integer dex : first.getValue()) {
275                     updateIndex(offsets[dex], indexMaps[dex], indexes[dex]++, outCount);
276                     // Fetch the next value of the dexes we just polled out
277                     offsets[dex] = readIntoMap(dexSections[dex], sections[dex],
278                             indexMaps[dex], indexes[dex], values, dex);
279                 }
280                 write(first.getKey());
281                 outCount++;
282             }
283 
284             getSection(contentsOut).size = outCount;
285         }
286 
readIntoMap(Dex.Section in, TableOfContents.Section section, IndexMap indexMap, int index, TreeMap<T, List<Integer>> values, int dex)287         private int readIntoMap(Dex.Section in, TableOfContents.Section section, IndexMap indexMap,
288                                 int index, TreeMap<T, List<Integer>> values, int dex) {
289             int offset = in != null ? in.getPosition() : -1;
290             if (index < section.size) {
291                 T v = read(in, indexMap, index);
292                 List<Integer> l = values.get(v);
293                 if (l == null) {
294                     l = new ArrayList<Integer>();
295                     values.put(v, l);
296                 }
297                 l.add(dex);
298             }
299             return offset;
300         }
301 
302         /**
303          * Merges unsorted sections by reading them completely into memory and
304          * sorting in memory.
305          */
mergeUnsorted()306         public final void mergeUnsorted() {
307             getSection(contentsOut).off = out.getPosition();
308 
309             List<UnsortedValue> all = new ArrayList<UnsortedValue>();
310             for (int i = 0; i < dexes.length; i++) {
311                 all.addAll(readUnsortedValues(dexes[i], indexMaps[i]));
312             }
313             if (all.isEmpty()) {
314                 getSection(contentsOut).off = 0;
315                 getSection(contentsOut).size = 0;
316                 return;
317             }
318             Collections.sort(all);
319 
320             int outCount = 0;
321             for (int i = 0; i < all.size(); ) {
322                 UnsortedValue e1 = all.get(i++);
323                 updateIndex(e1.offset, e1.indexMap, e1.index, outCount - 1);
324 
325                 while (i < all.size() && e1.compareTo(all.get(i)) == 0) {
326                     UnsortedValue e2 = all.get(i++);
327                     updateIndex(e2.offset, e2.indexMap, e2.index, outCount - 1);
328                 }
329 
330                 write(e1.value);
331                 outCount++;
332             }
333 
334             getSection(contentsOut).size = outCount;
335         }
336 
readUnsortedValues(Dex source, IndexMap indexMap)337         private List<UnsortedValue> readUnsortedValues(Dex source, IndexMap indexMap) {
338             TableOfContents.Section section = getSection(source.getTableOfContents());
339             if (!section.exists()) {
340                 return Collections.emptyList();
341             }
342 
343             List<UnsortedValue> result = new ArrayList<UnsortedValue>();
344             Dex.Section in = source.open(section.off);
345             for (int i = 0; i < section.size; i++) {
346                 int offset = in.getPosition();
347                 T value = read(in, indexMap, 0);
348                 result.add(new UnsortedValue(source, indexMap, value, i, offset));
349             }
350             return result;
351         }
352 
getSection(TableOfContents tableOfContents)353         abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
read(Dex.Section in, IndexMap indexMap, int index)354         abstract T read(Dex.Section in, IndexMap indexMap, int index);
updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex)355         abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex);
write(T value)356         abstract void write(T value);
357 
358         class UnsortedValue implements Comparable<UnsortedValue> {
359             final Dex source;
360             final IndexMap indexMap;
361             final T value;
362             final int index;
363             final int offset;
364 
UnsortedValue(Dex source, IndexMap indexMap, T value, int index, int offset)365             UnsortedValue(Dex source, IndexMap indexMap, T value, int index, int offset) {
366                 this.source = source;
367                 this.indexMap = indexMap;
368                 this.value = value;
369                 this.index = index;
370                 this.offset = offset;
371             }
372 
373             @Override
compareTo(UnsortedValue unsortedValue)374             public int compareTo(UnsortedValue unsortedValue) {
375                 return value.compareTo(unsortedValue.value);
376             }
377         }
378     }
379 
mergeApiLevels()380     private int mergeApiLevels() {
381         int maxApi = -1;
382         for (int i = 0; i < dexes.length; i++) {
383             int dexMinApi = dexes[i].getTableOfContents().apiLevel;
384             if (maxApi < dexMinApi) {
385                 maxApi = dexMinApi;
386             }
387         }
388         return maxApi;
389     }
390 
mergeStringIds()391     private void mergeStringIds() {
392         new IdMerger<String>(idsDefsOut) {
393             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
394                 return tableOfContents.stringIds;
395             }
396 
397             @Override String read(Dex.Section in, IndexMap indexMap, int index) {
398                 return in.readString();
399             }
400 
401             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
402                 indexMap.stringIds[oldIndex] = newIndex;
403             }
404 
405             @Override void write(String value) {
406                 contentsOut.stringDatas.size++;
407                 idsDefsOut.writeInt(stringDataOut.getPosition());
408                 stringDataOut.writeStringData(value);
409             }
410         }.mergeSorted();
411     }
412 
mergeTypeIds()413     private void mergeTypeIds() {
414         new IdMerger<Integer>(idsDefsOut) {
415             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
416                 return tableOfContents.typeIds;
417             }
418 
419             @Override Integer read(Dex.Section in, IndexMap indexMap, int index) {
420                 int stringIndex = in.readInt();
421                 return indexMap.adjustString(stringIndex);
422             }
423 
424             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
425                 if (newIndex < 0 || newIndex > 0xffff) {
426                     throw new DexIndexOverflowException("type ID not in [0, 0xffff]: " + newIndex);
427                 }
428                 indexMap.typeIds[oldIndex] = (short) newIndex;
429             }
430 
431             @Override void write(Integer value) {
432                 idsDefsOut.writeInt(value);
433             }
434         }.mergeSorted();
435     }
436 
mergeTypeLists()437     private void mergeTypeLists() {
438         new IdMerger<TypeList>(typeListOut) {
439             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
440                 return tableOfContents.typeLists;
441             }
442 
443             @Override TypeList read(Dex.Section in, IndexMap indexMap, int index) {
444                 return indexMap.adjustTypeList(in.readTypeList());
445             }
446 
447             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
448                 indexMap.putTypeListOffset(offset, typeListOut.getPosition());
449             }
450 
451             @Override void write(TypeList value) {
452                 typeListOut.writeTypeList(value);
453             }
454         }.mergeUnsorted();
455     }
456 
mergeProtoIds()457     private void mergeProtoIds() {
458         new IdMerger<ProtoId>(idsDefsOut) {
459             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
460                 return tableOfContents.protoIds;
461             }
462 
463             @Override ProtoId read(Dex.Section in, IndexMap indexMap, int index) {
464                 return indexMap.adjust(in.readProtoId());
465             }
466 
467             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
468                 if (newIndex < 0 || newIndex > 0xffff) {
469                     throw new DexIndexOverflowException("proto ID not in [0, 0xffff]: " + newIndex);
470                 }
471                 indexMap.protoIds[oldIndex] = (short) newIndex;
472             }
473 
474             @Override
475             void write(ProtoId value) {
476                 value.writeTo(idsDefsOut);
477             }
478         }.mergeSorted();
479     }
480 
mergeCallSiteIds()481     private void mergeCallSiteIds() {
482         new IdMerger<CallSiteId>(idsDefsOut) {
483             @Override
484             TableOfContents.Section getSection(TableOfContents tableOfContents) {
485                 return tableOfContents.callSiteIds;
486             }
487 
488             @Override
489             CallSiteId read(Dex.Section in, IndexMap indexMap, int index) {
490                 return indexMap.adjust(in.readCallSiteId());
491             }
492 
493             @Override
494             void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
495                 indexMap.callSiteIds[oldIndex] = newIndex;
496             }
497 
498             @Override
499             void write(CallSiteId value) {
500                 value.writeTo(idsDefsOut);
501             }
502         }.mergeSorted();
503     }
504 
mergeMethodHandles()505     private void mergeMethodHandles() {
506         new IdMerger<MethodHandle>(idsDefsOut) {
507             @Override
508             TableOfContents.Section getSection(TableOfContents tableOfContents) {
509                 return tableOfContents.methodHandles;
510             }
511 
512             @Override
513             MethodHandle read(Dex.Section in, IndexMap indexMap, int index) {
514                 return indexMap.adjust(in.readMethodHandle());
515             }
516 
517             @Override
518             void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
519                 indexMap.methodHandleIds.put(oldIndex, indexMap.methodHandleIds.size());
520             }
521 
522             @Override
523             void write(MethodHandle value) {
524                 value.writeTo(idsDefsOut);
525             }
526         }.mergeUnsorted();
527     }
528 
mergeFieldIds()529     private void mergeFieldIds() {
530         new IdMerger<FieldId>(idsDefsOut) {
531             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
532                 return tableOfContents.fieldIds;
533             }
534 
535             @Override FieldId read(Dex.Section in, IndexMap indexMap, int index) {
536                 return indexMap.adjust(in.readFieldId());
537             }
538 
539             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
540                 if (newIndex < 0 || newIndex > 0xffff) {
541                     throw new DexIndexOverflowException("field ID not in [0, 0xffff]: " + newIndex);
542                 }
543                 indexMap.fieldIds[oldIndex] = (short) newIndex;
544             }
545 
546             @Override void write(FieldId value) {
547                 value.writeTo(idsDefsOut);
548             }
549         }.mergeSorted();
550     }
551 
mergeMethodIds()552     private void mergeMethodIds() {
553         new IdMerger<MethodId>(idsDefsOut) {
554             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
555                 return tableOfContents.methodIds;
556             }
557 
558             @Override MethodId read(Dex.Section in, IndexMap indexMap, int index) {
559                 return indexMap.adjust(in.readMethodId());
560             }
561 
562             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
563                 if (newIndex < 0 || newIndex > 0xffff) {
564                     throw new DexIndexOverflowException(
565                         "method ID not in [0, 0xffff]: " + newIndex);
566                 }
567                 indexMap.methodIds[oldIndex] = (short) newIndex;
568             }
569 
570             @Override void write(MethodId methodId) {
571                 methodId.writeTo(idsDefsOut);
572             }
573         }.mergeSorted();
574     }
575 
mergeAnnotations()576     private void mergeAnnotations() {
577         new IdMerger<Annotation>(annotationOut) {
578             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
579                 return tableOfContents.annotations;
580             }
581 
582             @Override Annotation read(Dex.Section in, IndexMap indexMap, int index) {
583                 return indexMap.adjust(in.readAnnotation());
584             }
585 
586             @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
587                 indexMap.putAnnotationOffset(offset, annotationOut.getPosition());
588             }
589 
590             @Override void write(Annotation value) {
591                 value.writeTo(annotationOut);
592             }
593         }.mergeUnsorted();
594     }
595 
mergeClassDefs()596     private void mergeClassDefs() {
597         SortableType[] types = getSortedTypes();
598         contentsOut.classDefs.off = idsDefsOut.getPosition();
599         contentsOut.classDefs.size = types.length;
600 
601         for (SortableType type : types) {
602             Dex in = type.getDex();
603             transformClassDef(in, type.getClassDef(), type.getIndexMap());
604         }
605     }
606 
607     /**
608      * Returns the union of classes from both files, sorted in order such that
609      * a class is always preceded by its supertype and implemented interfaces.
610      */
getSortedTypes()611     private SortableType[] getSortedTypes() {
612         // size is pessimistic; doesn't include arrays
613         SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size];
614         for (int i = 0; i < dexes.length; i++) {
615             readSortableTypes(sortableTypes, dexes[i], indexMaps[i]);
616         }
617 
618         /*
619          * Populate the depths of each sortable type. This makes D iterations
620          * through all N types, where 'D' is the depth of the deepest type. For
621          * example, the deepest class in libcore is Xalan's KeyIterator, which
622          * is 11 types deep.
623          */
624         while (true) {
625             boolean allDone = true;
626             for (SortableType sortableType : sortableTypes) {
627                 if (sortableType != null && !sortableType.isDepthAssigned()) {
628                     allDone &= sortableType.tryAssignDepth(sortableTypes);
629                 }
630             }
631             if (allDone) {
632                 break;
633             }
634         }
635 
636         // Now that all types have depth information, the result can be sorted
637         Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER);
638 
639         // Strip nulls from the end
640         int firstNull = Arrays.asList(sortableTypes).indexOf(null);
641         return firstNull != -1
642                 ? Arrays.copyOfRange(sortableTypes, 0, firstNull)
643                 : sortableTypes;
644     }
645 
646     /**
647      * Reads just enough data on each class so that we can sort it and then find
648      * it later.
649      */
readSortableTypes(SortableType[] sortableTypes, Dex buffer, IndexMap indexMap)650     private void readSortableTypes(SortableType[] sortableTypes, Dex buffer,
651             IndexMap indexMap) {
652         for (ClassDef classDef : buffer.classDefs()) {
653             SortableType sortableType = indexMap.adjust(
654                     new SortableType(buffer, indexMap, classDef));
655             int t = sortableType.getTypeIndex();
656             if (sortableTypes[t] == null) {
657                 sortableTypes[t] = sortableType;
658             } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) {
659                 throw new DexException("Multiple dex files define "
660                         + buffer.typeNames().get(classDef.getTypeIndex()));
661             }
662         }
663     }
664 
665     /**
666      * Copy annotation sets from each input to the output.
667      *
668      * TODO: this may write multiple copies of the same annotation set.
669      * We should shrink the output by merging rather than unioning
670      */
unionAnnotationSetsAndDirectories()671     private void unionAnnotationSetsAndDirectories() {
672         for (int i = 0; i < dexes.length; i++) {
673             transformAnnotationSets(dexes[i], indexMaps[i]);
674         }
675         for (int i = 0; i < dexes.length; i++) {
676             transformAnnotationSetRefLists(dexes[i], indexMaps[i]);
677         }
678         for (int i = 0; i < dexes.length; i++) {
679             transformAnnotationDirectories(dexes[i], indexMaps[i]);
680         }
681         for (int i = 0; i < dexes.length; i++) {
682             transformStaticValues(dexes[i], indexMaps[i]);
683         }
684     }
685 
transformAnnotationSets(Dex in, IndexMap indexMap)686     private void transformAnnotationSets(Dex in, IndexMap indexMap) {
687         TableOfContents.Section section = in.getTableOfContents().annotationSets;
688         if (section.exists()) {
689             Dex.Section setIn = in.open(section.off);
690             for (int i = 0; i < section.size; i++) {
691                 transformAnnotationSet(indexMap, setIn);
692             }
693         }
694     }
695 
transformAnnotationSetRefLists(Dex in, IndexMap indexMap)696     private void transformAnnotationSetRefLists(Dex in, IndexMap indexMap) {
697         TableOfContents.Section section = in.getTableOfContents().annotationSetRefLists;
698         if (section.exists()) {
699             Dex.Section setIn = in.open(section.off);
700             for (int i = 0; i < section.size; i++) {
701                 transformAnnotationSetRefList(indexMap, setIn);
702             }
703         }
704     }
705 
transformAnnotationDirectories(Dex in, IndexMap indexMap)706     private void transformAnnotationDirectories(Dex in, IndexMap indexMap) {
707         TableOfContents.Section section = in.getTableOfContents().annotationsDirectories;
708         if (section.exists()) {
709             Dex.Section directoryIn = in.open(section.off);
710             for (int i = 0; i < section.size; i++) {
711                 transformAnnotationDirectory(directoryIn, indexMap);
712             }
713         }
714     }
715 
transformStaticValues(Dex in, IndexMap indexMap)716     private void transformStaticValues(Dex in, IndexMap indexMap) {
717         TableOfContents.Section section = in.getTableOfContents().encodedArrays;
718         if (section.exists()) {
719             Dex.Section staticValuesIn = in.open(section.off);
720             for (int i = 0; i < section.size; i++) {
721                 transformStaticValues(staticValuesIn, indexMap);
722             }
723         }
724     }
725 
726     /**
727      * Reads a class_def_item beginning at {@code in} and writes the index and
728      * data.
729      */
transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap)730     private void transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap) {
731         idsDefsOut.assertFourByteAligned();
732         idsDefsOut.writeInt(classDef.getTypeIndex());
733         idsDefsOut.writeInt(classDef.getAccessFlags());
734         idsDefsOut.writeInt(classDef.getSupertypeIndex());
735         idsDefsOut.writeInt(classDef.getInterfacesOffset());
736 
737         int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex());
738         idsDefsOut.writeInt(sourceFileIndex);
739 
740         int annotationsOff = classDef.getAnnotationsOffset();
741         idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff));
742 
743         int classDataOff = classDef.getClassDataOffset();
744         if (classDataOff == 0) {
745             idsDefsOut.writeInt(0);
746         } else {
747             idsDefsOut.writeInt(classDataOut.getPosition());
748             ClassData classData = in.readClassData(classDef);
749             transformClassData(in, classData, indexMap);
750         }
751 
752         int staticValuesOff = classDef.getStaticValuesOffset();
753         idsDefsOut.writeInt(indexMap.adjustEncodedArray(staticValuesOff));
754     }
755 
756     /**
757      * Transform all annotations on a class.
758      */
transformAnnotationDirectory( Dex.Section directoryIn, IndexMap indexMap)759     private void transformAnnotationDirectory(
760             Dex.Section directoryIn, IndexMap indexMap) {
761         contentsOut.annotationsDirectories.size++;
762         annotationsDirectoryOut.assertFourByteAligned();
763         indexMap.putAnnotationDirectoryOffset(
764                 directoryIn.getPosition(), annotationsDirectoryOut.getPosition());
765 
766         int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt());
767         annotationsDirectoryOut.writeInt(classAnnotationsOffset);
768 
769         int fieldsSize = directoryIn.readInt();
770         annotationsDirectoryOut.writeInt(fieldsSize);
771 
772         int methodsSize = directoryIn.readInt();
773         annotationsDirectoryOut.writeInt(methodsSize);
774 
775         int parameterListSize = directoryIn.readInt();
776         annotationsDirectoryOut.writeInt(parameterListSize);
777 
778         for (int i = 0; i < fieldsSize; i++) {
779             // field index
780             annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt()));
781 
782             // annotations offset
783             annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
784         }
785 
786         for (int i = 0; i < methodsSize; i++) {
787             // method index
788             annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
789 
790             // annotation set offset
791             annotationsDirectoryOut.writeInt(
792                     indexMap.adjustAnnotationSet(directoryIn.readInt()));
793         }
794 
795         for (int i = 0; i < parameterListSize; i++) {
796             // method index
797             annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
798 
799             // annotations offset
800             annotationsDirectoryOut.writeInt(
801                     indexMap.adjustAnnotationSetRefList(directoryIn.readInt()));
802         }
803     }
804 
805     /**
806      * Transform all annotations on a single type, member or parameter.
807      */
transformAnnotationSet(IndexMap indexMap, Dex.Section setIn)808     private void transformAnnotationSet(IndexMap indexMap, Dex.Section setIn) {
809         contentsOut.annotationSets.size++;
810         annotationSetOut.assertFourByteAligned();
811         indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition());
812 
813         int size = setIn.readInt();
814         annotationSetOut.writeInt(size);
815 
816         for (int j = 0; j < size; j++) {
817             annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt()));
818         }
819     }
820 
821     /**
822      * Transform all annotation set ref lists.
823      */
transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn)824     private void transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn) {
825         contentsOut.annotationSetRefLists.size++;
826         annotationSetRefListOut.assertFourByteAligned();
827         indexMap.putAnnotationSetRefListOffset(
828                 refListIn.getPosition(), annotationSetRefListOut.getPosition());
829 
830         int parameterCount = refListIn.readInt();
831         annotationSetRefListOut.writeInt(parameterCount);
832         for (int p = 0; p < parameterCount; p++) {
833             annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt()));
834         }
835     }
836 
transformClassData(Dex in, ClassData classData, IndexMap indexMap)837     private void transformClassData(Dex in, ClassData classData, IndexMap indexMap) {
838         contentsOut.classDatas.size++;
839 
840         ClassData.Field[] staticFields = classData.getStaticFields();
841         ClassData.Field[] instanceFields = classData.getInstanceFields();
842         ClassData.Method[] directMethods = classData.getDirectMethods();
843         ClassData.Method[] virtualMethods = classData.getVirtualMethods();
844 
845         classDataOut.writeUleb128(staticFields.length);
846         classDataOut.writeUleb128(instanceFields.length);
847         classDataOut.writeUleb128(directMethods.length);
848         classDataOut.writeUleb128(virtualMethods.length);
849 
850         transformFields(indexMap, staticFields);
851         transformFields(indexMap, instanceFields);
852         transformMethods(in, indexMap, directMethods);
853         transformMethods(in, indexMap, virtualMethods);
854     }
855 
transformFields(IndexMap indexMap, ClassData.Field[] fields)856     private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
857         int lastOutFieldIndex = 0;
858         for (ClassData.Field field : fields) {
859             int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
860             classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex);
861             lastOutFieldIndex = outFieldIndex;
862             classDataOut.writeUleb128(field.getAccessFlags());
863         }
864     }
865 
transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods)866     private void transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods) {
867         int lastOutMethodIndex = 0;
868         for (ClassData.Method method : methods) {
869             int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
870             classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex);
871             lastOutMethodIndex = outMethodIndex;
872 
873             classDataOut.writeUleb128(method.getAccessFlags());
874 
875             if (method.getCodeOffset() == 0) {
876                 classDataOut.writeUleb128(0);
877             } else {
878                 codeOut.alignToFourBytesWithZeroFill();
879                 classDataOut.writeUleb128(codeOut.getPosition());
880                 transformCode(in, in.readCode(method), indexMap);
881             }
882         }
883     }
884 
transformCode(Dex in, Code code, IndexMap indexMap)885     private void transformCode(Dex in, Code code, IndexMap indexMap) {
886         contentsOut.codes.size++;
887         codeOut.assertFourByteAligned();
888 
889         codeOut.writeUnsignedShort(code.getRegistersSize());
890         codeOut.writeUnsignedShort(code.getInsSize());
891         codeOut.writeUnsignedShort(code.getOutsSize());
892 
893         Code.Try[] tries = code.getTries();
894         Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
895         codeOut.writeUnsignedShort(tries.length);
896 
897         int debugInfoOffset = code.getDebugInfoOffset();
898         if (debugInfoOffset != 0) {
899             codeOut.writeInt(debugInfoOut.getPosition());
900             transformDebugInfoItem(in.open(debugInfoOffset), indexMap);
901         } else {
902             codeOut.writeInt(0);
903         }
904 
905         short[] instructions = code.getInstructions();
906         short[] newInstructions = instructionTransformer.transform(indexMap, instructions);
907         codeOut.writeInt(newInstructions.length);
908         codeOut.write(newInstructions);
909 
910         if (tries.length > 0) {
911             if (newInstructions.length % 2 == 1) {
912                 codeOut.writeShort((short) 0); // padding
913             }
914 
915             /*
916              * We can't write the tries until we've written the catch handlers.
917              * Unfortunately they're in the opposite order in the dex file so we
918              * need to transform them out-of-order.
919              */
920             Dex.Section triesSection = dexOut.open(codeOut.getPosition());
921             codeOut.skip(tries.length * SizeOf.TRY_ITEM);
922             int[] offsets = transformCatchHandlers(indexMap, catchHandlers);
923             transformTries(triesSection, tries, offsets);
924         }
925     }
926 
927     /**
928      * Writes the catch handlers to {@code codeOut} and returns their indices.
929      */
transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers)930     private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) {
931         int baseOffset = codeOut.getPosition();
932         codeOut.writeUleb128(catchHandlers.length);
933         int[] offsets = new int[catchHandlers.length];
934         for (int i = 0; i < catchHandlers.length; i++) {
935             offsets[i] = codeOut.getPosition() - baseOffset;
936             transformEncodedCatchHandler(catchHandlers[i], indexMap);
937         }
938         return offsets;
939     }
940 
transformTries(Dex.Section out, Code.Try[] tries, int[] catchHandlerOffsets)941     private void transformTries(Dex.Section out, Code.Try[] tries,
942             int[] catchHandlerOffsets) {
943         for (Code.Try tryItem : tries) {
944             out.writeInt(tryItem.getStartAddress());
945             out.writeUnsignedShort(tryItem.getInstructionCount());
946             out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]);
947         }
948     }
949 
950     private static final byte DBG_END_SEQUENCE = 0x00;
951     private static final byte DBG_ADVANCE_PC = 0x01;
952     private static final byte DBG_ADVANCE_LINE = 0x02;
953     private static final byte DBG_START_LOCAL = 0x03;
954     private static final byte DBG_START_LOCAL_EXTENDED = 0x04;
955     private static final byte DBG_END_LOCAL = 0x05;
956     private static final byte DBG_RESTART_LOCAL = 0x06;
957     private static final byte DBG_SET_PROLOGUE_END = 0x07;
958     private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08;
959     private static final byte DBG_SET_FILE = 0x09;
960 
transformDebugInfoItem(Dex.Section in, IndexMap indexMap)961     private void transformDebugInfoItem(Dex.Section in, IndexMap indexMap) {
962         contentsOut.debugInfos.size++;
963         int lineStart = in.readUleb128();
964         debugInfoOut.writeUleb128(lineStart);
965 
966         int parametersSize = in.readUleb128();
967         debugInfoOut.writeUleb128(parametersSize);
968 
969         for (int p = 0; p < parametersSize; p++) {
970             int parameterName = in.readUleb128p1();
971             debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName));
972         }
973 
974         int addrDiff;    // uleb128   address delta.
975         int lineDiff;    // sleb128   line delta.
976         int registerNum; // uleb128   register number.
977         int nameIndex;   // uleb128p1 string index.    Needs indexMap adjustment.
978         int typeIndex;   // uleb128p1 type index.      Needs indexMap adjustment.
979         int sigIndex;    // uleb128p1 string index.    Needs indexMap adjustment.
980 
981         while (true) {
982             int opcode = in.readByte();
983             debugInfoOut.writeByte(opcode);
984 
985             switch (opcode) {
986             case DBG_END_SEQUENCE:
987                 return;
988 
989             case DBG_ADVANCE_PC:
990                 addrDiff = in.readUleb128();
991                 debugInfoOut.writeUleb128(addrDiff);
992                 break;
993 
994             case DBG_ADVANCE_LINE:
995                 lineDiff = in.readSleb128();
996                 debugInfoOut.writeSleb128(lineDiff);
997                 break;
998 
999             case DBG_START_LOCAL:
1000             case DBG_START_LOCAL_EXTENDED:
1001                 registerNum = in.readUleb128();
1002                 debugInfoOut.writeUleb128(registerNum);
1003                 nameIndex = in.readUleb128p1();
1004                 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
1005                 typeIndex = in.readUleb128p1();
1006                 debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex));
1007                 if (opcode == DBG_START_LOCAL_EXTENDED) {
1008                     sigIndex = in.readUleb128p1();
1009                     debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex));
1010                 }
1011                 break;
1012 
1013             case DBG_END_LOCAL:
1014             case DBG_RESTART_LOCAL:
1015                 registerNum = in.readUleb128();
1016                 debugInfoOut.writeUleb128(registerNum);
1017                 break;
1018 
1019             case DBG_SET_FILE:
1020                 nameIndex = in.readUleb128p1();
1021                 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
1022                 break;
1023 
1024             case DBG_SET_PROLOGUE_END:
1025             case DBG_SET_EPILOGUE_BEGIN:
1026             default:
1027                 break;
1028             }
1029         }
1030     }
1031 
transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap)1032     private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
1033         int catchAllAddress = catchHandler.getCatchAllAddress();
1034         int[] typeIndexes = catchHandler.getTypeIndexes();
1035         int[] addresses = catchHandler.getAddresses();
1036 
1037         if (catchAllAddress != -1) {
1038             codeOut.writeSleb128(-typeIndexes.length);
1039         } else {
1040             codeOut.writeSleb128(typeIndexes.length);
1041         }
1042 
1043         for (int i = 0; i < typeIndexes.length; i++) {
1044             codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i]));
1045             codeOut.writeUleb128(addresses[i]);
1046         }
1047 
1048         if (catchAllAddress != -1) {
1049             codeOut.writeUleb128(catchAllAddress);
1050         }
1051     }
1052 
transformStaticValues(Dex.Section in, IndexMap indexMap)1053     private void transformStaticValues(Dex.Section in, IndexMap indexMap) {
1054         contentsOut.encodedArrays.size++;
1055         indexMap.putEncodedArrayValueOffset(in.getPosition(), encodedArrayOut.getPosition());
1056         indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut);
1057     }
1058 
1059     /**
1060      * Byte counts for the sections written when creating a dex. Target sizes
1061      * are defined in one of two ways:
1062      * <ul>
1063      * <li>By pessimistically guessing how large the union of dex files will be.
1064      *     We're pessimistic because we can't predict the amount of duplication
1065      *     between dex files, nor can we predict the length of ULEB-encoded
1066      *     offsets or indices.
1067      * <li>By exactly measuring an existing dex.
1068      * </ul>
1069      */
1070     private static class WriterSizes {
1071         private int header = SizeOf.HEADER_ITEM;
1072         private int idsDefs;
1073         private int mapList;
1074         private int typeList;
1075         private int classData;
1076         private int code;
1077         private int stringData;
1078         private int debugInfo;
1079         private int encodedArray;
1080         private int annotationsDirectory;
1081         private int annotationsSet;
1082         private int annotationsSetRefList;
1083         private int annotation;
1084 
1085         /**
1086          * Compute sizes for merging several dexes.
1087          */
WriterSizes(Dex[] dexes)1088         public WriterSizes(Dex[] dexes) {
1089             for (int i = 0; i < dexes.length; i++) {
1090                 plus(dexes[i].getTableOfContents(), false);
1091             }
1092             fourByteAlign();
1093         }
1094 
WriterSizes(DexMerger dexMerger)1095         public WriterSizes(DexMerger dexMerger) {
1096             header = dexMerger.headerOut.used();
1097             idsDefs = dexMerger.idsDefsOut.used();
1098             mapList = dexMerger.mapListOut.used();
1099             typeList = dexMerger.typeListOut.used();
1100             classData = dexMerger.classDataOut.used();
1101             code = dexMerger.codeOut.used();
1102             stringData = dexMerger.stringDataOut.used();
1103             debugInfo = dexMerger.debugInfoOut.used();
1104             encodedArray = dexMerger.encodedArrayOut.used();
1105             annotationsDirectory = dexMerger.annotationsDirectoryOut.used();
1106             annotationsSet = dexMerger.annotationSetOut.used();
1107             annotationsSetRefList = dexMerger.annotationSetRefListOut.used();
1108             annotation = dexMerger.annotationOut.used();
1109             fourByteAlign();
1110         }
1111 
plus(TableOfContents contents, boolean exact)1112         private void plus(TableOfContents contents, boolean exact) {
1113             idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM
1114                     + contents.typeIds.size * SizeOf.TYPE_ID_ITEM
1115                     + contents.protoIds.size * SizeOf.PROTO_ID_ITEM
1116                     + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM
1117                     + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM
1118                     + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM;
1119             mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM);
1120             typeList += fourByteAlign(contents.typeLists.byteCount); // We count each dex's
1121             // typelists section as realigned on 4 bytes, because each typelist of each dex's
1122             // typelists section is aligned on 4 bytes. If we didn't, there is a case where each
1123             // size of both dex's typelists section is a multiple of 2 but not a multiple of 4,
1124             // and the sum of both sizes is a multiple of 4 but would not be sufficient to write
1125             // each typelist aligned on 4 bytes.
1126             stringData += contents.stringDatas.byteCount;
1127             annotationsDirectory += contents.annotationsDirectories.byteCount;
1128             annotationsSet += contents.annotationSets.byteCount;
1129             annotationsSetRefList += contents.annotationSetRefLists.byteCount;
1130 
1131             if (exact) {
1132                 code += contents.codes.byteCount;
1133                 classData += contents.classDatas.byteCount;
1134                 encodedArray += contents.encodedArrays.byteCount;
1135                 annotation += contents.annotations.byteCount;
1136                 debugInfo += contents.debugInfos.byteCount;
1137             } else {
1138                 // at most 1/4 of the bytes in a code section are uleb/sleb
1139                 code += (int) Math.ceil(contents.codes.byteCount * 1.25);
1140                 // at most 2/3 of the bytes in a class data section are uleb/sleb that may change
1141                 // (assuming the worst case that section contains only methods and no fields)
1142                 classData += (int) Math.ceil(contents.classDatas.byteCount * 1.67);
1143                 // all of the bytes in an encoding arrays section may be uleb/sleb
1144                 encodedArray += contents.encodedArrays.byteCount * 2;
1145                 // all of the bytes in an annotations section may be uleb/sleb
1146                 annotation += (int) Math.ceil(contents.annotations.byteCount * 2);
1147                 // all of the bytes in a debug info section may be uleb/sleb. The additive constant
1148                 // is a fudge factor observed to be required when merging small
1149                 // DEX files (b/68483205).
1150                 debugInfo += contents.debugInfos.byteCount * 2 + 8;
1151             }
1152         }
1153 
fourByteAlign()1154         private void fourByteAlign() {
1155             header = fourByteAlign(header);
1156             idsDefs = fourByteAlign(idsDefs);
1157             mapList = fourByteAlign(mapList);
1158             typeList = fourByteAlign(typeList);
1159             classData = fourByteAlign(classData);
1160             code = fourByteAlign(code);
1161             stringData = fourByteAlign(stringData);
1162             debugInfo = fourByteAlign(debugInfo);
1163             encodedArray = fourByteAlign(encodedArray);
1164             annotationsDirectory = fourByteAlign(annotationsDirectory);
1165             annotationsSet = fourByteAlign(annotationsSet);
1166             annotationsSetRefList = fourByteAlign(annotationsSetRefList);
1167             annotation = fourByteAlign(annotation);
1168         }
1169 
fourByteAlign(int position)1170         private static int fourByteAlign(int position) {
1171             return (position + 3) & ~3;
1172         }
1173 
size()1174         public int size() {
1175             return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo
1176                     + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList
1177                     + annotation;
1178         }
1179     }
1180 
main(String[] args)1181     public static void main(String[] args) throws IOException {
1182         if (args.length < 2) {
1183             printUsage();
1184             return;
1185         }
1186 
1187         Dex[] dexes = new Dex[args.length - 1];
1188         for (int i = 1; i < args.length; i++) {
1189             dexes[i - 1] = new Dex(new File(args[i]));
1190         }
1191         Dex merged = new DexMerger(dexes, CollisionPolicy.KEEP_FIRST, new DxContext()).merge();
1192         merged.writeTo(new File(args[0]));
1193     }
1194 
printUsage()1195     private static void printUsage() {
1196         System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex> ...");
1197         System.out.println();
1198         System.out.println(
1199             "If a class is defined in several dex, the class found in the first dex will be used.");
1200     }
1201 }
1202