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