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.MethodHandle.MethodHandleType;
22import com.android.dex.util.ByteInput;
23import com.android.dex.util.ByteOutput;
24import com.android.dex.util.FileUtils;
25
26import java.io.ByteArrayOutputStream;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.io.UTFDataFormatException;
34import java.nio.ByteBuffer;
35import java.nio.ByteOrder;
36import java.security.MessageDigest;
37import java.security.NoSuchAlgorithmException;
38import java.util.AbstractList;
39import java.util.Collections;
40import java.util.Iterator;
41import java.util.List;
42import java.util.NoSuchElementException;
43import java.util.RandomAccess;
44import java.util.zip.Adler32;
45import java.util.zip.ZipEntry;
46import 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 */
52public 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     */
75    public Dex(byte[] data) throws IOException {
76        this(ByteBuffer.wrap(data));
77    }
78
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     */
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     */
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     */
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     */
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
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
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
162    public void writeTo(File dexOut) throws IOException {
163        try (OutputStream out = new FileOutputStream(dexOut)) {
164            writeTo(out);
165        }
166    }
167
168    public TableOfContents getTableOfContents() {
169        return tableOfContents;
170    }
171
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
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
198    public int getLength() {
199        return data.capacity();
200    }
201
202    public int getNextSectionStart() {
203        return nextSectionStart;
204    }
205
206    /**
207     * Returns a copy of the the bytes of this dex.
208     */
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
217    public List<String> strings() {
218        return strings;
219    }
220
221    public List<Integer> typeIds() {
222        return typeIds;
223    }
224
225    public List<String> typeNames() {
226        return typeNames;
227    }
228
229    public List<ProtoId> protoIds() {
230        return protoIds;
231    }
232
233    public List<FieldId> fieldIds() {
234        return fieldIds;
235    }
236
237    public List<MethodId> methodIds() {
238        return methodIds;
239    }
240
241    public Iterable<ClassDef> classDefs() {
242        return new ClassDefIterable();
243    }
244
245    public TypeList readTypeList(int offset) {
246        if (offset == 0) {
247            return TypeList.EMPTY;
248        }
249        return open(offset).readTypeList();
250    }
251
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
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     */
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     */
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     */
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     */
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
334        private Section(String name, ByteBuffer data) {
335            this.name = name;
336            this.data = data;
337            this.initialPosition = data.position();
338        }
339
340        public int getPosition() {
341            return data.position();
342        }
343
344        public int readInt() {
345            return data.getInt();
346        }
347
348        public short readShort() {
349            return data.getShort();
350        }
351
352        public int readUnsignedShort() {
353            return readShort() & 0xffff;
354        }
355
356        @Override
357        public byte readByte() {
358            return data.get();
359        }
360
361        public byte[] readByteArray(int length) {
362            byte[] result = new byte[length];
363            data.get(result);
364            return result;
365        }
366
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
378        public int readUleb128() {
379            return Leb128.readUnsignedLeb128(this);
380        }
381
382        public int readUleb128p1() {
383            return Leb128.readUnsignedLeb128(this) - 1;
384        }
385
386        public int readSleb128() {
387            return Leb128.readSignedLeb128(this);
388        }
389
390        public void writeUleb128p1(int i) {
391            writeUleb128(i + 1);
392        }
393
394        public TypeList readTypeList() {
395            int size = readInt();
396            short[] types = readShortArray(size);
397            alignToFourBytes();
398            return new TypeList(Dex.this, types);
399        }
400
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
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
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
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
444        public CallSiteId readCallSiteId() {
445            int offset = readInt();
446            return new CallSiteId(Dex.this, offset);
447        }
448
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
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
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
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
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
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
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
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
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
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         */
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
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
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
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         */
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         */
627        public void alignToFourBytesWithZeroFill() {
628            while ((data.position() & 3) != 0) {
629                data.put((byte) 0);
630            }
631        }
632
633        public void assertFourByteAligned() {
634            if ((data.position() & 3) != 0) {
635                throw new IllegalStateException("Not four byte aligned!");
636            }
637        }
638
639        public void write(byte[] bytes) {
640            this.data.put(bytes);
641        }
642
643        @Override
644        public void writeByte(int b) {
645            data.put((byte) b);
646        }
647
648        public void writeShort(short i) {
649            data.putShort(i);
650        }
651
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
660        public void write(short[] shorts) {
661            for (short s : shorts) {
662                writeShort(s);
663            }
664        }
665
666        public void writeInt(int i) {
667            data.putInt(i);
668        }
669
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
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
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
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         */
709        public int used() {
710            return data.position() - initialPosition;
711        }
712    }
713
714    private final class StringTable extends AbstractList<String> implements RandomAccess {
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        }
720        @Override public int size() {
721            return tableOfContents.stringIds.size;
722        }
723    }
724
725    private final class TypeIndexToDescriptorIndexTable extends AbstractList<Integer>
726            implements RandomAccess {
727        @Override public Integer get(int index) {
728            return descriptorIndexFromTypeIndex(index);
729        }
730        @Override public int size() {
731            return tableOfContents.typeIds.size;
732        }
733    }
734
735    private final class TypeIndexToDescriptorTable extends AbstractList<String>
736            implements RandomAccess {
737        @Override public String get(int index) {
738            return strings.get(descriptorIndexFromTypeIndex(index));
739        }
740        @Override public int size() {
741            return tableOfContents.typeIds.size;
742        }
743    }
744
745    private final class ProtoIdTable extends AbstractList<ProtoId> implements RandomAccess {
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        }
751        @Override public int size() {
752            return tableOfContents.protoIds.size;
753        }
754    }
755
756    private final class FieldIdTable extends AbstractList<FieldId> implements RandomAccess {
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        }
762        @Override public int size() {
763            return tableOfContents.fieldIds.size;
764        }
765    }
766
767    private final class MethodIdTable extends AbstractList<MethodId> implements RandomAccess {
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        }
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
783        public boolean hasNext() {
784            return count < tableOfContents.classDefs.size;
785        }
786        @Override
787        public ClassDef next() {
788            if (!hasNext()) {
789                throw new NoSuchElementException();
790            }
791            count++;
792            return in.readClassDef();
793        }
794        @Override
795            public void remove() {
796            throw new UnsupportedOperationException();
797        }
798    }
799
800    private final class ClassDefIterable implements Iterable<ClassDef> {
801        @Override
802        public Iterator<ClassDef> iterator() {
803            return !tableOfContents.classDefs.exists()
804               ? Collections.<ClassDef>emptySet().iterator()
805               : new ClassDefIterator();
806        }
807    }
808}
809