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