• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.dex;
18 
19 import com.android.dex.Code.CatchHandler;
20 import com.android.dex.Code.Try;
21 import com.android.dex.MethodHandle.MethodHandleType;
22 import com.android.dex.util.ByteInput;
23 import com.android.dex.util.ByteOutput;
24 import com.android.dex.util.FileUtils;
25 
26 import java.io.ByteArrayOutputStream;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.io.UTFDataFormatException;
34 import java.nio.ByteBuffer;
35 import java.nio.ByteOrder;
36 import java.security.MessageDigest;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.AbstractList;
39 import java.util.Collections;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.NoSuchElementException;
43 import java.util.RandomAccess;
44 import java.util.zip.Adler32;
45 import java.util.zip.ZipEntry;
46 import java.util.zip.ZipFile;
47 
48 /**
49  * The bytes of a dex file in memory for reading and writing. All int offsets
50  * are unsigned.
51  */
52 public final class Dex {
53     private static final int CHECKSUM_OFFSET = 8;
54     private static final int CHECKSUM_SIZE = 4;
55     private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE;
56     private static final int SIGNATURE_SIZE = 20;
57     // Provided as a convenience to avoid a memory allocation to benefit Dalvik.
58     // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik.
59     static final short[] EMPTY_SHORT_ARRAY = new short[0];
60 
61     private ByteBuffer data;
62     private final TableOfContents tableOfContents = new TableOfContents();
63     private int nextSectionStart = 0;
64     private final StringTable strings = new StringTable();
65     private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable();
66     private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable();
67     private final ProtoIdTable protoIds = new ProtoIdTable();
68     private final FieldIdTable fieldIds = new FieldIdTable();
69     private final MethodIdTable methodIds = new MethodIdTable();
70 
71     /**
72      * Creates a new dex that reads from {@code data}. It is an error to modify
73      * {@code data} after using it to create a dex buffer.
74      */
Dex(byte[] data)75     public Dex(byte[] data) throws IOException {
76         this(ByteBuffer.wrap(data));
77     }
78 
Dex(ByteBuffer data)79     private Dex(ByteBuffer data) throws IOException {
80         this.data = data;
81         this.data.order(ByteOrder.LITTLE_ENDIAN);
82         this.tableOfContents.readFrom(this);
83     }
84 
85     /**
86      * Creates a new empty dex of the specified size.
87      */
Dex(int byteCount)88     public Dex(int byteCount) throws IOException {
89         this.data = ByteBuffer.wrap(new byte[byteCount]);
90         this.data.order(ByteOrder.LITTLE_ENDIAN);
91     }
92 
93     /**
94      * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}.
95      */
Dex(InputStream in)96     public Dex(InputStream in) throws IOException {
97         try {
98             loadFrom(in);
99         } finally {
100             in.close();
101         }
102     }
103 
104     /**
105      * Creates a new dex buffer from the dex file {@code file}.
106      */
Dex(File file)107     public Dex(File file) throws IOException {
108         if (FileUtils.hasArchiveSuffix(file.getName())) {
109             ZipFile zipFile = new ZipFile(file);
110             ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME);
111             if (entry != null) {
112                 try (InputStream inputStream = zipFile.getInputStream(entry)) {
113                     loadFrom(inputStream);
114                 }
115                 zipFile.close();
116             } else {
117                 throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file);
118             }
119         } else if (file.getName().endsWith(".dex")) {
120             try (InputStream inputStream = new FileInputStream(file)) {
121                 loadFrom(inputStream);
122             }
123         } else {
124             throw new DexException("unknown output extension: " + file);
125         }
126     }
127 
128     /**
129      * It is the caller's responsibility to close {@code in}.
130      */
loadFrom(InputStream in)131     private void loadFrom(InputStream in) throws IOException {
132         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
133         byte[] buffer = new byte[8192];
134 
135         int count;
136         while ((count = in.read(buffer)) != -1) {
137             bytesOut.write(buffer, 0, count);
138         }
139 
140         this.data = ByteBuffer.wrap(bytesOut.toByteArray());
141         this.data.order(ByteOrder.LITTLE_ENDIAN);
142         this.tableOfContents.readFrom(this);
143     }
144 
checkBounds(int index, int length)145     private static void checkBounds(int index, int length) {
146         if (index < 0 || index >= length) {
147             throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
148         }
149     }
150 
writeTo(OutputStream out)151     public void writeTo(OutputStream out) throws IOException {
152         byte[] buffer = new byte[8192];
153         ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
154         data.clear();
155         while (data.hasRemaining()) {
156             int count = Math.min(buffer.length, data.remaining());
157             data.get(buffer, 0, count);
158             out.write(buffer, 0, count);
159         }
160     }
161 
writeTo(File dexOut)162     public void writeTo(File dexOut) throws IOException {
163         try (OutputStream out = new FileOutputStream(dexOut)) {
164             writeTo(out);
165         }
166     }
167 
getTableOfContents()168     public TableOfContents getTableOfContents() {
169         return tableOfContents;
170     }
171 
open(int position)172     public Section open(int position) {
173         if (position < 0 || position >= data.capacity()) {
174             throw new IllegalArgumentException("position=" + position
175                     + " length=" + data.capacity());
176         }
177         ByteBuffer sectionData = data.duplicate();
178         sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary?
179         sectionData.position(position);
180         sectionData.limit(data.capacity());
181         return new Section("section", sectionData);
182     }
183 
appendSection(int maxByteCount, String name)184     public Section appendSection(int maxByteCount, String name) {
185         if ((maxByteCount & 3) != 0) {
186             throw new IllegalStateException("Not four byte aligned!");
187         }
188         int limit = nextSectionStart + maxByteCount;
189         ByteBuffer sectionData = data.duplicate();
190         sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary?
191         sectionData.position(nextSectionStart);
192         sectionData.limit(limit);
193         Section result = new Section(name, sectionData);
194         nextSectionStart = limit;
195         return result;
196     }
197 
getLength()198     public int getLength() {
199         return data.capacity();
200     }
201 
getNextSectionStart()202     public int getNextSectionStart() {
203         return nextSectionStart;
204     }
205 
206     /**
207      * Returns a copy of the the bytes of this dex.
208      */
getBytes()209     public byte[] getBytes() {
210         ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
211         byte[] result = new byte[data.capacity()];
212         data.position(0);
213         data.get(result);
214         return result;
215     }
216 
strings()217     public List<String> strings() {
218         return strings;
219     }
220 
typeIds()221     public List<Integer> typeIds() {
222         return typeIds;
223     }
224 
typeNames()225     public List<String> typeNames() {
226         return typeNames;
227     }
228 
protoIds()229     public List<ProtoId> protoIds() {
230         return protoIds;
231     }
232 
fieldIds()233     public List<FieldId> fieldIds() {
234         return fieldIds;
235     }
236 
methodIds()237     public List<MethodId> methodIds() {
238         return methodIds;
239     }
240 
classDefs()241     public Iterable<ClassDef> classDefs() {
242         return new ClassDefIterable();
243     }
244 
readTypeList(int offset)245     public TypeList readTypeList(int offset) {
246         if (offset == 0) {
247             return TypeList.EMPTY;
248         }
249         return open(offset).readTypeList();
250     }
251 
readClassData(ClassDef classDef)252     public ClassData readClassData(ClassDef classDef) {
253         int offset = classDef.getClassDataOffset();
254         if (offset == 0) {
255             throw new IllegalArgumentException("offset == 0");
256         }
257         return open(offset).readClassData();
258     }
259 
readCode(ClassData.Method method)260     public Code readCode(ClassData.Method method) {
261         int offset = method.getCodeOffset();
262         if (offset == 0) {
263             throw new IllegalArgumentException("offset == 0");
264         }
265         return open(offset).readCode();
266     }
267 
268     /**
269      * Returns the signature of all but the first 32 bytes of this dex. The
270      * first 32 bytes of dex files are not specified to be included in the
271      * signature.
272      */
computeSignature()273     public byte[] computeSignature() throws IOException {
274         MessageDigest digest;
275         try {
276             digest = MessageDigest.getInstance("SHA-1");
277         } catch (NoSuchAlgorithmException e) {
278             throw new AssertionError();
279         }
280         byte[] buffer = new byte[8192];
281         ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
282         data.limit(data.capacity());
283         data.position(SIGNATURE_OFFSET + SIGNATURE_SIZE);
284         while (data.hasRemaining()) {
285             int count = Math.min(buffer.length, data.remaining());
286             data.get(buffer, 0, count);
287             digest.update(buffer, 0, count);
288         }
289         return digest.digest();
290     }
291 
292     /**
293      * Returns the checksum of all but the first 12 bytes of {@code dex}.
294      */
computeChecksum()295     public int computeChecksum() throws IOException {
296         Adler32 adler32 = new Adler32();
297         byte[] buffer = new byte[8192];
298         ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
299         data.limit(data.capacity());
300         data.position(CHECKSUM_OFFSET + CHECKSUM_SIZE);
301         while (data.hasRemaining()) {
302             int count = Math.min(buffer.length, data.remaining());
303             data.get(buffer, 0, count);
304             adler32.update(buffer, 0, count);
305         }
306         return (int) adler32.getValue();
307     }
308 
309     /**
310      * Generates the signature and checksum of the dex file {@code out} and
311      * writes them to the file.
312      */
writeHashes()313     public void writeHashes() throws IOException {
314         open(SIGNATURE_OFFSET).write(computeSignature());
315         open(CHECKSUM_OFFSET).writeInt(computeChecksum());
316     }
317 
318     /**
319      * Look up a descriptor index from a type index. Cheaper than:
320      * {@code open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();}
321      */
descriptorIndexFromTypeIndex(int typeIndex)322     public int descriptorIndexFromTypeIndex(int typeIndex) {
323        checkBounds(typeIndex, tableOfContents.typeIds.size);
324        int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex);
325        return data.getInt(position);
326     }
327 
328 
329     public final class Section implements ByteInput, ByteOutput {
330         private final String name;
331         private final ByteBuffer data;
332         private final int initialPosition;
333 
Section(String name, ByteBuffer data)334         private Section(String name, ByteBuffer data) {
335             this.name = name;
336             this.data = data;
337             this.initialPosition = data.position();
338         }
339 
getPosition()340         public int getPosition() {
341             return data.position();
342         }
343 
readInt()344         public int readInt() {
345             return data.getInt();
346         }
347 
readShort()348         public short readShort() {
349             return data.getShort();
350         }
351 
readUnsignedShort()352         public int readUnsignedShort() {
353             return readShort() & 0xffff;
354         }
355 
356         @Override
readByte()357         public byte readByte() {
358             return data.get();
359         }
360 
readByteArray(int length)361         public byte[] readByteArray(int length) {
362             byte[] result = new byte[length];
363             data.get(result);
364             return result;
365         }
366 
readShortArray(int length)367         public short[] readShortArray(int length) {
368             if (length == 0) {
369                 return EMPTY_SHORT_ARRAY;
370             }
371             short[] result = new short[length];
372             for (int i = 0; i < length; i++) {
373                 result[i] = readShort();
374             }
375             return result;
376         }
377 
readUleb128()378         public int readUleb128() {
379             return Leb128.readUnsignedLeb128(this);
380         }
381 
readUleb128p1()382         public int readUleb128p1() {
383             return Leb128.readUnsignedLeb128(this) - 1;
384         }
385 
readSleb128()386         public int readSleb128() {
387             return Leb128.readSignedLeb128(this);
388         }
389 
writeUleb128p1(int i)390         public void writeUleb128p1(int i) {
391             writeUleb128(i + 1);
392         }
393 
readTypeList()394         public TypeList readTypeList() {
395             int size = readInt();
396             short[] types = readShortArray(size);
397             alignToFourBytes();
398             return new TypeList(Dex.this, types);
399         }
400 
readString()401         public String readString() {
402             int offset = readInt();
403             int savedPosition = data.position();
404             int savedLimit = data.limit();
405             data.position(offset);
406             data.limit(data.capacity());
407             try {
408                 int expectedLength = readUleb128();
409                 String result = Mutf8.decode(this, new char[expectedLength]);
410                 if (result.length() != expectedLength) {
411                     throw new DexException("Declared length " + expectedLength
412                             + " doesn't match decoded length of " + result.length());
413                 }
414                 return result;
415             } catch (UTFDataFormatException e) {
416                 throw new DexException(e);
417             } finally {
418                 data.position(savedPosition);
419                 data.limit(savedLimit);
420             }
421         }
422 
readFieldId()423         public FieldId readFieldId() {
424             int declaringClassIndex = readUnsignedShort();
425             int typeIndex = readUnsignedShort();
426             int nameIndex = readInt();
427             return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex);
428         }
429 
readMethodId()430         public MethodId readMethodId() {
431             int declaringClassIndex = readUnsignedShort();
432             int protoIndex = readUnsignedShort();
433             int nameIndex = readInt();
434             return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex);
435         }
436 
readProtoId()437         public ProtoId readProtoId() {
438             int shortyIndex = readInt();
439             int returnTypeIndex = readInt();
440             int parametersOffset = readInt();
441             return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset);
442         }
443 
readCallSiteId()444         public CallSiteId readCallSiteId() {
445             int offset = readInt();
446             return new CallSiteId(Dex.this, offset);
447         }
448 
readMethodHandle()449         public MethodHandle readMethodHandle() {
450             MethodHandleType methodHandleType = MethodHandleType.fromValue(readUnsignedShort());
451             int unused1 = readUnsignedShort();
452             int fieldOrMethodId = readUnsignedShort();
453             int unused2 = readUnsignedShort();
454             return new MethodHandle(Dex.this, methodHandleType, unused1, fieldOrMethodId, unused2);
455         }
456 
readClassDef()457         public ClassDef readClassDef() {
458             int offset = getPosition();
459             int type = readInt();
460             int accessFlags = readInt();
461             int supertype = readInt();
462             int interfacesOffset = readInt();
463             int sourceFileIndex = readInt();
464             int annotationsOffset = readInt();
465             int classDataOffset = readInt();
466             int staticValuesOffset = readInt();
467             return new ClassDef(Dex.this, offset, type, accessFlags, supertype,
468                     interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset,
469                     staticValuesOffset);
470         }
471 
readCode()472         private Code readCode() {
473             int registersSize = readUnsignedShort();
474             int insSize = readUnsignedShort();
475             int outsSize = readUnsignedShort();
476             int triesSize = readUnsignedShort();
477             int debugInfoOffset = readInt();
478             int instructionsSize = readInt();
479             short[] instructions = readShortArray(instructionsSize);
480             Try[] tries;
481             CatchHandler[] catchHandlers;
482             if (triesSize > 0) {
483                 if (instructions.length % 2 == 1) {
484                     readShort(); // padding
485                 }
486 
487                 /*
488                  * We can't read the tries until we've read the catch handlers.
489                  * Unfortunately they're in the opposite order in the dex file
490                  * so we need to read them out-of-order.
491                  */
492                 Section triesSection = open(data.position());
493                 skip(triesSize * SizeOf.TRY_ITEM);
494                 catchHandlers = readCatchHandlers();
495                 tries = triesSection.readTries(triesSize, catchHandlers);
496             } else {
497                 tries = new Try[0];
498                 catchHandlers = new CatchHandler[0];
499             }
500             return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
501                             tries, catchHandlers);
502         }
503 
readCatchHandlers()504         private CatchHandler[] readCatchHandlers() {
505             int baseOffset = data.position();
506             int catchHandlersSize = readUleb128();
507             CatchHandler[] result = new CatchHandler[catchHandlersSize];
508             for (int i = 0; i < catchHandlersSize; i++) {
509                 int offset = data.position() - baseOffset;
510                 result[i] = readCatchHandler(offset);
511             }
512             return result;
513         }
514 
readTries(int triesSize, CatchHandler[] catchHandlers)515         private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) {
516             Try[] result = new Try[triesSize];
517             for (int i = 0; i < triesSize; i++) {
518                 int startAddress = readInt();
519                 int instructionCount = readUnsignedShort();
520                 int handlerOffset = readUnsignedShort();
521                 int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset);
522                 result[i] = new Try(startAddress, instructionCount, catchHandlerIndex);
523             }
524             return result;
525         }
526 
findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset)527         private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) {
528             for (int i = 0; i < catchHandlers.length; i++) {
529                 CatchHandler catchHandler = catchHandlers[i];
530                 if (catchHandler.getOffset() == offset) {
531                     return i;
532                 }
533             }
534             throw new IllegalArgumentException();
535         }
536 
readCatchHandler(int offset)537         private CatchHandler readCatchHandler(int offset) {
538             int size = readSleb128();
539             int handlersCount = Math.abs(size);
540             int[] typeIndexes = new int[handlersCount];
541             int[] addresses = new int[handlersCount];
542             for (int i = 0; i < handlersCount; i++) {
543                 typeIndexes[i] = readUleb128();
544                 addresses[i] = readUleb128();
545             }
546             int catchAllAddress = size <= 0 ? readUleb128() : -1;
547             return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset);
548         }
549 
readClassData()550         private ClassData readClassData() {
551             int staticFieldsSize = readUleb128();
552             int instanceFieldsSize = readUleb128();
553             int directMethodsSize = readUleb128();
554             int virtualMethodsSize = readUleb128();
555             ClassData.Field[] staticFields = readFields(staticFieldsSize);
556             ClassData.Field[] instanceFields = readFields(instanceFieldsSize);
557             ClassData.Method[] directMethods = readMethods(directMethodsSize);
558             ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize);
559             return new ClassData(staticFields, instanceFields, directMethods, virtualMethods);
560         }
561 
readFields(int count)562         private ClassData.Field[] readFields(int count) {
563             ClassData.Field[] result = new ClassData.Field[count];
564             int fieldIndex = 0;
565             for (int i = 0; i < count; i++) {
566                 fieldIndex += readUleb128(); // field index diff
567                 int accessFlags = readUleb128();
568                 result[i] = new ClassData.Field(fieldIndex, accessFlags);
569             }
570             return result;
571         }
572 
readMethods(int count)573         private ClassData.Method[] readMethods(int count) {
574             ClassData.Method[] result = new ClassData.Method[count];
575             int methodIndex = 0;
576             for (int i = 0; i < count; i++) {
577                 methodIndex += readUleb128(); // method index diff
578                 int accessFlags = readUleb128();
579                 int codeOff = readUleb128();
580                 result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff);
581             }
582             return result;
583         }
584 
585         /**
586          * Returns a byte array containing the bytes from {@code start} to this
587          * section's current position.
588          */
getBytesFrom(int start)589         private byte[] getBytesFrom(int start) {
590             int end = data.position();
591             byte[] result = new byte[end - start];
592             data.position(start);
593             data.get(result);
594             return result;
595         }
596 
readAnnotation()597         public Annotation readAnnotation() {
598             byte visibility = readByte();
599             int start = data.position();
600             new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue();
601             return new Annotation(Dex.this, visibility, new EncodedValue(getBytesFrom(start)));
602         }
603 
readEncodedArray()604         public EncodedValue readEncodedArray() {
605             int start = data.position();
606             new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue();
607             return new EncodedValue(getBytesFrom(start));
608         }
609 
skip(int count)610         public void skip(int count) {
611             if (count < 0) {
612                 throw new IllegalArgumentException();
613             }
614             data.position(data.position() + count);
615         }
616 
617         /**
618          * Skips bytes until the position is aligned to a multiple of 4.
619          */
alignToFourBytes()620         public void alignToFourBytes() {
621             data.position((data.position() + 3) & ~3);
622         }
623 
624         /**
625          * Writes 0x00 until the position is aligned to a multiple of 4.
626          */
alignToFourBytesWithZeroFill()627         public void alignToFourBytesWithZeroFill() {
628             while ((data.position() & 3) != 0) {
629                 data.put((byte) 0);
630             }
631         }
632 
assertFourByteAligned()633         public void assertFourByteAligned() {
634             if ((data.position() & 3) != 0) {
635                 throw new IllegalStateException("Not four byte aligned!");
636             }
637         }
638 
write(byte[] bytes)639         public void write(byte[] bytes) {
640             this.data.put(bytes);
641         }
642 
643         @Override
writeByte(int b)644         public void writeByte(int b) {
645             data.put((byte) b);
646         }
647 
writeShort(short i)648         public void writeShort(short i) {
649             data.putShort(i);
650         }
651 
writeUnsignedShort(int i)652         public void writeUnsignedShort(int i) {
653             short s = (short) i;
654             if (i != (s & 0xffff)) {
655                 throw new IllegalArgumentException("Expected an unsigned short: " + i);
656             }
657             writeShort(s);
658         }
659 
write(short[] shorts)660         public void write(short[] shorts) {
661             for (short s : shorts) {
662                 writeShort(s);
663             }
664         }
665 
writeInt(int i)666         public void writeInt(int i) {
667             data.putInt(i);
668         }
669 
writeUleb128(int i)670         public void writeUleb128(int i) {
671             try {
672                 Leb128.writeUnsignedLeb128(this, i);
673             } catch (ArrayIndexOutOfBoundsException e) {
674                 throw new DexException("Section limit " + data.limit() + " exceeded by " + name);
675             }
676         }
677 
writeSleb128(int i)678         public void writeSleb128(int i) {
679             try {
680                 Leb128.writeSignedLeb128(this, i);
681             } catch (ArrayIndexOutOfBoundsException e) {
682                 throw new DexException("Section limit " + data.limit() + " exceeded by " + name);
683             }
684         }
685 
writeStringData(String value)686         public void writeStringData(String value) {
687             try {
688                 int length = value.length();
689                 writeUleb128(length);
690                 write(Mutf8.encode(value));
691                 writeByte(0);
692             } catch (UTFDataFormatException e) {
693                 throw new AssertionError();
694             }
695         }
696 
writeTypeList(TypeList typeList)697         public void writeTypeList(TypeList typeList) {
698             short[] types = typeList.getTypes();
699             writeInt(types.length);
700             for (short type : types) {
701                 writeShort(type);
702             }
703             alignToFourBytesWithZeroFill();
704         }
705 
706         /**
707          * Returns the number of bytes used by this section.
708          */
used()709         public int used() {
710             return data.position() - initialPosition;
711         }
712     }
713 
714     private final class StringTable extends AbstractList<String> implements RandomAccess {
get(int index)715         @Override public String get(int index) {
716             checkBounds(index, tableOfContents.stringIds.size);
717             return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM))
718                     .readString();
719         }
size()720         @Override public int size() {
721             return tableOfContents.stringIds.size;
722         }
723     }
724 
725     private final class TypeIndexToDescriptorIndexTable extends AbstractList<Integer>
726             implements RandomAccess {
get(int index)727         @Override public Integer get(int index) {
728             return descriptorIndexFromTypeIndex(index);
729         }
size()730         @Override public int size() {
731             return tableOfContents.typeIds.size;
732         }
733     }
734 
735     private final class TypeIndexToDescriptorTable extends AbstractList<String>
736             implements RandomAccess {
get(int index)737         @Override public String get(int index) {
738             return strings.get(descriptorIndexFromTypeIndex(index));
739         }
size()740         @Override public int size() {
741             return tableOfContents.typeIds.size;
742         }
743     }
744 
745     private final class ProtoIdTable extends AbstractList<ProtoId> implements RandomAccess {
get(int index)746         @Override public ProtoId get(int index) {
747             checkBounds(index, tableOfContents.protoIds.size);
748             return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index))
749                     .readProtoId();
750         }
size()751         @Override public int size() {
752             return tableOfContents.protoIds.size;
753         }
754     }
755 
756     private final class FieldIdTable extends AbstractList<FieldId> implements RandomAccess {
get(int index)757         @Override public FieldId get(int index) {
758             checkBounds(index, tableOfContents.fieldIds.size);
759             return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index))
760                     .readFieldId();
761         }
size()762         @Override public int size() {
763             return tableOfContents.fieldIds.size;
764         }
765     }
766 
767     private final class MethodIdTable extends AbstractList<MethodId> implements RandomAccess {
get(int index)768         @Override public MethodId get(int index) {
769             checkBounds(index, tableOfContents.methodIds.size);
770             return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index))
771                     .readMethodId();
772         }
size()773         @Override public int size() {
774             return tableOfContents.methodIds.size;
775         }
776     }
777 
778     private final class ClassDefIterator implements Iterator<ClassDef> {
779         private final Dex.Section in = open(tableOfContents.classDefs.off);
780         private int count = 0;
781 
782         @Override
hasNext()783         public boolean hasNext() {
784             return count < tableOfContents.classDefs.size;
785         }
786         @Override
next()787         public ClassDef next() {
788             if (!hasNext()) {
789                 throw new NoSuchElementException();
790             }
791             count++;
792             return in.readClassDef();
793         }
794         @Override
remove()795             public void remove() {
796             throw new UnsupportedOperationException();
797         }
798     }
799 
800     private final class ClassDefIterable implements Iterable<ClassDef> {
801         @Override
iterator()802         public Iterator<ClassDef> iterator() {
803             return !tableOfContents.classDefs.exists()
804                ? Collections.<ClassDef>emptySet().iterator()
805                : new ClassDefIterator();
806         }
807     }
808 }
809