1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer;
33
34import com.google.common.collect.Iterables;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
37import com.google.common.collect.Ordering;
38import org.jf.dexlib2.AccessFlags;
39import org.jf.dexlib2.Opcode;
40import org.jf.dexlib2.Opcodes;
41import org.jf.dexlib2.ReferenceType;
42import org.jf.dexlib2.base.BaseAnnotation;
43import org.jf.dexlib2.base.BaseAnnotationElement;
44import org.jf.dexlib2.builder.MutableMethodImplementation;
45import org.jf.dexlib2.builder.instruction.BuilderInstruction31c;
46import org.jf.dexlib2.dexbacked.raw.*;
47import org.jf.dexlib2.iface.Annotation;
48import org.jf.dexlib2.iface.ExceptionHandler;
49import org.jf.dexlib2.iface.TryBlock;
50import org.jf.dexlib2.iface.debug.DebugItem;
51import org.jf.dexlib2.iface.debug.LineNumber;
52import org.jf.dexlib2.iface.instruction.Instruction;
53import org.jf.dexlib2.iface.instruction.OneRegisterInstruction;
54import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
55import org.jf.dexlib2.iface.instruction.formats.*;
56import org.jf.dexlib2.iface.reference.FieldReference;
57import org.jf.dexlib2.iface.reference.MethodProtoReference;
58import org.jf.dexlib2.iface.reference.MethodReference;
59import org.jf.dexlib2.iface.reference.StringReference;
60import org.jf.dexlib2.iface.reference.TypeReference;
61import org.jf.dexlib2.util.InstructionUtil;
62import org.jf.dexlib2.util.MethodUtil;
63import org.jf.dexlib2.util.ReferenceUtil;
64import org.jf.dexlib2.writer.io.DeferredOutputStream;
65import org.jf.dexlib2.writer.io.DeferredOutputStreamFactory;
66import org.jf.dexlib2.writer.io.DexDataStore;
67import org.jf.dexlib2.writer.io.MemoryDeferredOutputStream;
68import org.jf.dexlib2.writer.util.TryListBuilder;
69import org.jf.util.CollectionUtils;
70import org.jf.util.ExceptionWithContext;
71
72import javax.annotation.Nonnull;
73import javax.annotation.Nullable;
74import java.io.ByteArrayOutputStream;
75import java.io.IOException;
76import java.io.InputStream;
77import java.io.OutputStream;
78import java.nio.ByteBuffer;
79import java.nio.ByteOrder;
80import java.security.MessageDigest;
81import java.security.NoSuchAlgorithmException;
82import java.util.*;
83import java.util.Map.Entry;
84import java.util.zip.Adler32;
85
86public abstract class DexWriter<
87        StringKey extends CharSequence, StringRef extends StringReference, TypeKey extends CharSequence,
88        TypeRef extends TypeReference, ProtoRefKey extends MethodProtoReference,
89        FieldRefKey extends FieldReference, MethodRefKey extends MethodReference,
90        ClassKey extends Comparable<? super ClassKey>,
91        AnnotationKey extends Annotation, AnnotationSetKey,
92        TypeListKey,
93        FieldKey, MethodKey,
94        EncodedValue,
95        AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement> {
96    public static final int NO_INDEX = -1;
97    public static final int NO_OFFSET = 0;
98
99    protected final Opcodes opcodes;
100
101    protected int stringIndexSectionOffset = NO_OFFSET;
102    protected int typeSectionOffset = NO_OFFSET;
103    protected int protoSectionOffset = NO_OFFSET;
104    protected int fieldSectionOffset = NO_OFFSET;
105    protected int methodSectionOffset = NO_OFFSET;
106    protected int classIndexSectionOffset = NO_OFFSET;
107
108    protected int stringDataSectionOffset = NO_OFFSET;
109    protected int classDataSectionOffset = NO_OFFSET;
110    protected int typeListSectionOffset = NO_OFFSET;
111    protected int encodedArraySectionOffset = NO_OFFSET;
112    protected int annotationSectionOffset = NO_OFFSET;
113    protected int annotationSetSectionOffset = NO_OFFSET;
114    protected int annotationSetRefSectionOffset = NO_OFFSET;
115    protected int annotationDirectorySectionOffset = NO_OFFSET;
116    protected int debugSectionOffset = NO_OFFSET;
117    protected int codeSectionOffset = NO_OFFSET;
118    protected int mapSectionOffset = NO_OFFSET;
119
120    protected int numEncodedArrayItems = 0;
121    protected int numAnnotationSetRefItems = 0;
122    protected int numAnnotationDirectoryItems = 0;
123    protected int numDebugInfoItems = 0;
124    protected int numCodeItemItems = 0;
125    protected int numClassDataItems = 0;
126
127    protected final StringSection<StringKey, StringRef> stringSection;
128    protected final TypeSection<StringKey, TypeKey, TypeRef> typeSection;
129    protected final ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey> protoSection;
130    protected final FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection;
131    protected final MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey> methodSection;
132    protected final ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
133            EncodedValue> classSection;
134
135    protected final TypeListSection<TypeKey, TypeListKey> typeListSection;
136    protected final AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement, EncodedValue> annotationSection;
137    protected final AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection;
138
139    protected DexWriter(Opcodes opcodes,
140                        StringSection<StringKey, StringRef> stringSection,
141                        TypeSection<StringKey, TypeKey, TypeRef> typeSection,
142                        ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey> protoSection,
143                        FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection,
144                        MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey> methodSection,
145                        ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
146                                EncodedValue> classSection,
147                        TypeListSection<TypeKey, TypeListKey> typeListSection,
148                        AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement,
149                                EncodedValue> annotationSection,
150                        AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection) {
151        this.opcodes = opcodes;
152
153        this.stringSection = stringSection;
154        this.typeSection = typeSection;
155        this.protoSection = protoSection;
156        this.fieldSection = fieldSection;
157        this.methodSection = methodSection;
158        this.classSection = classSection;
159        this.typeListSection = typeListSection;
160        this.annotationSection = annotationSection;
161        this.annotationSetSection = annotationSetSection;
162    }
163
164    protected abstract void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
165                                              @Nonnull EncodedValue encodedValue) throws IOException;
166
167    private static Comparator<Map.Entry> toStringKeyComparator =
168            new Comparator<Map.Entry>() {
169                @Override public int compare(Entry o1, Entry o2) {
170                    return o1.getKey().toString().compareTo(o2.getKey().toString());
171                }
172            };
173
174    private static <T extends Comparable<? super T>> Comparator<Map.Entry<? extends T, ?>> comparableKeyComparator() {
175        return new Comparator<Entry<? extends T, ?>>() {
176            @Override public int compare(Entry<? extends T, ?> o1, Entry<? extends T, ?> o2) {
177                return o1.getKey().compareTo(o2.getKey());
178            }
179        };
180    }
181
182    protected class InternalEncodedValueWriter extends EncodedValueWriter<StringKey, TypeKey, FieldRefKey, MethodRefKey,
183            AnnotationElement, EncodedValue> {
184        private InternalEncodedValueWriter(@Nonnull DexDataWriter writer) {
185            super(writer, stringSection, typeSection, fieldSection, methodSection, annotationSection);
186        }
187
188        @Override protected void writeEncodedValue(@Nonnull EncodedValue encodedValue) throws IOException {
189            DexWriter.this.writeEncodedValue(this, encodedValue);
190        }
191    }
192
193    private int getDataSectionOffset() {
194        return HeaderItem.ITEM_SIZE +
195                stringSection.getItems().size() * StringIdItem.ITEM_SIZE +
196                typeSection.getItems().size() * TypeIdItem.ITEM_SIZE +
197                protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE +
198                fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE +
199                methodSection.getItems().size() * MethodIdItem.ITEM_SIZE +
200                classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
201    }
202
203    @Nonnull
204    public List<String> getMethodReferences() {
205        List<String> methodReferences = Lists.newArrayList();
206        for (Entry<? extends MethodRefKey, Integer> methodReference: methodSection.getItems()) {
207            methodReferences.add(ReferenceUtil.getMethodDescriptor(methodReference.getKey()));
208        }
209        return methodReferences;
210    }
211
212    @Nonnull
213    public List<String> getFieldReferences() {
214        List<String> fieldReferences = Lists.newArrayList();
215        for (Entry<? extends FieldRefKey, Integer> fieldReference: fieldSection.getItems()) {
216            fieldReferences.add(ReferenceUtil.getFieldDescriptor(fieldReference.getKey()));
217        }
218        return fieldReferences;
219    }
220
221    @Nonnull
222    public List<String> getTypeReferences() {
223        List<String> classReferences = Lists.newArrayList();
224        for (Entry<? extends TypeKey, Integer> typeReference: typeSection.getItems()) {
225            classReferences.add(typeReference.getKey().toString());
226        }
227        return classReferences;
228    }
229
230    public void writeTo(@Nonnull DexDataStore dest) throws IOException {
231        this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
232    }
233
234    public void writeTo(@Nonnull DexDataStore dest,
235                        @Nonnull DeferredOutputStreamFactory tempFactory) throws IOException {
236        try {
237            int dataSectionOffset = getDataSectionOffset();
238            DexDataWriter headerWriter = outputAt(dest, 0);
239            DexDataWriter indexWriter = outputAt(dest, HeaderItem.ITEM_SIZE);
240            DexDataWriter offsetWriter = outputAt(dest, dataSectionOffset);
241            try {
242                writeStrings(indexWriter, offsetWriter);
243                writeTypes(indexWriter);
244                writeTypeLists(offsetWriter);
245                writeProtos(indexWriter);
246                writeFields(indexWriter);
247                writeMethods(indexWriter);
248                writeEncodedArrays(offsetWriter);
249                writeAnnotations(offsetWriter);
250                writeAnnotationSets(offsetWriter);
251                writeAnnotationSetRefs(offsetWriter);
252                writeAnnotationDirectories(offsetWriter);
253                writeDebugAndCodeItems(offsetWriter, tempFactory.makeDeferredOutputStream());
254                writeClasses(indexWriter, offsetWriter);
255                writeMapItem(offsetWriter);
256                writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition());
257            } finally {
258                headerWriter.close();
259                indexWriter.close();
260                offsetWriter.close();
261            }
262            updateSignature(dest);
263            updateChecksum(dest);
264        } finally {
265            dest.close();
266        }
267    }
268
269    private void updateSignature(@Nonnull DexDataStore dataStore) throws IOException {
270        MessageDigest md;
271        try {
272            md = MessageDigest.getInstance("SHA-1");
273        } catch (NoSuchAlgorithmException ex) {
274            throw new RuntimeException(ex);
275        }
276
277        byte[] buffer = new byte[4 * 1024];
278        InputStream input = dataStore.readAt(HeaderItem.SIGNATURE_DATA_START_OFFSET);
279        int bytesRead = input.read(buffer);
280        while (bytesRead >= 0) {
281            md.update(buffer, 0, bytesRead);
282            bytesRead = input.read(buffer);
283        }
284
285        byte[] signature = md.digest();
286        if (signature.length != HeaderItem.SIGNATURE_SIZE) {
287            throw new RuntimeException("unexpected digest write: " + signature.length + " bytes");
288        }
289
290        // write signature
291        OutputStream output = dataStore.outputAt(HeaderItem.SIGNATURE_OFFSET);
292        output.write(signature);
293        output.close();
294    }
295
296    private void updateChecksum(@Nonnull DexDataStore dataStore) throws IOException {
297        Adler32 a32 = new Adler32();
298
299        byte[] buffer = new byte[4 * 1024];
300        InputStream input = dataStore.readAt(HeaderItem.CHECKSUM_DATA_START_OFFSET);
301        int bytesRead = input.read(buffer);
302        while (bytesRead >= 0) {
303            a32.update(buffer, 0, bytesRead);
304            bytesRead = input.read(buffer);
305        }
306
307        // write checksum, utilizing logic in DexWriter to write the integer value properly
308        OutputStream output = dataStore.outputAt(HeaderItem.CHECKSUM_OFFSET);
309        DexDataWriter.writeInt(output, (int)a32.getValue());
310        output.close();
311    }
312
313    private static DexDataWriter outputAt(DexDataStore dataStore, int filePosition) throws IOException {
314        return new DexDataWriter(dataStore.outputAt(filePosition), filePosition);
315    }
316
317    private void writeStrings(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException {
318        stringIndexSectionOffset = indexWriter.getPosition();
319        stringDataSectionOffset = offsetWriter.getPosition();
320        int index = 0;
321        List<Entry<? extends StringKey, Integer>> stringEntries = Lists.newArrayList(stringSection.getItems());
322        Collections.sort(stringEntries, toStringKeyComparator);
323
324        for (Map.Entry<? extends StringKey, Integer>  entry: stringEntries) {
325            entry.setValue(index++);
326            indexWriter.writeInt(offsetWriter.getPosition());
327            String stringValue = entry.getKey().toString();
328            offsetWriter.writeUleb128(stringValue.length());
329            offsetWriter.writeString(stringValue);
330            offsetWriter.write(0);
331        }
332    }
333
334    private void writeTypes(@Nonnull DexDataWriter writer) throws IOException {
335        typeSectionOffset = writer.getPosition();
336        int index = 0;
337
338        List<Map.Entry<? extends TypeKey, Integer>> typeEntries = Lists.newArrayList(typeSection.getItems());
339        Collections.sort(typeEntries, toStringKeyComparator);
340
341        for (Map.Entry<? extends TypeKey, Integer> entry : typeEntries) {
342            entry.setValue(index++);
343            writer.writeInt(stringSection.getItemIndex(typeSection.getString(entry.getKey())));
344        }
345    }
346
347    private void writeProtos(@Nonnull DexDataWriter writer) throws IOException {
348        protoSectionOffset = writer.getPosition();
349        int index = 0;
350
351        List<Map.Entry<? extends ProtoRefKey, Integer>> protoEntries = Lists.newArrayList(protoSection.getItems());
352        Collections.sort(protoEntries, DexWriter.<ProtoRefKey>comparableKeyComparator());
353
354        for (Map.Entry<? extends ProtoRefKey, Integer> entry: protoEntries) {
355            entry.setValue(index++);
356            ProtoRefKey key = entry.getKey();
357            writer.writeInt(stringSection.getItemIndex(protoSection.getShorty(key)));
358            writer.writeInt(typeSection.getItemIndex(protoSection.getReturnType(key)));
359            writer.writeInt(typeListSection.getNullableItemOffset(protoSection.getParameters(key)));
360        }
361    }
362
363    private void writeFields(@Nonnull DexDataWriter writer) throws IOException {
364        fieldSectionOffset = writer.getPosition();
365        int index = 0;
366
367        List<Map.Entry<? extends FieldRefKey, Integer>> fieldEntries = Lists.newArrayList(fieldSection.getItems());
368        Collections.sort(fieldEntries, DexWriter.<FieldRefKey>comparableKeyComparator());
369
370        for (Map.Entry<? extends FieldRefKey, Integer> entry: fieldEntries) {
371            entry.setValue(index++);
372            FieldRefKey key = entry.getKey();
373            writer.writeUshort(typeSection.getItemIndex(fieldSection.getDefiningClass(key)));
374            writer.writeUshort(typeSection.getItemIndex(fieldSection.getFieldType(key)));
375            writer.writeInt(stringSection.getItemIndex(fieldSection.getName(key)));
376        }
377    }
378
379    private void writeMethods(@Nonnull DexDataWriter writer) throws IOException {
380        methodSectionOffset = writer.getPosition();
381        int index = 0;
382
383        List<Map.Entry<? extends MethodRefKey, Integer>> methodEntries = Lists.newArrayList(methodSection.getItems());
384        Collections.sort(methodEntries, DexWriter.<MethodRefKey>comparableKeyComparator());
385
386        for (Map.Entry<? extends MethodRefKey, Integer> entry: methodEntries) {
387            entry.setValue(index++);
388            MethodRefKey key = entry.getKey();
389            writer.writeUshort(typeSection.getItemIndex(methodSection.getDefiningClass(key)));
390            writer.writeUshort(protoSection.getItemIndex(methodSection.getPrototype(key)));
391            writer.writeInt(stringSection.getItemIndex(methodSection.getName(key)));
392        }
393    }
394
395    private void writeClasses(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException {
396        classIndexSectionOffset = indexWriter.getPosition();
397        classDataSectionOffset = offsetWriter.getPosition();
398
399        List<Map.Entry<? extends ClassKey, Integer>> classEntries = Lists.newArrayList(classSection.getItems());
400        Collections.sort(classEntries, DexWriter.<ClassKey>comparableKeyComparator());
401
402        int index = 0;
403        for (Map.Entry<? extends ClassKey, Integer> key: classEntries) {
404            index = writeClass(indexWriter, offsetWriter, index, key);
405        }
406    }
407
408    /**
409     * Writes out the class_def_item and class_data_item for the given class.
410     *
411     * This will recursively write out any unwritten superclass/interface before writing the class itself, as per the
412     * dex specification.
413     *
414     * @return the index for the next class to be written
415     */
416    private int writeClass(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter,
417                           int nextIndex, @Nullable Map.Entry<? extends ClassKey, Integer> entry) throws IOException {
418        if (entry == null) {
419            // class does not exist in this dex file, cannot write it
420            return nextIndex;
421        }
422
423        if (entry.getValue() != NO_INDEX) {
424            // class has already been written, no need to write it
425            return nextIndex;
426        }
427
428        ClassKey key = entry.getKey();
429
430        // set a bogus index, to make sure we don't recurse and double-write it
431        entry.setValue(0);
432
433        // first, try to write the superclass
434        Map.Entry<? extends ClassKey, Integer> superEntry =
435                classSection.getClassEntryByType(classSection.getSuperclass(key));
436        nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, superEntry);
437
438        // then, try to write interfaces
439        for (TypeKey interfaceTypeKey: typeListSection.getTypes(classSection.getInterfaces(key))) {
440            Map.Entry<? extends ClassKey, Integer> interfaceEntry = classSection.getClassEntryByType(interfaceTypeKey);
441            nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, interfaceEntry);
442        }
443
444        // now set the index for real
445        entry.setValue(nextIndex++);
446
447        // and finally, write the class itself
448        // first, the class_def_item
449        indexWriter.writeInt(typeSection.getItemIndex(classSection.getType(key)));
450        indexWriter.writeInt(classSection.getAccessFlags(key));
451        indexWriter.writeInt(typeSection.getNullableItemIndex(classSection.getSuperclass(key)));
452        indexWriter.writeInt(typeListSection.getNullableItemOffset(classSection.getInterfaces(key)));
453        indexWriter.writeInt(stringSection.getNullableItemIndex(classSection.getSourceFile(key)));
454        indexWriter.writeInt(classSection.getAnnotationDirectoryOffset(key));
455
456        Collection<? extends FieldKey> staticFields = classSection.getSortedStaticFields(key);
457        Collection<? extends FieldKey> instanceFields = classSection.getSortedInstanceFields(key);
458        Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(key);
459        Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(key);
460        boolean classHasData = staticFields.size() > 0 ||
461                instanceFields.size() > 0 ||
462                directMethods.size() > 0 ||
463                virtualMethods.size() > 0;
464
465        if (classHasData) {
466            indexWriter.writeInt(offsetWriter.getPosition());
467        } else {
468            indexWriter.writeInt(0);
469        }
470
471        indexWriter.writeInt(classSection.getEncodedArrayOffset(key));
472
473        // now write the class_data_item
474        if (classHasData) {
475            numClassDataItems++;
476
477            offsetWriter.writeUleb128(staticFields.size());
478            offsetWriter.writeUleb128(instanceFields.size());
479            offsetWriter.writeUleb128(directMethods.size());
480            offsetWriter.writeUleb128(virtualMethods.size());
481
482            writeEncodedFields(offsetWriter, staticFields);
483            writeEncodedFields(offsetWriter, instanceFields);
484            writeEncodedMethods(offsetWriter, directMethods);
485            writeEncodedMethods(offsetWriter, virtualMethods);
486        }
487
488        return nextIndex;
489    }
490
491    private void writeEncodedFields(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends FieldKey> fields)
492            throws IOException {
493        int prevIndex = 0;
494        for (FieldKey key: fields) {
495            int index = fieldSection.getFieldIndex(key);
496            writer.writeUleb128(index - prevIndex);
497            writer.writeUleb128(classSection.getFieldAccessFlags(key));
498            prevIndex = index;
499        }
500    }
501
502    private void writeEncodedMethods(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends MethodKey> methods)
503            throws IOException {
504        int prevIndex = 0;
505        for (MethodKey key: methods) {
506            int index = methodSection.getMethodIndex(key);
507            writer.writeUleb128(index-prevIndex);
508            writer.writeUleb128(classSection.getMethodAccessFlags(key));
509            writer.writeUleb128(classSection.getCodeItemOffset(key));
510            prevIndex = index;
511        }
512    }
513
514    private void writeTypeLists(@Nonnull DexDataWriter writer) throws IOException {
515        writer.align();
516        typeListSectionOffset = writer.getPosition();
517        for (Map.Entry<? extends TypeListKey, Integer> entry: typeListSection.getItems()) {
518            writer.align();
519            entry.setValue(writer.getPosition());
520
521            Collection<? extends TypeKey> types = typeListSection.getTypes(entry.getKey());
522            writer.writeInt(types.size());
523            for (TypeKey typeKey: types) {
524                writer.writeUshort(typeSection.getItemIndex(typeKey));
525            }
526        }
527    }
528
529    private static class EncodedArrayKey<EncodedValue> {
530        @Nonnull Collection<? extends EncodedValue> elements;
531
532        public EncodedArrayKey() {
533        }
534
535        @Override public int hashCode() {
536            return CollectionUtils.listHashCode(elements);
537        }
538
539        @Override public boolean equals(Object o) {
540            if (o instanceof EncodedArrayKey) {
541                EncodedArrayKey other = (EncodedArrayKey)o;
542                if (elements.size() != other.elements.size()) {
543                    return false;
544                }
545                return Iterables.elementsEqual(elements, other.elements);
546            }
547            return false;
548        }
549    }
550
551    private void writeEncodedArrays(@Nonnull DexDataWriter writer) throws IOException {
552        InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer);
553        encodedArraySectionOffset = writer.getPosition();
554
555        HashMap<EncodedArrayKey<EncodedValue>, Integer> internedItems = Maps.newHashMap();
556        EncodedArrayKey<EncodedValue> key = new EncodedArrayKey<EncodedValue>();
557
558        for (ClassKey classKey: classSection.getSortedClasses()) {
559            Collection <? extends EncodedValue> elements = classSection.getStaticInitializers(classKey);
560            if (elements != null && elements.size() > 0) {
561                key.elements = elements;
562                Integer prev = internedItems.get(key);
563                if (prev != null) {
564                    classSection.setEncodedArrayOffset(classKey, prev);
565                } else {
566                    int offset = writer.getPosition();
567                    internedItems.put(key, offset);
568                    classSection.setEncodedArrayOffset(classKey, offset);
569                    key = new EncodedArrayKey<EncodedValue>();
570
571                    numEncodedArrayItems++;
572
573                    writer.writeUleb128(elements.size());
574                    for (EncodedValue value: elements) {
575                        writeEncodedValue(encodedValueWriter, value);
576                    }
577                }
578            }
579        }
580    }
581
582    private void writeAnnotations(@Nonnull DexDataWriter writer) throws IOException {
583        InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer);
584
585        annotationSectionOffset = writer.getPosition();
586        for (Map.Entry<? extends AnnotationKey, Integer> entry: annotationSection.getItems()) {
587            entry.setValue(writer.getPosition());
588
589            AnnotationKey key = entry.getKey();
590
591            writer.writeUbyte(annotationSection.getVisibility(key));
592            writer.writeUleb128(typeSection.getItemIndex(annotationSection.getType(key)));
593
594            Collection<? extends AnnotationElement> elements = Ordering.from(BaseAnnotationElement.BY_NAME)
595                    .immutableSortedCopy(annotationSection.getElements(key));
596
597            writer.writeUleb128(elements.size());
598
599            for (AnnotationElement element: elements) {
600                writer.writeUleb128(stringSection.getItemIndex(annotationSection.getElementName(element)));
601                writeEncodedValue(encodedValueWriter, annotationSection.getElementValue(element));
602            }
603        }
604    }
605
606    private void writeAnnotationSets(@Nonnull DexDataWriter writer) throws IOException {
607        writer.align();
608        annotationSetSectionOffset = writer.getPosition();
609        if (shouldCreateEmptyAnnotationSet()) {
610            writer.writeInt(0);
611        }
612        for (Map.Entry<? extends AnnotationSetKey, Integer> entry: annotationSetSection.getItems()) {
613            Collection<? extends AnnotationKey> annotations = Ordering.from(BaseAnnotation.BY_TYPE)
614                    .immutableSortedCopy(annotationSetSection.getAnnotations(entry.getKey()));
615
616            writer.align();
617            entry.setValue(writer.getPosition());
618            writer.writeInt(annotations.size());
619            for (AnnotationKey annotationKey: annotations) {
620                writer.writeInt(annotationSection.getItemOffset(annotationKey));
621            }
622        }
623    }
624
625    private void writeAnnotationSetRefs(@Nonnull DexDataWriter writer) throws IOException {
626        writer.align();
627        annotationSetRefSectionOffset = writer.getPosition();
628        HashMap<List<? extends AnnotationSetKey>, Integer> internedItems = Maps.newHashMap();
629
630        for (ClassKey classKey: classSection.getSortedClasses()) {
631            for (MethodKey methodKey: classSection.getSortedMethods(classKey)) {
632                List<? extends AnnotationSetKey> parameterAnnotations = classSection.getParameterAnnotations(methodKey);
633                if (parameterAnnotations != null) {
634                    Integer prev = internedItems.get(parameterAnnotations);
635                    if (prev != null) {
636                        classSection.setAnnotationSetRefListOffset(methodKey, prev);
637                    } else {
638                        writer.align();
639                        int position = writer.getPosition();
640                        classSection.setAnnotationSetRefListOffset(methodKey, position);
641                        internedItems.put(parameterAnnotations, position);
642
643                        numAnnotationSetRefItems++;
644
645                        writer.writeInt(parameterAnnotations.size());
646                        for (AnnotationSetKey annotationSetKey: parameterAnnotations) {
647                            if (annotationSetSection.getAnnotations(annotationSetKey).size() > 0) {
648                                writer.writeInt(annotationSetSection.getItemOffset(annotationSetKey));
649                            } else if (shouldCreateEmptyAnnotationSet()) {
650                                writer.writeInt(annotationSetSectionOffset);
651                            } else {
652                                writer.writeInt(NO_OFFSET);
653                            }
654                        }
655                    }
656                }
657            }
658        }
659    }
660
661    private void writeAnnotationDirectories(@Nonnull DexDataWriter writer) throws IOException {
662        writer.align();
663        annotationDirectorySectionOffset = writer.getPosition();
664        HashMap<AnnotationSetKey, Integer> internedItems = Maps.newHashMap();
665
666        ByteBuffer tempBuffer = ByteBuffer.allocate(65536);
667        tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
668
669        for (ClassKey key: classSection.getSortedClasses()) {
670            // first, we write the field/method/parameter items to a temporary buffer, so that we can get a count
671            // of each type, and determine if we even need to write an annotation directory for this class
672
673            Collection<? extends FieldKey> fields = classSection.getSortedFields(key);
674            Collection<? extends MethodKey> methods = classSection.getSortedMethods(key);
675
676            // this is how much space we'll need if every field and method has annotations.
677            int maxSize = fields.size() * 8 + methods.size() * 16;
678            if (maxSize > tempBuffer.capacity()) {
679                tempBuffer = ByteBuffer.allocate(maxSize);
680                tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
681            }
682
683            tempBuffer.clear();
684
685            int fieldAnnotations = 0;
686            int methodAnnotations = 0;
687            int parameterAnnotations = 0;
688
689            for (FieldKey field: fields) {
690                AnnotationSetKey fieldAnnotationsKey = classSection.getFieldAnnotations(field);
691                if (fieldAnnotationsKey != null) {
692                    fieldAnnotations++;
693                    tempBuffer.putInt(fieldSection.getFieldIndex(field));
694                    tempBuffer.putInt(annotationSetSection.getItemOffset(fieldAnnotationsKey));
695                }
696            }
697
698            for (MethodKey method: methods) {
699                AnnotationSetKey methodAnnotationsKey = classSection.getMethodAnnotations(method);
700                if (methodAnnotationsKey != null) {
701                    methodAnnotations++;
702                    tempBuffer.putInt(methodSection.getMethodIndex(method));
703                    tempBuffer.putInt(annotationSetSection.getItemOffset(methodAnnotationsKey));
704                }
705            }
706
707            for (MethodKey method: methods) {
708                int offset = classSection.getAnnotationSetRefListOffset(method);
709                if (offset != DexWriter.NO_OFFSET) {
710                    parameterAnnotations++;
711                    tempBuffer.putInt(methodSection.getMethodIndex(method));
712                    tempBuffer.putInt(offset);
713                }
714            }
715
716            // now, we finally know how many field/method/parameter annotations were written to the temp buffer
717
718            AnnotationSetKey classAnnotationKey = classSection.getClassAnnotations(key);
719            if (fieldAnnotations == 0 && methodAnnotations == 0 && parameterAnnotations == 0) {
720                if (classAnnotationKey != null) {
721                    // This is an internable directory. Let's see if we've already written one like it
722                    Integer directoryOffset = internedItems.get(classAnnotationKey);
723                    if (directoryOffset != null) {
724                        classSection.setAnnotationDirectoryOffset(key, directoryOffset);
725                        continue;
726                    } else {
727                        internedItems.put(classAnnotationKey, writer.getPosition());
728                    }
729                } else {
730                    continue;
731                }
732            }
733
734            // yep, we need to write it out
735            numAnnotationDirectoryItems++;
736            classSection.setAnnotationDirectoryOffset(key, writer.getPosition());
737
738            writer.writeInt(annotationSetSection.getNullableItemOffset(classAnnotationKey));
739            writer.writeInt(fieldAnnotations);
740            writer.writeInt(methodAnnotations);
741            writer.writeInt(parameterAnnotations);
742            writer.write(tempBuffer.array(), 0, tempBuffer.position());
743        }
744    }
745
746    private static class CodeItemOffset<MethodKey> {
747        @Nonnull MethodKey method;
748        int codeOffset;
749
750        private CodeItemOffset(@Nonnull MethodKey method, int codeOffset) {
751            this.codeOffset = codeOffset;
752            this.method = method;
753        }
754    }
755
756    private void writeDebugAndCodeItems(@Nonnull DexDataWriter offsetWriter,
757                                        @Nonnull DeferredOutputStream temp) throws IOException {
758        ByteArrayOutputStream ehBuf = new ByteArrayOutputStream();
759        debugSectionOffset = offsetWriter.getPosition();
760        DebugWriter<StringKey, TypeKey> debugWriter =
761                new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, offsetWriter);
762
763        DexDataWriter codeWriter = new DexDataWriter(temp, 0);
764
765        List<CodeItemOffset<MethodKey>> codeOffsets = Lists.newArrayList();
766
767        for (ClassKey classKey: classSection.getSortedClasses()) {
768            Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey);
769            Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey);
770
771            Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods);
772
773            for (MethodKey methodKey: methods) {
774                List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks =
775                        classSection.getTryBlocks(methodKey);
776                Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey);
777                Iterable<? extends DebugItem> debugItems = classSection.getDebugItems(methodKey);
778
779                if (instructions != null && stringSection.hasJumboIndexes()) {
780                    boolean needsFix = false;
781                    for (Instruction instruction: instructions) {
782                        if (instruction.getOpcode() == Opcode.CONST_STRING) {
783                            if (stringSection.getItemIndex(
784                                    (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) {
785                                needsFix = true;
786                                break;
787                            }
788                        }
789                    }
790
791                    if (needsFix) {
792                        MutableMethodImplementation mutableMethodImplementation =
793                                classSection.makeMutableMethodImplementation(methodKey);
794                        fixInstructions(mutableMethodImplementation);
795
796                        instructions = mutableMethodImplementation.getInstructions();
797                        tryBlocks = mutableMethodImplementation.getTryBlocks();
798                        debugItems = mutableMethodImplementation.getDebugItems();
799                    }
800                }
801
802                int debugItemOffset = writeDebugItem(offsetWriter, debugWriter,
803                        classSection.getParameterNames(methodKey), debugItems);
804                int codeItemOffset = writeCodeItem(codeWriter, ehBuf, methodKey, tryBlocks, instructions, debugItemOffset);
805
806                if (codeItemOffset != -1) {
807                    codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset));
808                }
809            }
810        }
811
812        offsetWriter.align();
813        codeSectionOffset = offsetWriter.getPosition();
814
815        codeWriter.close();
816        temp.writeTo(offsetWriter);
817        temp.close();
818
819        for (CodeItemOffset<MethodKey> codeOffset: codeOffsets) {
820            classSection.setCodeItemOffset(codeOffset.method, codeSectionOffset + codeOffset.codeOffset);
821        }
822    }
823
824    private void fixInstructions(@Nonnull MutableMethodImplementation methodImplementation) {
825        List<? extends Instruction> instructions = methodImplementation.getInstructions();
826
827        for (int i=0; i<instructions.size(); i++) {
828            Instruction instruction = instructions.get(i);
829
830            if (instruction.getOpcode() == Opcode.CONST_STRING) {
831                if (stringSection.getItemIndex(
832                        (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) {
833                    methodImplementation.replaceInstruction(i, new BuilderInstruction31c(Opcode.CONST_STRING_JUMBO,
834                            ((OneRegisterInstruction)instruction).getRegisterA(),
835                            ((ReferenceInstruction)instruction).getReference()));
836                }
837            }
838        }
839    }
840
841    private int writeDebugItem(@Nonnull DexDataWriter writer,
842                               @Nonnull DebugWriter<StringKey, TypeKey> debugWriter,
843                               @Nullable Iterable<? extends StringKey> parameterNames,
844                               @Nullable Iterable<? extends DebugItem> debugItems) throws IOException {
845        int parameterCount = 0;
846        int lastNamedParameterIndex = -1;
847        if (parameterNames != null) {
848            parameterCount = Iterables.size(parameterNames);
849            int index = 0;
850            for (StringKey parameterName: parameterNames) {
851                if (parameterName != null) {
852                    lastNamedParameterIndex = index;
853                }
854                index++;
855            }
856        }
857
858
859        if (lastNamedParameterIndex == -1 && (debugItems == null || Iterables.isEmpty(debugItems))) {
860            return NO_OFFSET;
861        }
862
863        numDebugInfoItems++;
864
865        int debugItemOffset = writer.getPosition();
866        int startingLineNumber = 0;
867
868        if (debugItems != null) {
869            for (org.jf.dexlib2.iface.debug.DebugItem debugItem: debugItems) {
870                if (debugItem instanceof LineNumber) {
871                    startingLineNumber = ((LineNumber)debugItem).getLineNumber();
872                    break;
873                }
874            }
875        }
876        writer.writeUleb128(startingLineNumber);
877
878        writer.writeUleb128(parameterCount);
879        if (parameterNames != null) {
880            int index = 0;
881            for (StringKey parameterName: parameterNames) {
882                if (index == parameterCount) {
883                    break;
884                }
885                index++;
886                writer.writeUleb128(stringSection.getNullableItemIndex(parameterName) + 1);
887            }
888        }
889
890        if (debugItems != null) {
891            debugWriter.reset(startingLineNumber);
892
893            for (DebugItem debugItem: debugItems) {
894                classSection.writeDebugItem(debugWriter, debugItem);
895            }
896        }
897        // write an END_SEQUENCE opcode, to end the debug item
898        writer.write(0);
899
900        return debugItemOffset;
901    }
902
903    private int writeCodeItem(@Nonnull DexDataWriter writer,
904                              @Nonnull ByteArrayOutputStream ehBuf,
905                              @Nonnull MethodKey methodKey,
906                              @Nonnull List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks,
907                              @Nullable Iterable<? extends Instruction> instructions,
908                              int debugItemOffset) throws IOException {
909        if (instructions == null && debugItemOffset == NO_OFFSET) {
910            return -1;
911        }
912
913        numCodeItemItems++;
914
915        writer.align();
916
917        int codeItemOffset = writer.getPosition();
918
919        writer.writeUshort(classSection.getRegisterCount(methodKey));
920
921        boolean isStatic = AccessFlags.STATIC.isSet(classSection.getMethodAccessFlags(methodKey));
922        Collection<? extends TypeKey> parameters = typeListSection.getTypes(
923                protoSection.getParameters(methodSection.getPrototype(methodKey)));
924
925        writer.writeUshort(MethodUtil.getParameterRegisterCount(parameters, isStatic));
926
927        if (instructions != null) {
928            tryBlocks = TryListBuilder.massageTryBlocks(tryBlocks);
929
930            int outParamCount = 0;
931            int codeUnitCount = 0;
932            for (Instruction instruction: instructions) {
933                codeUnitCount += instruction.getCodeUnits();
934                if (instruction.getOpcode().referenceType == ReferenceType.METHOD) {
935                    ReferenceInstruction refInsn = (ReferenceInstruction)instruction;
936                    MethodReference methodRef = (MethodReference)refInsn.getReference();
937                    int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode()));
938                    if (paramCount > outParamCount) {
939                        outParamCount = paramCount;
940                    }
941                }
942            }
943
944            writer.writeUshort(outParamCount);
945            writer.writeUshort(tryBlocks.size());
946            writer.writeInt(debugItemOffset);
947
948            InstructionWriter instructionWriter =
949                    InstructionWriter.makeInstructionWriter(opcodes, writer, stringSection, typeSection, fieldSection,
950                            methodSection, protoSection);
951
952            writer.writeInt(codeUnitCount);
953            for (Instruction instruction: instructions) {
954                switch (instruction.getOpcode().format) {
955                    case Format10t:
956                        instructionWriter.write((Instruction10t)instruction);
957                        break;
958                    case Format10x:
959                        instructionWriter.write((Instruction10x)instruction);
960                        break;
961                    case Format11n:
962                        instructionWriter.write((Instruction11n)instruction);
963                        break;
964                    case Format11x:
965                        instructionWriter.write((Instruction11x)instruction);
966                        break;
967                    case Format12x:
968                        instructionWriter.write((Instruction12x)instruction);
969                        break;
970                    case Format20bc:
971                        instructionWriter.write((Instruction20bc)instruction);
972                        break;
973                    case Format20t:
974                        instructionWriter.write((Instruction20t)instruction);
975                        break;
976                    case Format21c:
977                        instructionWriter.write((Instruction21c)instruction);
978                        break;
979                    case Format21ih:
980                        instructionWriter.write((Instruction21ih)instruction);
981                        break;
982                    case Format21lh:
983                        instructionWriter.write((Instruction21lh)instruction);
984                        break;
985                    case Format21s:
986                        instructionWriter.write((Instruction21s)instruction);
987                        break;
988                    case Format21t:
989                        instructionWriter.write((Instruction21t)instruction);
990                        break;
991                    case Format22b:
992                        instructionWriter.write((Instruction22b)instruction);
993                        break;
994                    case Format22c:
995                        instructionWriter.write((Instruction22c)instruction);
996                        break;
997                    case Format22s:
998                        instructionWriter.write((Instruction22s)instruction);
999                        break;
1000                    case Format22t:
1001                        instructionWriter.write((Instruction22t)instruction);
1002                        break;
1003                    case Format22x:
1004                        instructionWriter.write((Instruction22x)instruction);
1005                        break;
1006                    case Format23x:
1007                        instructionWriter.write((Instruction23x)instruction);
1008                        break;
1009                    case Format30t:
1010                        instructionWriter.write((Instruction30t)instruction);
1011                        break;
1012                    case Format31c:
1013                        instructionWriter.write((Instruction31c)instruction);
1014                        break;
1015                    case Format31i:
1016                        instructionWriter.write((Instruction31i)instruction);
1017                        break;
1018                    case Format31t:
1019                        instructionWriter.write((Instruction31t)instruction);
1020                        break;
1021                    case Format32x:
1022                        instructionWriter.write((Instruction32x)instruction);
1023                        break;
1024                    case Format35c:
1025                        instructionWriter.write((Instruction35c)instruction);
1026                        break;
1027                    case Format3rc:
1028                        instructionWriter.write((Instruction3rc)instruction);
1029                        break;
1030                    case Format45cc:
1031                        instructionWriter.write((Instruction45cc) instruction);
1032                        break;
1033                    case Format4rcc:
1034                        instructionWriter.write((Instruction4rcc) instruction);
1035                        break;
1036                    case Format51l:
1037                        instructionWriter.write((Instruction51l)instruction);
1038                        break;
1039                    case ArrayPayload:
1040                        instructionWriter.write((ArrayPayload)instruction);
1041                        break;
1042                    case PackedSwitchPayload:
1043                        instructionWriter.write((PackedSwitchPayload)instruction);
1044                        break;
1045                    case SparseSwitchPayload:
1046                        instructionWriter.write((SparseSwitchPayload)instruction);
1047                        break;
1048                    default:
1049                        throw new ExceptionWithContext("Unsupported instruction format: %s",
1050                                instruction.getOpcode().format);
1051                }
1052            }
1053
1054            if (tryBlocks.size() > 0) {
1055                writer.align();
1056
1057                // filter out unique lists of exception handlers
1058                Map<List<? extends ExceptionHandler>, Integer> exceptionHandlerOffsetMap = Maps.newHashMap();
1059                for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
1060                    exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0);
1061                }
1062                DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size());
1063
1064                for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
1065                    int startAddress = tryBlock.getStartCodeAddress();
1066                    int endAddress = startAddress + tryBlock.getCodeUnitCount();
1067
1068                    int tbCodeUnitCount = endAddress - startAddress;
1069
1070                    writer.writeInt(startAddress);
1071                    writer.writeUshort(tbCodeUnitCount);
1072
1073                    if (tryBlock.getExceptionHandlers().size() == 0) {
1074                        throw new ExceptionWithContext("No exception handlers for the try block!");
1075                    }
1076
1077                    Integer offset = exceptionHandlerOffsetMap.get(tryBlock.getExceptionHandlers());
1078                    if (offset != 0) {
1079                        // exception handler has already been written out, just use it
1080                        writer.writeUshort(offset);
1081                    } else {
1082                        // if offset has not been set yet, we are about to write out a new exception handler
1083                        offset = ehBuf.size();
1084                        writer.writeUshort(offset);
1085                        exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), offset);
1086
1087                        // check if the last exception handler is a catch-all and adjust the size accordingly
1088                        int ehSize = tryBlock.getExceptionHandlers().size();
1089                        ExceptionHandler ehLast = tryBlock.getExceptionHandlers().get(ehSize-1);
1090                        if (ehLast.getExceptionType() == null) {
1091                            ehSize = ehSize * (-1) + 1;
1092                        }
1093
1094                        // now let's layout the exception handlers, assuming that catch-all is always last
1095                        DexDataWriter.writeSleb128(ehBuf, ehSize);
1096                        for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) {
1097                            TypeKey exceptionTypeKey = classSection.getExceptionType(eh);
1098
1099                            int codeAddress = eh.getHandlerCodeAddress();
1100
1101                            if (exceptionTypeKey != null) {
1102                                //regular exception handling
1103                                DexDataWriter.writeUleb128(ehBuf, typeSection.getItemIndex(exceptionTypeKey));
1104                                DexDataWriter.writeUleb128(ehBuf, codeAddress);
1105                            } else {
1106                                //catch-all
1107                                DexDataWriter.writeUleb128(ehBuf, codeAddress);
1108                            }
1109                        }
1110                    }
1111                }
1112
1113                if (ehBuf.size() > 0) {
1114                    ehBuf.writeTo(writer);
1115                    ehBuf.reset();
1116                }
1117            }
1118        } else {
1119            // no instructions, all we have is the debug item offset
1120            writer.writeUshort(0);
1121            writer.writeUshort(0);
1122            writer.writeInt(debugItemOffset);
1123            writer.writeInt(0);
1124        }
1125
1126        return codeItemOffset;
1127    }
1128
1129    private int calcNumItems() {
1130        int numItems = 0;
1131
1132        // header item
1133        numItems++;
1134
1135        if (stringSection.getItems().size() > 0) {
1136            numItems += 2; // index and data
1137        }
1138        if (typeSection.getItems().size()  > 0) {
1139            numItems++;
1140        }
1141        if (protoSection.getItems().size() > 0) {
1142            numItems++;
1143        }
1144        if (fieldSection.getItems().size() > 0) {
1145            numItems++;
1146        }
1147        if (methodSection.getItems().size() > 0) {
1148            numItems++;
1149        }
1150        if (typeListSection.getItems().size() > 0) {
1151            numItems++;
1152        }
1153        if (numEncodedArrayItems > 0) {
1154            numItems++;
1155        }
1156        if (annotationSection.getItems().size() > 0) {
1157            numItems++;
1158        }
1159        if (annotationSetSection.getItems().size() > 0 || shouldCreateEmptyAnnotationSet()) {
1160            numItems++;
1161        }
1162        if (numAnnotationSetRefItems > 0) {
1163            numItems++;
1164        }
1165        if (numAnnotationDirectoryItems > 0) {
1166            numItems++;
1167        }
1168        if (numDebugInfoItems > 0) {
1169            numItems++;
1170        }
1171        if (numCodeItemItems > 0) {
1172            numItems++;
1173        }
1174        if (classSection.getItems().size() > 0) {
1175            numItems++;
1176        }
1177        if (numClassDataItems > 0) {
1178            numItems++;
1179        }
1180        // map item itself
1181        numItems++;
1182
1183        return numItems;
1184    }
1185
1186    private void writeMapItem(@Nonnull DexDataWriter writer) throws IOException{
1187        writer.align();
1188        mapSectionOffset = writer.getPosition();
1189        int numItems = calcNumItems();
1190
1191        writer.writeInt(numItems);
1192
1193        // index section
1194        writeMapItem(writer, ItemType.HEADER_ITEM, 1, 0);
1195        writeMapItem(writer, ItemType.STRING_ID_ITEM, stringSection.getItems().size(), stringIndexSectionOffset);
1196        writeMapItem(writer, ItemType.TYPE_ID_ITEM, typeSection.getItems().size(), typeSectionOffset);
1197        writeMapItem(writer, ItemType.PROTO_ID_ITEM, protoSection.getItems().size(), protoSectionOffset);
1198        writeMapItem(writer, ItemType.FIELD_ID_ITEM, fieldSection.getItems().size(), fieldSectionOffset);
1199        writeMapItem(writer, ItemType.METHOD_ID_ITEM, methodSection.getItems().size(), methodSectionOffset);
1200        writeMapItem(writer, ItemType.CLASS_DEF_ITEM, classSection.getItems().size(), classIndexSectionOffset);
1201
1202        // data section
1203        writeMapItem(writer, ItemType.STRING_DATA_ITEM, stringSection.getItems().size(), stringDataSectionOffset);
1204        writeMapItem(writer, ItemType.TYPE_LIST, typeListSection.getItems().size(), typeListSectionOffset);
1205        writeMapItem(writer, ItemType.ENCODED_ARRAY_ITEM, numEncodedArrayItems, encodedArraySectionOffset);
1206        writeMapItem(writer, ItemType.ANNOTATION_ITEM, annotationSection.getItems().size(), annotationSectionOffset);
1207        writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM,
1208                annotationSetSection.getItems().size() + (shouldCreateEmptyAnnotationSet() ? 1 : 0), annotationSetSectionOffset);
1209        writeMapItem(writer, ItemType.ANNOTATION_SET_REF_LIST, numAnnotationSetRefItems, annotationSetRefSectionOffset);
1210        writeMapItem(writer, ItemType.ANNOTATION_DIRECTORY_ITEM, numAnnotationDirectoryItems,
1211                annotationDirectorySectionOffset);
1212        writeMapItem(writer, ItemType.DEBUG_INFO_ITEM, numDebugInfoItems, debugSectionOffset);
1213        writeMapItem(writer, ItemType.CODE_ITEM, numCodeItemItems, codeSectionOffset);
1214        writeMapItem(writer, ItemType.CLASS_DATA_ITEM, numClassDataItems, classDataSectionOffset);
1215        writeMapItem(writer, ItemType.MAP_LIST, 1, mapSectionOffset);
1216    }
1217
1218    private void writeMapItem(@Nonnull DexDataWriter writer, int type, int size, int offset) throws IOException {
1219        if (size > 0) {
1220            writer.writeUshort(type);
1221            writer.writeUshort(0);
1222            writer.writeInt(size);
1223            writer.writeInt(offset);
1224        }
1225    }
1226
1227    private void writeHeader(@Nonnull DexDataWriter writer, int dataOffset, int fileSize) throws IOException {
1228        // Write the appropriate header.
1229        writer.write(HeaderItem.getMagicForApi(opcodes.api));
1230
1231        // checksum placeholder
1232        writer.writeInt(0);
1233
1234        // signature placeholder
1235        writer.write(new byte[20]);
1236
1237        writer.writeInt(fileSize);
1238        writer.writeInt(HeaderItem.ITEM_SIZE);
1239        writer.writeInt(HeaderItem.LITTLE_ENDIAN_TAG);
1240
1241        // link
1242        writer.writeInt(0);
1243        writer.writeInt(0);
1244
1245        // map
1246        writer.writeInt(mapSectionOffset);
1247
1248        // index sections
1249
1250        writeSectionInfo(writer, stringSection.getItems().size(), stringIndexSectionOffset);
1251        writeSectionInfo(writer, typeSection.getItems().size(), typeSectionOffset);
1252        writeSectionInfo(writer, protoSection.getItems().size(), protoSectionOffset);
1253        writeSectionInfo(writer, fieldSection.getItems().size(), fieldSectionOffset);
1254        writeSectionInfo(writer, methodSection.getItems().size(), methodSectionOffset);
1255        writeSectionInfo(writer, classSection.getItems().size(), classIndexSectionOffset);
1256
1257        // data section
1258        writer.writeInt(fileSize - dataOffset);
1259        writer.writeInt(dataOffset);
1260    }
1261
1262    private void writeSectionInfo(DexDataWriter writer, int numItems, int offset) throws IOException {
1263        writer.writeInt(numItems);
1264        if (numItems > 0) {
1265            writer.writeInt(offset);
1266        } else {
1267            writer.writeInt(0);
1268        }
1269    }
1270
1271    private boolean shouldCreateEmptyAnnotationSet() {
1272        // Workaround for a crash in Dalvik VM before Jelly Bean MR1 (4.2)
1273        // which is triggered by NO_OFFSET in parameter annotation list.
1274        // (https://code.google.com/p/android/issues/detail?id=35304)
1275        return (opcodes.api < 17);
1276    }
1277}
1278