DexMerger.java revision bf7dfeea94f21dd0e097cf5f786f9995722fd70d
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 = new WriterSizes(this);
194        int wastedByteCount = writerSizes.size() - compactedSizes.size();
195        if (wastedByteCount >  + compactWasteThreshold) {
196            DexMerger compacter = new DexMerger(
197                    dexOut, new DexBuffer(), CollisionPolicy.FAIL, compactedSizes);
198            result = compacter.mergeDexBuffers();
199            System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n",
200                    dexOut.getLength() / 1024f,
201                    result.getLength() / 1024f,
202                    wastedByteCount / 1024f);
203        }
204
205        long elapsed = System.nanoTime() - start;
206        System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B "
207                + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n",
208                dexA.getTableOfContents().classDefs.size,
209                dexA.getLength() / 1024f,
210                dexB.getTableOfContents().classDefs.size,
211                dexB.getLength() / 1024f,
212                result.getTableOfContents().classDefs.size,
213                result.getLength() / 1024f,
214                elapsed / 1000000000f);
215
216        return result;
217    }
218
219    /**
220     * Reads an IDs section of two dex files and writes an IDs section of a
221     * merged dex file. Populates maps from old to new indices in the process.
222     */
223    abstract class IdMerger<T extends Comparable<T>> {
224        private final DexBuffer.Section out;
225
226        protected IdMerger(DexBuffer.Section out) {
227            this.out = out;
228        }
229
230        /**
231         * Merges already-sorted sections, reading only two values into memory
232         * at a time.
233         */
234        public final void mergeSorted() {
235            TableOfContents.Section aSection = getSection(dexA.getTableOfContents());
236            TableOfContents.Section bSection = getSection(dexB.getTableOfContents());
237            getSection(contentsOut).off = out.getPosition();
238
239            DexBuffer.Section inA = aSection.exists() ? dexA.open(aSection.off) : null;
240            DexBuffer.Section inB = bSection.exists() ? dexB.open(bSection.off) : null;
241            int aOffset = -1;
242            int bOffset = -1;
243            int aIndex = 0;
244            int bIndex = 0;
245            int outCount = 0;
246            T a = null;
247            T b = null;
248
249            while (true) {
250                if (a == null && aIndex < aSection.size) {
251                    aOffset = inA.getPosition();
252                    a = read(inA, aIndexMap, aIndex);
253                }
254                if (b == null && bIndex < bSection.size) {
255                    bOffset = inB.getPosition();
256                    b = read(inB, bIndexMap, bIndex);
257                }
258
259                // Write the smaller of a and b. If they're equal, write only once
260                boolean advanceA;
261                boolean advanceB;
262                if (a != null && b != null) {
263                    int compare = a.compareTo(b);
264                    advanceA = compare <= 0;
265                    advanceB = compare >= 0;
266                } else {
267                    advanceA = (a != null);
268                    advanceB = (b != null);
269                }
270
271                T toWrite = null;
272                if (advanceA) {
273                    toWrite = a;
274                    updateIndex(aOffset, aIndexMap, aIndex++, outCount);
275                    a = null;
276                    aOffset = -1;
277                }
278                if (advanceB) {
279                    toWrite = b;
280                    updateIndex(bOffset, bIndexMap, bIndex++, outCount);
281                    b = null;
282                    bOffset = -1;
283                }
284                if (toWrite == null) {
285                    break; // advanceA == false && advanceB == false
286                }
287                write(toWrite);
288                outCount++;
289            }
290
291            getSection(contentsOut).size = outCount;
292        }
293
294        /**
295         * Merges unsorted sections by reading them completely into memory and
296         * sorting in memory.
297         */
298        public final void mergeUnsorted() {
299            getSection(contentsOut).off = out.getPosition();
300
301            List<UnsortedValue> all = new ArrayList<UnsortedValue>();
302            all.addAll(readUnsortedValues(dexA, aIndexMap));
303            all.addAll(readUnsortedValues(dexB, bIndexMap));
304            Collections.sort(all);
305
306            int outCount = 0;
307            for (int i = 0; i < all.size(); ) {
308                UnsortedValue e1 = all.get(i++);
309                updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1);
310
311                while (i < all.size() && e1.compareTo(all.get(i)) == 0) {
312                    UnsortedValue e2 = all.get(i++);
313                    updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1);
314                }
315
316                write(e1.value);
317                outCount++;
318            }
319
320            getSection(contentsOut).size = outCount;
321        }
322
323        private List<UnsortedValue> readUnsortedValues(DexBuffer source, IndexMap indexMap) {
324            TableOfContents.Section section = getSection(source.getTableOfContents());
325            if (!section.exists()) {
326                return Collections.emptyList();
327            }
328
329            List<UnsortedValue> result = new ArrayList<UnsortedValue>();
330            DexBuffer.Section in = source.open(section.off);
331            for (int i = 0; i < section.size; i++) {
332                int offset = in.getPosition();
333                T value = read(in, indexMap, 0);
334                result.add(new UnsortedValue(source, indexMap, value, i, offset));
335            }
336            return result;
337        }
338
339        abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
340        abstract T read(DexBuffer.Section in, IndexMap indexMap, int index);
341        abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex);
342        abstract void write(T value);
343
344        class UnsortedValue implements Comparable<UnsortedValue> {
345            final DexBuffer source;
346            final IndexMap indexMap;
347            final T value;
348            final int index;
349            final int offset;
350
351            UnsortedValue(DexBuffer source, IndexMap indexMap, T value, int index, int offset) {
352                this.source = source;
353                this.indexMap = indexMap;
354                this.value = value;
355                this.index = index;
356                this.offset = offset;
357            }
358
359            public int compareTo(UnsortedValue unsortedValue) {
360                return value.compareTo(unsortedValue.value);
361            }
362        }
363    }
364
365    private IndexMap getIndexMap(DexBuffer dexBuffer) {
366        if (dexBuffer == dexA) {
367            return aIndexMap;
368        } else if (dexBuffer == dexB) {
369            return bIndexMap;
370        } else {
371            throw new IllegalArgumentException();
372        }
373    }
374
375    private void mergeStringIds() {
376        new IdMerger<String>(idsDefsOut) {
377            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
378                return tableOfContents.stringIds;
379            }
380
381            @Override String read(DexBuffer.Section in, IndexMap indexMap, int index) {
382                return in.readString();
383            }
384
385            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
386                indexMap.stringIds[oldIndex] = newIndex;
387            }
388
389            @Override void write(String value) {
390                contentsOut.stringDatas.size++;
391                idsDefsOut.writeInt(stringDataOut.getPosition());
392                stringDataOut.writeStringData(value);
393            }
394        }.mergeSorted();
395    }
396
397    private void mergeTypeIds() {
398        new IdMerger<Integer>(idsDefsOut) {
399            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
400                return tableOfContents.typeIds;
401            }
402
403            @Override Integer read(DexBuffer.Section in, IndexMap indexMap, int index) {
404                int stringIndex = in.readInt();
405                return indexMap.adjustString(stringIndex);
406            }
407
408            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
409                indexMap.typeIds[oldIndex] = (short) newIndex;
410            }
411
412            @Override void write(Integer value) {
413                idsDefsOut.writeInt(value);
414            }
415        }.mergeSorted();
416    }
417
418    private void mergeTypeLists() {
419        new IdMerger<TypeList>(typeListOut) {
420            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
421                return tableOfContents.typeLists;
422            }
423
424            @Override TypeList read(DexBuffer.Section in, IndexMap indexMap, int index) {
425                return indexMap.adjustTypeList(in.readTypeList());
426            }
427
428            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
429                indexMap.putTypeListOffset(offset, typeListOut.getPosition());
430            }
431
432            @Override void write(TypeList value) {
433                typeListOut.writeTypeList(value);
434            }
435        }.mergeUnsorted();
436    }
437
438    private void mergeProtoIds() {
439        new IdMerger<ProtoId>(idsDefsOut) {
440            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
441                return tableOfContents.protoIds;
442            }
443
444            @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index) {
445                return indexMap.adjust(in.readProtoId());
446            }
447
448            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
449                indexMap.protoIds[oldIndex] = (short) newIndex;
450            }
451
452            @Override void write(ProtoId value) {
453                value.writeTo(idsDefsOut);
454            }
455        }.mergeSorted();
456    }
457
458    private void mergeFieldIds() {
459        new IdMerger<FieldId>(idsDefsOut) {
460            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
461                return tableOfContents.fieldIds;
462            }
463
464            @Override FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) {
465                return indexMap.adjust(in.readFieldId());
466            }
467
468            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
469                indexMap.fieldIds[oldIndex] = (short) newIndex;
470            }
471
472            @Override void write(FieldId value) {
473                value.writeTo(idsDefsOut);
474            }
475        }.mergeSorted();
476    }
477
478    private void mergeMethodIds() {
479        new IdMerger<MethodId>(idsDefsOut) {
480            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
481                return tableOfContents.methodIds;
482            }
483
484            @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index) {
485                return indexMap.adjust(in.readMethodId());
486            }
487
488            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
489                indexMap.methodIds[oldIndex] = (short) newIndex;
490            }
491
492            @Override void write(MethodId methodId) {
493                methodId.writeTo(idsDefsOut);
494            }
495        }.mergeSorted();
496    }
497
498    private void mergeAnnotations() {
499        new IdMerger<Annotation>(annotationOut) {
500            @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
501                return tableOfContents.annotations;
502            }
503
504            @Override Annotation read(DexBuffer.Section in, IndexMap indexMap, int index) {
505                return indexMap.adjust(in.readAnnotation());
506            }
507
508            @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
509                indexMap.putAnnotationOffset(offset, annotationOut.getPosition());
510            }
511
512            @Override void write(Annotation value) {
513                value.writeTo(annotationOut);
514            }
515        }.mergeUnsorted();
516    }
517
518    private void mergeClassDefs() {
519        SortableType[] types = getSortedTypes();
520        contentsOut.classDefs.off = idsDefsOut.getPosition();
521        contentsOut.classDefs.size = types.length;
522
523        for (SortableType type : types) {
524            DexBuffer in = type.getBuffer();
525            IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap;
526            transformClassDef(in, type.getClassDef(), indexMap);
527        }
528    }
529
530    /**
531     * Returns the union of classes from both files, sorted in order such that
532     * a class is always preceded by its supertype and implemented interfaces.
533     */
534    private SortableType[] getSortedTypes() {
535        // size is pessimistic; doesn't include arrays
536        SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size];
537        readSortableTypes(sortableTypes, dexA, aIndexMap);
538        readSortableTypes(sortableTypes, dexB, bIndexMap);
539
540        /*
541         * Populate the depths of each sortable type. This makes D iterations
542         * through all N types, where 'D' is the depth of the deepest type. For
543         * example, the deepest class in libcore is Xalan's KeyIterator, which
544         * is 11 types deep.
545         */
546        while (true) {
547            boolean allDone = true;
548            for (SortableType sortableType : sortableTypes) {
549                if (sortableType != null && !sortableType.isDepthAssigned()) {
550                    allDone &= sortableType.tryAssignDepth(sortableTypes);
551                }
552            }
553            if (allDone) {
554                break;
555            }
556        }
557
558        // Now that all types have depth information, the result can be sorted
559        Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER);
560
561        // Strip nulls from the end
562        int firstNull = Arrays.asList(sortableTypes).indexOf(null);
563        return firstNull != -1
564                ? Arrays.copyOfRange(sortableTypes, 0, firstNull)
565                : sortableTypes;
566    }
567
568    /**
569     * Reads just enough data on each class so that we can sort it and then find
570     * it later.
571     */
572    private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer,
573            IndexMap indexMap) {
574        for (ClassDef classDef : buffer.classDefs()) {
575            SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef));
576            int t = sortableType.getTypeIndex();
577            if (sortableTypes[t] == null) {
578                sortableTypes[t] = sortableType;
579            } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) {
580                throw new DexException("Multiple dex files define "
581                        + buffer.typeNames().get(classDef.getTypeIndex()));
582            }
583        }
584    }
585
586    /**
587     * Copy annotation sets from each input to the output.
588     *
589     * TODO: this may write multiple copies of the same annotation set.
590     * We should shrink the output by merging rather than unioning
591     */
592    private void unionAnnotationSetsAndDirectories() {
593        transformAnnotationSets(dexA, aIndexMap);
594        transformAnnotationSets(dexB, bIndexMap);
595        transformAnnotationDirectories(dexA, aIndexMap);
596        transformAnnotationDirectories(dexB, bIndexMap);
597        transformStaticValues(dexA, aIndexMap);
598        transformStaticValues(dexB, bIndexMap);
599    }
600
601    private void transformAnnotationSets(DexBuffer in, IndexMap indexMap) {
602        TableOfContents.Section section = in.getTableOfContents().annotationSets;
603        if (section.exists()) {
604            DexBuffer.Section setIn = in.open(section.off);
605            for (int i = 0; i < section.size; i++) {
606                transformAnnotationSet(indexMap, setIn);
607            }
608        }
609    }
610
611    private void transformAnnotationDirectories(DexBuffer in, IndexMap indexMap) {
612        TableOfContents.Section section = in.getTableOfContents().annotationsDirectories;
613        if (section.exists()) {
614            DexBuffer.Section directoryIn = in.open(section.off);
615            for (int i = 0; i < section.size; i++) {
616                transformAnnotationDirectory(in, directoryIn, indexMap);
617            }
618        }
619    }
620
621    private void transformStaticValues(DexBuffer in, IndexMap indexMap) {
622        TableOfContents.Section section = in.getTableOfContents().encodedArrays;
623        if (section.exists()) {
624            DexBuffer.Section staticValuesIn = in.open(section.off);
625            for (int i = 0; i < section.size; i++) {
626                transformStaticValues(staticValuesIn, indexMap);
627            }
628        }
629    }
630
631    /**
632     * Reads a class_def_item beginning at {@code in} and writes the index and
633     * data.
634     */
635    private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) {
636        idsDefsOut.assertFourByteAligned();
637        idsDefsOut.writeInt(classDef.getTypeIndex());
638        idsDefsOut.writeInt(classDef.getAccessFlags());
639        idsDefsOut.writeInt(classDef.getSupertypeIndex());
640        idsDefsOut.writeInt(classDef.getInterfacesOffset());
641
642        int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex());
643        idsDefsOut.writeInt(sourceFileIndex);
644
645        int annotationsOff = classDef.getAnnotationsOffset();
646        idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff));
647
648        int classDataOff = classDef.getClassDataOffset();
649        if (classDataOff == 0) {
650            idsDefsOut.writeInt(0);
651        } else {
652            idsDefsOut.writeInt(classDataOut.getPosition());
653            ClassData classData = in.readClassData(classDef);
654            transformClassData(in, classData, indexMap);
655        }
656
657        int staticValuesOff = classDef.getStaticValuesOffset();
658        idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff));
659    }
660
661    /**
662     * Transform all annotations on a class.
663     */
664    private void transformAnnotationDirectory(
665            DexBuffer in, DexBuffer.Section directoryIn, IndexMap indexMap) {
666        contentsOut.annotationsDirectories.size++;
667        annotationsDirectoryOut.assertFourByteAligned();
668        indexMap.putAnnotationDirectoryOffset(
669                directoryIn.getPosition(), annotationsDirectoryOut.getPosition());
670
671        int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt());
672        annotationsDirectoryOut.writeInt(classAnnotationsOffset);
673
674        int fieldsSize = directoryIn.readInt();
675        annotationsDirectoryOut.writeInt(fieldsSize);
676
677        int methodsSize = directoryIn.readInt();
678        annotationsDirectoryOut.writeInt(methodsSize);
679
680        int parameterListSize = directoryIn.readInt();
681        annotationsDirectoryOut.writeInt(parameterListSize);
682
683        for (int i = 0; i < fieldsSize; i++) {
684            // field index
685            annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt()));
686
687            // annotations offset
688            annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
689        }
690
691        for (int i = 0; i < methodsSize; i++) {
692            // method index
693            annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
694
695            // annotation set offset
696            annotationsDirectoryOut.writeInt(
697                    indexMap.adjustAnnotationSet(directoryIn.readInt()));
698        }
699
700        for (int i = 0; i < parameterListSize; i++) {
701            contentsOut.annotationSetRefLists.size++;
702            annotationSetRefListOut.assertFourByteAligned();
703
704            // method index
705            annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
706
707            // annotations offset
708            annotationsDirectoryOut.writeInt(annotationSetRefListOut.getPosition());
709            DexBuffer.Section refListIn = in.open(directoryIn.readInt());
710
711            // parameters
712            int parameterCount = refListIn.readInt();
713            annotationSetRefListOut.writeInt(parameterCount);
714            for (int p = 0; p < parameterCount; p++) {
715                annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt()));
716            }
717        }
718    }
719
720    /**
721     * Transform all annotations on a single type, member or parameter.
722     */
723    private void transformAnnotationSet(IndexMap indexMap, DexBuffer.Section setIn) {
724        contentsOut.annotationSets.size++;
725        annotationSetOut.assertFourByteAligned();
726        indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition());
727
728        int size = setIn.readInt();
729        annotationSetOut.writeInt(size);
730
731        for (int j = 0; j < size; j++) {
732            annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt()));
733        }
734    }
735
736    private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) {
737        contentsOut.classDatas.size++;
738
739        ClassData.Field[] staticFields = classData.getStaticFields();
740        ClassData.Field[] instanceFields = classData.getInstanceFields();
741        ClassData.Method[] directMethods = classData.getDirectMethods();
742        ClassData.Method[] virtualMethods = classData.getVirtualMethods();
743
744        classDataOut.writeUleb128(staticFields.length);
745        classDataOut.writeUleb128(instanceFields.length);
746        classDataOut.writeUleb128(directMethods.length);
747        classDataOut.writeUleb128(virtualMethods.length);
748
749        transformFields(indexMap, staticFields);
750        transformFields(indexMap, instanceFields);
751        transformMethods(in, indexMap, directMethods);
752        transformMethods(in, indexMap, virtualMethods);
753    }
754
755    private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
756        int lastOutFieldIndex = 0;
757        for (ClassData.Field field : fields) {
758            int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
759            classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex);
760            lastOutFieldIndex = outFieldIndex;
761            classDataOut.writeUleb128(field.getAccessFlags());
762        }
763    }
764
765    private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) {
766        int lastOutMethodIndex = 0;
767        for (ClassData.Method method : methods) {
768            int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
769            classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex);
770            lastOutMethodIndex = outMethodIndex;
771
772            classDataOut.writeUleb128(method.getAccessFlags());
773
774            if (method.getCodeOffset() == 0) {
775                classDataOut.writeUleb128(0);
776            } else {
777                codeOut.alignToFourBytes();
778                classDataOut.writeUleb128(codeOut.getPosition());
779                transformCode(in, in.readCode(method), indexMap);
780            }
781        }
782    }
783
784    private void transformCode(DexBuffer in, Code code, IndexMap indexMap) {
785        contentsOut.codes.size++;
786        codeOut.assertFourByteAligned();
787
788        codeOut.writeUnsignedShort(code.getRegistersSize());
789        codeOut.writeUnsignedShort(code.getInsSize());
790        codeOut.writeUnsignedShort(code.getOutsSize());
791
792        Code.Try[] tries = code.getTries();
793        codeOut.writeUnsignedShort(tries.length);
794
795        int debugInfoOffset = code.getDebugInfoOffset();
796        if (debugInfoOffset != 0) {
797            codeOut.writeInt(debugInfoOut.getPosition());
798            transformDebugInfoItem(in.open(debugInfoOffset), indexMap);
799        } else {
800            codeOut.writeInt(0);
801        }
802
803        short[] instructions = code.getInstructions();
804        InstructionTransformer transformer = (in == dexA)
805                ? aInstructionTransformer
806                : bInstructionTransformer;
807        short[] newInstructions = transformer.transform(instructions);
808        codeOut.writeInt(newInstructions.length);
809        codeOut.write(newInstructions);
810
811        if (tries.length > 0) {
812            if (newInstructions.length % 2 == 1) {
813                codeOut.writeShort((short) 0); // padding
814            }
815            for (Code.Try tryItem : tries) {
816                codeOut.writeInt(tryItem.getStartAddress());
817                codeOut.writeUnsignedShort(tryItem.getInstructionCount());
818                codeOut.writeUnsignedShort(tryItem.getHandlerOffset());
819            }
820            Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
821            codeOut.writeUleb128(catchHandlers.length);
822            for (Code.CatchHandler catchHandler : catchHandlers) {
823                transformEncodedCatchHandler(catchHandler, indexMap);
824            }
825        }
826    }
827
828    private static final byte DBG_END_SEQUENCE = 0x00;
829    private static final byte DBG_ADVANCE_PC = 0x01;
830    private static final byte DBG_ADVANCE_LINE = 0x02;
831    private static final byte DBG_START_LOCAL = 0x03;
832    private static final byte DBG_START_LOCAL_EXTENDED = 0x04;
833    private static final byte DBG_END_LOCAL = 0x05;
834    private static final byte DBG_RESTART_LOCAL = 0x06;
835    private static final byte DBG_SET_PROLOGUE_END = 0x07;
836    private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08;
837    private static final byte DBG_SET_FILE = 0x09;
838
839    private void transformDebugInfoItem(DexBuffer.Section in, IndexMap indexMap) {
840        contentsOut.debugInfos.size++;
841        int lineStart = in.readUleb128();
842        debugInfoOut.writeUleb128(lineStart);
843
844        int parametersSize = in.readUleb128();
845        debugInfoOut.writeUleb128(parametersSize);
846
847        for (int p = 0; p < parametersSize; p++) {
848            int parameterName = in.readUleb128p1();
849            debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName));
850        }
851
852        int addrDiff;    // uleb128   address delta.
853        int lineDiff;    // sleb128   line delta.
854        int registerNum; // uleb128   register number.
855        int nameIndex;   // uleb128p1 string index.    Needs indexMap adjustment.
856        int typeIndex;   // uleb128p1 type index.      Needs indexMap adjustment.
857        int sigIndex;    // uleb128p1 string index.    Needs indexMap adjustment.
858
859        while (true) {
860            int opcode = in.readByte();
861            debugInfoOut.writeByte(opcode);
862
863            switch (opcode) {
864            case DBG_END_SEQUENCE:
865                return;
866
867            case DBG_ADVANCE_PC:
868                addrDiff = in.readUleb128();
869                debugInfoOut.writeUleb128(addrDiff);
870                break;
871
872            case DBG_ADVANCE_LINE:
873                lineDiff = in.readSleb128();
874                debugInfoOut.writeSleb128(lineDiff);
875                break;
876
877            case DBG_START_LOCAL:
878            case DBG_START_LOCAL_EXTENDED:
879                registerNum = in.readUleb128();
880                debugInfoOut.writeUleb128(registerNum);
881                nameIndex = in.readUleb128p1();
882                debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
883                typeIndex = in.readUleb128p1();
884                debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex));
885                if (opcode == DBG_START_LOCAL_EXTENDED) {
886                    sigIndex = in.readUleb128p1();
887                    debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex));
888                }
889                break;
890
891            case DBG_END_LOCAL:
892            case DBG_RESTART_LOCAL:
893                registerNum = in.readUleb128();
894                debugInfoOut.writeUleb128(registerNum);
895                break;
896
897            case DBG_SET_FILE:
898                nameIndex = in.readUleb128p1();
899                debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
900                break;
901
902            case DBG_SET_PROLOGUE_END:
903            case DBG_SET_EPILOGUE_BEGIN:
904            default:
905                break;
906            }
907        }
908    }
909
910    private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
911        int catchAllAddress = catchHandler.getCatchAllAddress();
912        int[] typeIndexes = catchHandler.getTypeIndexes();
913        int[] addresses = catchHandler.getAddresses();
914
915        if (catchAllAddress != -1) {
916            codeOut.writeSleb128(-typeIndexes.length);
917        } else {
918            codeOut.writeSleb128(typeIndexes.length);
919        }
920
921        for (int i = 0; i < typeIndexes.length; i++) {
922            codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i]));
923            codeOut.writeUleb128(addresses[i]);
924        }
925
926        if (catchAllAddress != -1) {
927            codeOut.writeUleb128(catchAllAddress);
928        }
929    }
930
931    private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) {
932        contentsOut.encodedArrays.size++;
933        indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition());
934        indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut);
935    }
936
937    /**
938     * Byte counts for the sections written when creating a dex. Target sizes
939     * are defined in one of two ways:
940     * <ul>
941     * <li>By pessimistically guessing how large the union of dex files will be.
942     *     We're pessimistic because we can't predict the amount of duplication
943     *     between dex files, nor can we predict the length of ULEB-encoded
944     *     offsets or indices.
945     * <li>By exactly measuring an existing dex.
946     * </ul>
947     */
948    private static class WriterSizes {
949        private int header = SizeOf.HEADER_ITEM;
950        private int idsDefs;
951        private int mapList;
952        private int typeList;
953        private int classData;
954        private int code;
955        private int stringData;
956        private int debugInfo;
957        private int encodedArray;
958        private int annotationsDirectory;
959        private int annotationsSet;
960        private int annotationsSetRefList;
961        private int annotation;
962
963        /**
964         * Compute sizes for merging a and b.
965         */
966        public WriterSizes(DexBuffer a, DexBuffer b) {
967            plus(a.getTableOfContents(), false);
968            plus(b.getTableOfContents(), false);
969        }
970
971        public WriterSizes(DexMerger dexMerger) {
972            header = dexMerger.headerOut.used();
973            idsDefs = dexMerger.idsDefsOut.used();
974            mapList = dexMerger.mapListOut.used();
975            typeList = dexMerger.typeListOut.used();
976            classData = dexMerger.classDataOut.used();
977            code = dexMerger.codeOut.used();
978            stringData = dexMerger.stringDataOut.used();
979            debugInfo = dexMerger.debugInfoOut.used();
980            encodedArray = dexMerger.encodedArrayOut.used();
981            annotationsDirectory = dexMerger.annotationsDirectoryOut.used();
982            annotationsSet = dexMerger.annotationSetOut.used();
983            annotationsSetRefList = dexMerger.annotationSetRefListOut.used();
984            annotation = dexMerger.annotationOut.used();
985        }
986
987        public void plus(TableOfContents contents, boolean exact) {
988            idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM
989                    + contents.typeIds.size * SizeOf.TYPE_ID_ITEM
990                    + contents.protoIds.size * SizeOf.PROTO_ID_ITEM
991                    + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM
992                    + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM
993                    + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM;
994            mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM);
995            typeList += contents.typeLists.byteCount;
996            stringData += contents.stringDatas.byteCount;
997            annotationsDirectory += contents.annotationsDirectories.byteCount;
998            annotationsSet += contents.annotationSets.byteCount;
999            annotationsSetRefList += contents.annotationSetRefLists.byteCount;
1000
1001            if (exact) {
1002                code += contents.codes.byteCount;
1003                classData += contents.classDatas.byteCount;
1004                encodedArray += contents.encodedArrays.byteCount;
1005                annotation += contents.annotations.byteCount;
1006                debugInfo += contents.debugInfos.byteCount;
1007            } else {
1008                // at most 1/4 of the bytes in a code section are uleb/sleb
1009                code += (int) Math.ceil(contents.codes.byteCount * 1.25);
1010                // at most 1/3 of the bytes in a class data section are uleb/sleb
1011                classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34);
1012                // all of the bytes in an encoding arrays section may be uleb/sleb
1013                encodedArray += contents.encodedArrays.byteCount * 2;
1014                // all of the bytes in an annotations section may be uleb/sleb
1015                annotation += (int) Math.ceil(contents.annotations.byteCount * 2);
1016                // all of the bytes in a debug info section may be uleb/sleb
1017                debugInfo += contents.debugInfos.byteCount * 2;
1018            }
1019
1020            typeList = DexBuffer.fourByteAlign(typeList);
1021            code = DexBuffer.fourByteAlign(code);
1022        }
1023
1024        public int size() {
1025            return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo
1026                    + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList
1027                    + annotation;
1028        }
1029    }
1030
1031    public static void main(String[] args) throws IOException {
1032        if (args.length != 3) {
1033            printUsage();
1034            return;
1035        }
1036
1037        DexBuffer dexA = new DexBuffer(new File(args[1]));
1038        DexBuffer dexB = new DexBuffer(new File(args[2]));
1039        DexBuffer merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
1040        merged.writeTo(new File(args[0]));
1041    }
1042
1043    private static void printUsage() {
1044        System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex>");
1045        System.out.println();
1046        System.out.println("If both a and b define the same classes, a's copy will be used.");
1047    }
1048}
1049