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