DexMerger.java revision a6264bd14e66bd05f98d0b1b98ee9e044b93faf9
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
17package com.android.dx.merge;
18
19import com.android.dx.dex.SizeOf;
20import com.android.dx.dex.TableOfContents;
21import com.android.dx.io.Annotation;
22import com.android.dx.io.ClassData;
23import com.android.dx.io.ClassDef;
24import com.android.dx.io.Code;
25import com.android.dx.io.DexBuffer;
26import com.android.dx.io.DexHasher;
27import com.android.dx.io.FieldId;
28import com.android.dx.io.MethodId;
29import com.android.dx.io.ProtoId;
30import com.android.dx.util.DexException;
31import java.io.File;
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collections;
36import java.util.List;
37
38/**
39 * Combine two dex files into one.
40 */
41public final class DexMerger {
42    private final DexBuffer dexA;
43    private final DexBuffer dexB;
44    private final CollisionPolicy collisionPolicy;
45    private final WriterSizes writerSizes;
46
47    private final DexBuffer dexOut = new DexBuffer();
48
49    private final DexBuffer.Section headerOut;
50
51    /** All IDs and definitions sections */
52    private final DexBuffer.Section idsDefsOut;
53
54    private final DexBuffer.Section mapListOut;
55
56    private final DexBuffer.Section typeListOut;
57
58    private final DexBuffer.Section classDataOut;
59
60    private final DexBuffer.Section codeOut;
61
62    private final DexBuffer.Section stringDataOut;
63
64    private final DexBuffer.Section debugInfoOut;
65
66    private final DexBuffer.Section encodedArrayOut;
67
68    /** annotations directory on a type */
69    private final DexBuffer.Section annotationsDirectoryOut;
70
71    /** sets of annotations on a member, parameter or type */
72    private final DexBuffer.Section annotationSetOut;
73
74    /** parameter lists */
75    private final DexBuffer.Section annotationSetRefListOut;
76
77    /** individual annotations, each containing zero or more fields */
78    private final DexBuffer.Section annotationOut;
79
80    private final TableOfContents contentsOut;
81
82    private final IndexMap aIndexMap;
83    private final IndexMap bIndexMap;
84    private final InstructionTransformer aInstructionTransformer;
85    private final InstructionTransformer bInstructionTransformer;
86
87    /** minimum number of wasted bytes before it's worthwhile to compact the result */
88    private int compactWasteThreshold = 1024 * 1024; // 1MiB
89
90    public DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy)
91            throws IOException {
92        this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB));
93    }
94
95    private DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy,
96            WriterSizes writerSizes) throws IOException {
97        this.dexA = dexA;
98        this.dexB = dexB;
99        this.collisionPolicy = collisionPolicy;
100        this.writerSizes = writerSizes;
101
102        TableOfContents aContents = dexA.getTableOfContents();
103        TableOfContents bContents = dexB.getTableOfContents();
104        aIndexMap = new IndexMap(dexOut, aContents);
105        bIndexMap = new IndexMap(dexOut, bContents);
106        aInstructionTransformer = new InstructionTransformer(aIndexMap);
107        bInstructionTransformer = new InstructionTransformer(bIndexMap);
108
109        headerOut = dexOut.appendSection(writerSizes.header, "header");
110        idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs");
111
112        contentsOut = dexOut.getTableOfContents();
113        contentsOut.dataOff = dexOut.getLength();
114
115        contentsOut.mapList.off = dexOut.getLength();
116        contentsOut.mapList.size = 1;
117        mapListOut = dexOut.appendSection(writerSizes.mapList, "map list");
118
119        contentsOut.typeLists.off = dexOut.getLength();
120        typeListOut = dexOut.appendSection(writerSizes.typeList, "type list");
121
122        contentsOut.annotationSetRefLists.off = dexOut.getLength();
123        annotationSetRefListOut = dexOut.appendSection(
124                writerSizes.annotationsSetRefList, "annotation set ref list");
125
126        contentsOut.annotationSets.off = dexOut.getLength();
127        annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets");
128
129        contentsOut.classDatas.off = dexOut.getLength();
130        classDataOut = dexOut.appendSection(writerSizes.classData, "class data");
131
132        contentsOut.codes.off = dexOut.getLength();
133        codeOut = dexOut.appendSection(writerSizes.code, "code");
134
135        contentsOut.stringDatas.off = dexOut.getLength();
136        stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data");
137
138        contentsOut.debugInfos.off = dexOut.getLength();
139        debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info");
140
141        contentsOut.annotations.off = dexOut.getLength();
142        annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation");
143
144        contentsOut.encodedArrays.off = dexOut.getLength();
145        encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array");
146
147        contentsOut.annotationsDirectories.off = dexOut.getLength();
148        annotationsDirectoryOut = dexOut.appendSection(
149                writerSizes.annotationsDirectory, "annotations directory");
150
151        dexOut.noMoreSections();
152        contentsOut.dataSize = dexOut.getLength() - contentsOut.dataOff;
153    }
154
155    public void setCompactWasteThreshold(int compactWasteThreshold) {
156        this.compactWasteThreshold = compactWasteThreshold;
157    }
158
159    private DexBuffer mergeDexBuffers() throws IOException {
160        mergeStringIds();
161        mergeTypeIds();
162        mergeTypeLists();
163        mergeProtoIds();
164        mergeFieldIds();
165        mergeMethodIds();
166        mergeAnnotations();
167        unionAnnotationSetsAndDirectories();
168        mergeClassDefs();
169
170        // write the header
171        contentsOut.header.off = 0;
172        contentsOut.header.size = 1;
173        contentsOut.fileSize = dexOut.getLength();
174        contentsOut.computeSizesFromOffsets();
175        contentsOut.writeHeader(headerOut);
176        contentsOut.writeMap(mapListOut);
177
178        // generate and write the hashes
179        new DexHasher().writeHashes(dexOut);
180
181        return dexOut;
182    }
183
184    public DexBuffer merge() throws IOException {
185        long start = System.nanoTime();
186        DexBuffer result = mergeDexBuffers();
187
188        /*
189         * We use pessimistic sizes when merging dex files. If those sizes
190         * result in too many bytes wasted, compact the result. To compact,
191         * simply merge the result with itself.
192         */
193        WriterSizes compactedSizes = writerSizes.clone();
194        compactedSizes.minusWaste(this);
195        int wastedByteCount = writerSizes.size() - compactedSizes.size();
196        if (wastedByteCount >  + compactWasteThreshold) {
197            DexMerger compacter = new DexMerger(
198                    dexOut, new DexBuffer(), CollisionPolicy.FAIL, compactedSizes);
199            result = compacter.mergeDexBuffers();
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);
204        }
205
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);
216
217        return result;
218    }
219
220    /**
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.
223     */
224    abstract class IdMerger<T extends Comparable<T>> {
225        private final DexBuffer.Section out;
226
227        protected IdMerger(DexBuffer.Section out) {
228            this.out = out;
229        }
230
231        /**
232         * Merges already-sorted sections, reading only two values into memory
233         * at a time.
234         */
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();
239
240            DexBuffer.Section inA = aSection.exists() ? dexA.open(aSection.off) : null;
241            DexBuffer.Section inB = bSection.exists() ? dexB.open(bSection.off) : null;
242            int aOffset = -1;
243            int bOffset = -1;
244            int aIndex = 0;
245            int bIndex = 0;
246            int outCount = 0;
247            T a = null;
248            T b = null;
249
250            while (true) {
251                if (a == null && aIndex < aSection.size) {
252                    aOffset = inA.getPosition();
253                    a = read(inA, aIndexMap, aIndex);
254                }
255                if (b == null && bIndex < bSection.size) {
256                    bOffset = inB.getPosition();
257                    b = read(inB, bIndexMap, bIndex);
258                }
259
260                // Write the smaller of a and b. If they're equal, write only once
261                boolean advanceA;
262                boolean advanceB;
263                if (a != null && b != null) {
264                    int compare = a.compareTo(b);
265                    advanceA = compare <= 0;
266                    advanceB = compare >= 0;
267                } else {
268                    advanceA = (a != null);
269                    advanceB = (b != null);
270                }
271
272                T toWrite = null;
273                if (advanceA) {
274                    toWrite = a;
275                    updateIndex(aOffset, aIndexMap, aIndex++, outCount);
276                    a = null;
277                    aOffset = -1;
278                }
279                if (advanceB) {
280                    toWrite = b;
281                    updateIndex(bOffset, bIndexMap, bIndex++, outCount);
282                    b = null;
283                    bOffset = -1;
284                }
285                if (toWrite == null) {
286                    break; // advanceA == false && advanceB == false
287                }
288                write(toWrite);
289                outCount++;
290            }
291
292            getSection(contentsOut).size = outCount;
293        }
294
295        /**
296         * Merges unsorted sections by reading them completely into memory and
297         * sorting in memory.
298         */
299        public final void mergeUnsorted() {
300            getSection(contentsOut).off = out.getPosition();
301
302            List<UnsortedValue> all = new ArrayList<UnsortedValue>();
303            all.addAll(readUnsortedValues(dexA, aIndexMap));
304            all.addAll(readUnsortedValues(dexB, bIndexMap));
305            Collections.sort(all);
306
307            int outCount = 0;
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);
311
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);
315                }
316
317                write(e1.value);
318                outCount++;
319            }
320
321            getSection(contentsOut).size = outCount;
322        }
323
324        private List<UnsortedValue> readUnsortedValues(DexBuffer source, IndexMap indexMap) {
325            TableOfContents.Section section = getSection(source.getTableOfContents());
326            if (!section.exists()) {
327                return Collections.emptyList();
328            }
329
330            List<UnsortedValue> result = new ArrayList<UnsortedValue>();
331            DexBuffer.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));
336            }
337            return result;
338        }
339
340        abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
341        abstract T read(DexBuffer.Section in, IndexMap indexMap, int index);
342        abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex);
343        abstract void write(T value);
344
345        class UnsortedValue implements Comparable<UnsortedValue> {
346            final DexBuffer source;
347            final IndexMap indexMap;
348            final T value;
349            final int index;
350            final int offset;
351
352            UnsortedValue(DexBuffer source, IndexMap indexMap, T value, int index, int offset) {
353                this.source = source;
354                this.indexMap = indexMap;
355                this.value = value;
356                this.index = index;
357                this.offset = offset;
358            }
359
360            public int compareTo(UnsortedValue unsortedValue) {
361                return value.compareTo(unsortedValue.value);
362            }
363        }
364    }
365
366    private IndexMap getIndexMap(DexBuffer dexBuffer) {
367        if (dexBuffer == dexA) {
368            return aIndexMap;
369        } else if (dexBuffer == dexB) {
370            return bIndexMap;
371        } else {
372            throw new IllegalArgumentException();
373        }
374    }
375
376    private void mergeStringIds() {
377        new IdMerger<String>(idsDefsOut) {
378            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
379                return tableOfContents.stringIds;
380            }
381
382            @Override String read(DexBuffer.Section in, IndexMap indexMap, int index) {
383                return in.readString();
384            }
385
386            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
387                indexMap.stringIds[oldIndex] = newIndex;
388            }
389
390            @Override void write(String value) {
391                contentsOut.stringDatas.size++;
392                idsDefsOut.writeInt(stringDataOut.getPosition());
393                stringDataOut.writeStringData(value);
394            }
395        }.mergeSorted();
396    }
397
398    private void mergeTypeIds() {
399        new IdMerger<Integer>(idsDefsOut) {
400            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
401                return tableOfContents.typeIds;
402            }
403
404            @Override Integer read(DexBuffer.Section in, IndexMap indexMap, int index) {
405                int stringIndex = in.readInt();
406                return indexMap.adjustString(stringIndex);
407            }
408
409            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
410                indexMap.typeIds[oldIndex] = (short) newIndex;
411            }
412
413            @Override void write(Integer value) {
414                idsDefsOut.writeInt(value);
415            }
416        }.mergeSorted();
417    }
418
419    private void mergeTypeLists() {
420        new IdMerger<TypeList>(typeListOut) {
421            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
422                return tableOfContents.typeLists;
423            }
424
425            @Override TypeList read(DexBuffer.Section in, IndexMap indexMap, int index) {
426                return indexMap.adjustTypeList(in.readTypeList());
427            }
428
429            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
430                indexMap.putTypeListOffset(offset, typeListOut.getPosition());
431            }
432
433            @Override void write(TypeList value) {
434                typeListOut.writeTypeList(value);
435            }
436        }.mergeUnsorted();
437    }
438
439    private void mergeProtoIds() {
440        new IdMerger<ProtoId>(idsDefsOut) {
441            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
442                return tableOfContents.protoIds;
443            }
444
445            @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index) {
446                return indexMap.adjust(in.readProtoId());
447            }
448
449            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
450                indexMap.protoIds[oldIndex] = (short) newIndex;
451            }
452
453            @Override void write(ProtoId value) {
454                value.writeTo(idsDefsOut);
455            }
456        }.mergeSorted();
457    }
458
459    private void mergeFieldIds() {
460        new IdMerger<FieldId>(idsDefsOut) {
461            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
462                return tableOfContents.fieldIds;
463            }
464
465            @Override FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) {
466                return indexMap.adjust(in.readFieldId());
467            }
468
469            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
470                indexMap.fieldIds[oldIndex] = (short) newIndex;
471            }
472
473            @Override void write(FieldId value) {
474                value.writeTo(idsDefsOut);
475            }
476        }.mergeSorted();
477    }
478
479    private void mergeMethodIds() {
480        new IdMerger<MethodId>(idsDefsOut) {
481            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
482                return tableOfContents.methodIds;
483            }
484
485            @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index) {
486                return indexMap.adjust(in.readMethodId());
487            }
488
489            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
490                indexMap.methodIds[oldIndex] = (short) newIndex;
491            }
492
493            @Override void write(MethodId methodId) {
494                methodId.writeTo(idsDefsOut);
495            }
496        }.mergeSorted();
497    }
498
499    private void mergeAnnotations() {
500        new IdMerger<Annotation>(annotationOut) {
501            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
502                return tableOfContents.annotations;
503            }
504
505            @Override Annotation read(DexBuffer.Section in, IndexMap indexMap, int index) {
506                return indexMap.adjust(in.readAnnotation());
507            }
508
509            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
510                indexMap.putAnnotationOffset(offset, annotationOut.getPosition());
511            }
512
513            @Override void write(Annotation value) {
514                value.writeTo(annotationOut);
515            }
516        }.mergeUnsorted();
517    }
518
519    private void mergeClassDefs() {
520        SortableType[] types = getSortedTypes();
521        contentsOut.classDefs.off = idsDefsOut.getPosition();
522        contentsOut.classDefs.size = types.length;
523
524        for (SortableType type : types) {
525            DexBuffer in = type.getBuffer();
526            IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap;
527            transformClassDef(in, type.getClassDef(), indexMap);
528        }
529    }
530
531    /**
532     * Returns the union of classes from both files, sorted in order such that
533     * a class is always preceded by its supertype and implemented interfaces.
534     */
535    private SortableType[] getSortedTypes() {
536        // size is pessimistic; doesn't include arrays
537        SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size];
538        readSortableTypes(sortableTypes, dexA, aIndexMap);
539        readSortableTypes(sortableTypes, dexB, bIndexMap);
540
541        /*
542         * Populate the depths of each sortable type. This makes D iterations
543         * through all N types, where 'D' is the depth of the deepest type. For
544         * example, the deepest class in libcore is Xalan's KeyIterator, which
545         * is 11 types deep.
546         */
547        while (true) {
548            boolean allDone = true;
549            for (SortableType sortableType : sortableTypes) {
550                if (sortableType != null && !sortableType.isDepthAssigned()) {
551                    allDone &= sortableType.tryAssignDepth(sortableTypes);
552                }
553            }
554            if (allDone) {
555                break;
556            }
557        }
558
559        // Now that all types have depth information, the result can be sorted
560        Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER);
561
562        // Strip nulls from the end
563        int firstNull = Arrays.asList(sortableTypes).indexOf(null);
564        return firstNull != -1
565                ? Arrays.copyOfRange(sortableTypes, 0, firstNull)
566                : sortableTypes;
567    }
568
569    /**
570     * Reads just enough data on each class so that we can sort it and then find
571     * it later.
572     */
573    private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer,
574            IndexMap indexMap) {
575        for (ClassDef classDef : buffer.classDefs()) {
576            SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef));
577            int t = sortableType.getTypeIndex();
578            if (sortableTypes[t] == null) {
579                sortableTypes[t] = sortableType;
580            } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) {
581                throw new DexException("Multiple dex files define "
582                        + buffer.typeNames().get(classDef.getTypeIndex()));
583            }
584        }
585    }
586
587    /**
588     * Copy annotation sets from each input to the output.
589     *
590     * TODO: this may write multiple copies of the same annotation set.
591     * We should shrink the output by merging rather than unioning
592     */
593    private void unionAnnotationSetsAndDirectories() {
594        transformAnnotationSets(dexA, aIndexMap);
595        transformAnnotationSets(dexB, bIndexMap);
596        transformAnnotationDirectories(dexA, aIndexMap);
597        transformAnnotationDirectories(dexB, bIndexMap);
598    }
599
600    private void transformAnnotationSets(DexBuffer in, IndexMap indexMap) {
601        TableOfContents.Section section = in.getTableOfContents().annotationSets;
602        if (section.exists()) {
603            DexBuffer.Section setIn = in.open(section.off);
604            for (int i = 0; i < section.size; i++) {
605                transformAnnotationSet(indexMap, setIn);
606            }
607        }
608    }
609
610    private void transformAnnotationDirectories(DexBuffer in, IndexMap indexMap) {
611        TableOfContents.Section section = in.getTableOfContents().annotationsDirectories;
612        if (section.exists()) {
613            DexBuffer.Section directoryIn = in.open(section.off);
614            for (int i = 0; i < section.size; i++) {
615                transformAnnotationDirectory(in, directoryIn, indexMap);
616            }
617        }
618    }
619
620    /**
621     * Reads a class_def_item beginning at {@code in} and writes the index and
622     * data.
623     */
624    private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) {
625        idsDefsOut.assertFourByteAligned();
626        idsDefsOut.writeInt(classDef.getTypeIndex());
627        idsDefsOut.writeInt(classDef.getAccessFlags());
628        idsDefsOut.writeInt(classDef.getSupertypeIndex());
629        idsDefsOut.writeInt(classDef.getInterfacesOffset());
630
631        int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex());
632        idsDefsOut.writeInt(sourceFileIndex);
633
634        int annotationsOff = classDef.getAnnotationsOffset();
635        idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff));
636
637        int classDataOff = classDef.getClassDataOffset();
638        if (classDataOff == 0) {
639            idsDefsOut.writeInt(0);
640        } else {
641            idsDefsOut.writeInt(classDataOut.getPosition());
642            ClassData classData = in.readClassData(classDef);
643            transformClassData(in, classData, indexMap);
644        }
645
646        int staticValuesOff = classDef.getStaticValuesOffset();
647        if (staticValuesOff == 0) {
648            idsDefsOut.writeInt(0);
649        } else {
650            DexBuffer.Section staticValuesIn = in.open(staticValuesOff);
651            idsDefsOut.writeInt(encodedArrayOut.getPosition());
652            transformStaticValues(staticValuesIn, indexMap);
653        }
654    }
655
656    /**
657     * Transform all annotations on a class.
658     */
659    private void transformAnnotationDirectory(
660            DexBuffer in, DexBuffer.Section directoryIn, IndexMap indexMap) {
661        contentsOut.annotationsDirectories.size++;
662        annotationsDirectoryOut.assertFourByteAligned();
663        indexMap.putAnnotationDirectoryOffset(
664                directoryIn.getPosition(), annotationsDirectoryOut.getPosition());
665
666        int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt());
667        annotationsDirectoryOut.writeInt(classAnnotationsOffset);
668
669        int fieldsSize = directoryIn.readInt();
670        annotationsDirectoryOut.writeInt(fieldsSize);
671
672        int methodsSize = directoryIn.readInt();
673        annotationsDirectoryOut.writeInt(methodsSize);
674
675        int parameterListSize = directoryIn.readInt();
676        annotationsDirectoryOut.writeInt(parameterListSize);
677
678        for (int i = 0; i < fieldsSize; i++) {
679            // field index
680            annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt()));
681
682            // annotations offset
683            annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
684        }
685
686        for (int i = 0; i < methodsSize; i++) {
687            // method index
688            annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
689
690            // annotation set offset
691            annotationsDirectoryOut.writeInt(
692                    indexMap.adjustAnnotationSet(directoryIn.readInt()));
693        }
694
695        for (int i = 0; i < parameterListSize; i++) {
696            contentsOut.annotationSetRefLists.size++;
697            annotationSetRefListOut.assertFourByteAligned();
698
699            // method index
700            annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
701
702            // annotations offset
703            annotationsDirectoryOut.writeInt(annotationSetRefListOut.getPosition());
704            DexBuffer.Section refListIn = in.open(directoryIn.readInt());
705
706            // parameters
707            int parameterCount = refListIn.readInt();
708            annotationSetRefListOut.writeInt(parameterCount);
709            for (int p = 0; p < parameterCount; p++) {
710                annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt()));
711            }
712        }
713    }
714
715    /**
716     * Transform all annotations on a single type, member or parameter.
717     */
718    private void transformAnnotationSet(IndexMap indexMap, DexBuffer.Section setIn) {
719        contentsOut.annotationSets.size++;
720        annotationSetOut.assertFourByteAligned();
721        indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition());
722
723        int size = setIn.readInt();
724        annotationSetOut.writeInt(size);
725
726        for (int j = 0; j < size; j++) {
727            annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt()));
728        }
729    }
730
731    private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) {
732        contentsOut.classDatas.size++;
733
734        ClassData.Field[] staticFields = classData.getStaticFields();
735        ClassData.Field[] instanceFields = classData.getInstanceFields();
736        ClassData.Method[] directMethods = classData.getDirectMethods();
737        ClassData.Method[] virtualMethods = classData.getVirtualMethods();
738
739        classDataOut.writeUleb128(staticFields.length);
740        classDataOut.writeUleb128(instanceFields.length);
741        classDataOut.writeUleb128(directMethods.length);
742        classDataOut.writeUleb128(virtualMethods.length);
743
744        transformFields(indexMap, staticFields);
745        transformFields(indexMap, instanceFields);
746        transformMethods(in, indexMap, directMethods);
747        transformMethods(in, indexMap, virtualMethods);
748    }
749
750    private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
751        int lastOutFieldIndex = 0;
752        for (ClassData.Field field : fields) {
753            int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
754            classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex);
755            lastOutFieldIndex = outFieldIndex;
756            classDataOut.writeUleb128(field.getAccessFlags());
757        }
758    }
759
760    private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) {
761        int lastOutMethodIndex = 0;
762        for (ClassData.Method method : methods) {
763            int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
764            classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex);
765            lastOutMethodIndex = outMethodIndex;
766
767            classDataOut.writeUleb128(method.getAccessFlags());
768
769            if (method.getCodeOffset() == 0) {
770                classDataOut.writeUleb128(0);
771            } else {
772                codeOut.alignToFourBytes();
773                classDataOut.writeUleb128(codeOut.getPosition());
774                transformCode(in, in.readCode(method), indexMap);
775            }
776        }
777    }
778
779    private void transformCode(DexBuffer in, Code code, IndexMap indexMap) {
780        contentsOut.codes.size++;
781        codeOut.assertFourByteAligned();
782
783        codeOut.writeUnsignedShort(code.getRegistersSize());
784        codeOut.writeUnsignedShort(code.getInsSize());
785        codeOut.writeUnsignedShort(code.getOutsSize());
786
787        Code.Try[] tries = code.getTries();
788        codeOut.writeUnsignedShort(tries.length);
789
790        // TODO: retain debug info
791        // code.getDebugInfoOffset();
792        codeOut.writeInt(0);
793
794        short[] instructions = code.getInstructions();
795        InstructionTransformer transformer = (in == dexA)
796                ? aInstructionTransformer
797                : bInstructionTransformer;
798        short[] newInstructions = transformer.transform(instructions);
799        codeOut.writeInt(newInstructions.length);
800        codeOut.write(newInstructions);
801
802        if (tries.length > 0) {
803            if (newInstructions.length % 2 == 1) {
804                codeOut.writeShort((short) 0); // padding
805            }
806            for (Code.Try tryItem : tries) {
807                codeOut.writeInt(tryItem.getStartAddress());
808                codeOut.writeUnsignedShort(tryItem.getInstructionCount());
809                codeOut.writeUnsignedShort(tryItem.getHandlerOffset());
810            }
811            Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
812            codeOut.writeUleb128(catchHandlers.length);
813            for (Code.CatchHandler catchHandler : catchHandlers) {
814                transformEncodedCatchHandler(catchHandler, indexMap);
815            }
816        }
817    }
818
819    private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
820        int catchAllAddress = catchHandler.getCatchAllAddress();
821        int[] typeIndexes = catchHandler.getTypeIndexes();
822        int[] addresses = catchHandler.getAddresses();
823
824        if (catchAllAddress != -1) {
825            codeOut.writeSleb128(-typeIndexes.length);
826        } else {
827            codeOut.writeSleb128(typeIndexes.length);
828        }
829
830        for (int i = 0; i < typeIndexes.length; i++) {
831            codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i]));
832            codeOut.writeUleb128(addresses[i]);
833        }
834
835        if (catchAllAddress != -1) {
836            codeOut.writeUleb128(catchAllAddress);
837        }
838    }
839
840    private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) {
841        contentsOut.encodedArrays.size++;
842        indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut);
843    }
844
845    /**
846     * Byte counts for the sections written when creating a dex. Target sizes
847     * are defined in one of two ways:
848     * <ul>
849     * <li>By pessimistically guessing how large the union of dex files will be.
850     *     We're pessimistic because we can't predict the amount of duplication
851     *     between dex files, nor can we predict the length of ULEB-encoded
852     *     offsets or indices.
853     * <li>By exactly measuring an existing dex.
854     * </ul>
855     */
856    private static class WriterSizes implements Cloneable {
857        private int header = SizeOf.HEADER_ITEM;
858        private int idsDefs;
859        private int mapList;
860        private int typeList;
861        private int classData;
862        private int code;
863        private int stringData;
864        private int debugInfo;
865        private int encodedArray;
866        private int annotationsDirectory;
867        private int annotationsSet;
868        private int annotationsSetRefList;
869        private int annotation;
870
871        /**
872         * Compute sizes for merging a and b.
873         */
874        public WriterSizes(DexBuffer a, DexBuffer b) {
875            plus(a.getTableOfContents(), false);
876            plus(b.getTableOfContents(), false);
877        }
878
879        @Override public WriterSizes clone() {
880            try {
881                return (WriterSizes) super.clone();
882            } catch (CloneNotSupportedException e) {
883                throw new AssertionError();
884            }
885        }
886
887        public void plus(TableOfContents contents, boolean exact) {
888            idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM
889                    + contents.typeIds.size * SizeOf.TYPE_ID_ITEM
890                    + contents.protoIds.size * SizeOf.PROTO_ID_ITEM
891                    + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM
892                    + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM
893                    + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM;
894            mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM);
895            typeList += contents.typeLists.byteCount;
896            stringData += contents.stringDatas.byteCount;
897            debugInfo += contents.debugInfos.byteCount;
898            annotationsDirectory += contents.annotationsDirectories.byteCount;
899            annotationsSet += contents.annotationSets.byteCount;
900            annotationsSetRefList += contents.annotationSetRefLists.byteCount;
901
902            if (exact) {
903                code += contents.codes.byteCount;
904                classData += contents.classDatas.byteCount;
905                encodedArray += contents.encodedArrays.byteCount;
906                annotation += contents.annotations.byteCount;
907            } else {
908                // at most 1/4 of the bytes in a code section are uleb/sleb
909                code += (int) Math.ceil(contents.codes.byteCount * 1.25);
910                // at most 1/3 of the bytes in a class data section are uleb/sleb
911                classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34);
912                // all of the bytes in an encoding arrays section may be uleb/sleb
913                encodedArray += contents.encodedArrays.byteCount * 2;
914                // at most 1/3 of the bytes in an encoding arrays section are uleb/sleb
915                annotation += (int) Math.ceil(contents.annotations.byteCount * 1.34);
916            }
917        }
918
919        public void minusWaste(DexMerger dexMerger) {
920            header -= dexMerger.headerOut.remaining();
921            idsDefs -= dexMerger.idsDefsOut.remaining();
922            mapList -= dexMerger.mapListOut.remaining();
923            typeList -= dexMerger.typeListOut.remaining();
924            classData -= dexMerger.classDataOut.remaining();
925            code -= dexMerger.codeOut.remaining();
926            stringData -= dexMerger.stringDataOut.remaining();
927            debugInfo -= dexMerger.debugInfoOut.remaining();
928            encodedArray -= dexMerger.encodedArrayOut.remaining();
929            annotationsDirectory -= dexMerger.annotationsDirectoryOut.remaining();
930            annotationsSet -= dexMerger.annotationSetOut.remaining();
931            annotationsSetRefList -= dexMerger.annotationSetRefListOut.remaining();
932            annotation -= dexMerger.annotationOut.remaining();
933        }
934
935        public int size() {
936            return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo
937                    + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList
938                    + annotation;
939        }
940    }
941
942    public static void main(String[] args) throws IOException {
943        if (args.length != 3) {
944            printUsage();
945            return;
946        }
947
948        DexBuffer dexA = new DexBuffer(new File(args[1]));
949        DexBuffer dexB = new DexBuffer(new File(args[2]));
950        DexBuffer merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
951        merged.writeTo(new File(args[0]));
952    }
953
954    private static void printUsage() {
955        System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex>");
956        System.out.println();
957        System.out.println("If both a and b define the same classes, a's copy will be used.");
958    }
959}
960