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