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