DexBuffer.java revision d43341a24abe339d474b0b0d92669917ae2eb9bf
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.dx.io;
18
19import com.android.dx.dex.DexFormat;
20import com.android.dx.dex.SizeOf;
21import com.android.dx.dex.TableOfContents;
22import com.android.dx.io.Code.CatchHandler;
23import com.android.dx.io.Code.Try;
24import com.android.dx.merge.TypeList;
25import com.android.dx.util.ByteInput;
26import com.android.dx.util.ByteOutput;
27import com.android.dx.util.DexException;
28import com.android.dx.util.FileUtils;
29import com.android.dx.util.Leb128Utils;
30import com.android.dx.util.Mutf8;
31import java.io.ByteArrayOutputStream;
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.io.InputStream;
37import java.io.OutputStream;
38import java.io.UTFDataFormatException;
39import java.util.AbstractList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.Iterator;
43import java.util.List;
44import java.util.NoSuchElementException;
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 DexBuffer {
53    private byte[] data;
54    private final TableOfContents tableOfContents = new TableOfContents();
55    private int length = 0;
56
57    private final List<String> strings = new AbstractList<String>() {
58        @Override public String get(int index) {
59            checkBounds(index, tableOfContents.stringIds.size);
60            return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM))
61                    .readString();
62        }
63        @Override public int size() {
64            return tableOfContents.stringIds.size;
65        }
66    };
67
68    private final List<Integer> typeIds = new AbstractList<Integer>() {
69        @Override public Integer get(int index) {
70            checkBounds(index, tableOfContents.typeIds.size);
71            return open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();
72        }
73        @Override public int size() {
74            return tableOfContents.typeIds.size;
75        }
76    };
77
78    private final List<String> typeNames = new AbstractList<String>() {
79        @Override public String get(int index) {
80            checkBounds(index, tableOfContents.typeIds.size);
81            return strings.get(typeIds.get(index));
82        }
83        @Override public int size() {
84            return tableOfContents.typeIds.size;
85        }
86    };
87
88    private final List<ProtoId> protoIds = new AbstractList<ProtoId>() {
89        @Override public ProtoId get(int index) {
90            checkBounds(index, tableOfContents.protoIds.size);
91            return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index))
92                    .readProtoId();
93        }
94        @Override public int size() {
95            return tableOfContents.protoIds.size;
96        }
97    };
98
99    private final List<FieldId> fieldIds = new AbstractList<FieldId>() {
100        @Override public FieldId get(int index) {
101            checkBounds(index, tableOfContents.fieldIds.size);
102            return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index))
103                    .readFieldId();
104        }
105        @Override public int size() {
106            return tableOfContents.fieldIds.size;
107        }
108    };
109
110    private final List<MethodId> methodIds = new AbstractList<MethodId>() {
111        @Override public MethodId get(int index) {
112            checkBounds(index, tableOfContents.methodIds.size);
113            return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index))
114                    .readMethodId();
115        }
116        @Override public int size() {
117            return tableOfContents.methodIds.size;
118        }
119    };
120
121    /**
122     * Creates a new dex buffer defining no classes.
123     */
124    public DexBuffer() {
125        this.data = new byte[0];
126    }
127
128    /**
129     * Creates a new dex buffer that reads from {@code data}. It is an error to
130     * modify {@code data} after using it to create a dex buffer.
131     */
132    public DexBuffer(byte[] data) throws IOException {
133        this.data = data;
134        this.length = data.length;
135        this.tableOfContents.readFrom(this);
136    }
137
138    /**
139     * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}.
140     */
141    public DexBuffer(InputStream in) throws IOException {
142        loadFrom(in);
143    }
144
145    /**
146     * Creates a new dex buffer from the dex file {@code file}.
147     */
148    public DexBuffer(File file) throws IOException {
149        if (FileUtils.hasArchiveSuffix(file.getName())) {
150            ZipFile zipFile = new ZipFile(file);
151            ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME);
152            if (entry != null) {
153                loadFrom(zipFile.getInputStream(entry));
154                zipFile.close();
155            } else {
156                throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file);
157            }
158        } else if (file.getName().endsWith(".dex")) {
159            loadFrom(new FileInputStream(file));
160        } else {
161            throw new DexException("unknown output extension: " + file);
162        }
163    }
164
165    private void loadFrom(InputStream in) throws IOException {
166        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
167        byte[] buffer = new byte[8192];
168
169        int count;
170        while ((count = in.read(buffer)) != -1) {
171            bytesOut.write(buffer, 0, count);
172        }
173        in.close();
174
175        this.data = bytesOut.toByteArray();
176        this.length = data.length;
177        this.tableOfContents.readFrom(this);
178    }
179
180    private static void checkBounds(int index, int length) {
181        if (index < 0 || index >= length) {
182            throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
183        }
184    }
185
186    public void writeTo(OutputStream out) throws IOException {
187        out.write(data);
188    }
189
190    public void writeTo(File dexOut) throws IOException {
191        OutputStream out = new FileOutputStream(dexOut);
192        writeTo(out);
193        out.close();
194    }
195
196    public TableOfContents getTableOfContents() {
197        return tableOfContents;
198    }
199
200    public Section open(int position) {
201        if (position < 0 || position > length) {
202            throw new IllegalArgumentException("position=" + position + " length=" + length);
203        }
204        return new Section(position);
205    }
206
207    public Section appendSection(int maxByteCount, String name) {
208        int limit = fourByteAlign(length + maxByteCount);
209        Section result = new Section(name, length, limit);
210        length = limit;
211        return result;
212    }
213
214    public void noMoreSections() {
215        data = new byte[length];
216    }
217
218    public int getLength() {
219        return length;
220    }
221
222    public static int fourByteAlign(int position) {
223        return (position + 3) & ~3;
224    }
225
226    public byte[] getBytes() {
227        return data;
228    }
229
230    public List<String> strings() {
231        return strings;
232    }
233
234    public List<Integer> typeIds() {
235        return typeIds;
236    }
237
238    public List<String> typeNames() {
239        return typeNames;
240    }
241
242    public List<ProtoId> protoIds() {
243        return protoIds;
244    }
245
246    public List<FieldId> fieldIds() {
247        return fieldIds;
248    }
249
250    public List<MethodId> methodIds() {
251        return methodIds;
252    }
253
254    public Iterable<ClassDef> classDefs() {
255        return new Iterable<ClassDef>() {
256            public Iterator<ClassDef> iterator() {
257                if (!tableOfContents.classDefs.exists()) {
258                    return Collections.<ClassDef>emptySet().iterator();
259                }
260                return new Iterator<ClassDef>() {
261                    private DexBuffer.Section in = open(tableOfContents.classDefs.off);
262                    private int count = 0;
263
264                    public boolean hasNext() {
265                        return count < tableOfContents.classDefs.size;
266                    }
267                    public ClassDef next() {
268                        if (!hasNext()) {
269                            throw new NoSuchElementException();
270                        }
271                        count++;
272                        return in.readClassDef();
273                    }
274                    public void remove() {
275                        throw new UnsupportedOperationException();
276                    }
277                };
278            }
279        };
280    }
281
282    public TypeList readTypeList(int offset) {
283        if (offset == 0) {
284            return TypeList.EMPTY;
285        }
286        return open(offset).readTypeList();
287    }
288
289    public ClassData readClassData(ClassDef classDef) {
290        int offset = classDef.getClassDataOffset();
291        if (offset == 0) {
292            throw new IllegalArgumentException("offset == 0");
293        }
294        return open(offset).readClassData();
295    }
296
297    public Code readCode(ClassData.Method method) {
298        int offset = method.getCodeOffset();
299        if (offset == 0) {
300            throw new IllegalArgumentException("offset == 0");
301        }
302        return open(offset).readCode();
303    }
304
305    public final class Section implements ByteInput, ByteOutput {
306        private final String name;
307        private int position;
308        private final int limit;
309        private final int initialPosition;
310
311        private Section(String name, int position, int limit) {
312            this.name = name;
313            this.position = this.initialPosition = position;
314            this.limit = limit;
315        }
316
317        private Section(int position) {
318            this("section", position, data.length);
319        }
320
321        public int getPosition() {
322            return position;
323        }
324
325        public int readInt() {
326            int result = (data[position] & 0xff)
327                    | (data[position + 1] & 0xff) << 8
328                    | (data[position + 2] & 0xff) << 16
329                    | (data[position + 3] & 0xff) << 24;
330            position += 4;
331            return result;
332        }
333
334        public short readShort() {
335            int result = (data[position] & 0xff)
336                    | (data[position + 1] & 0xff) << 8;
337            position += 2;
338            return (short) result;
339        }
340
341        public int readUnsignedShort() {
342            return readShort() & 0xffff;
343        }
344
345        public byte readByte() {
346            return (byte) (data[position++] & 0xff);
347        }
348
349        public byte[] readByteArray(int length) {
350            byte[] result = Arrays.copyOfRange(data, position, position + length);
351            position += length;
352            return result;
353        }
354
355        public short[] readShortArray(int length) {
356            short[] result = new short[length];
357            for (int i = 0; i < length; i++) {
358                result[i] = readShort();
359            }
360            return result;
361        }
362
363        public int readUleb128() {
364            return Leb128Utils.readUnsignedLeb128(this);
365        }
366
367        public int readUleb128p1() {
368            return Leb128Utils.readUnsignedLeb128(this) - 1;
369        }
370
371        public int readSleb128() {
372            return Leb128Utils.readSignedLeb128(this);
373        }
374
375        public TypeList readTypeList() {
376            int size = readInt();
377            short[] types = new short[size];
378            for (int i = 0; i < size; i++) {
379                types[i] = readShort();
380            }
381            alignToFourBytes();
382            return new TypeList(DexBuffer.this, types);
383        }
384
385        public String readString() {
386            int offset = readInt();
387            int savedPosition = position;
388            position = offset;
389            try {
390                int expectedLength = readUleb128();
391                String result = Mutf8.decode(this, new char[expectedLength]);
392                if (result.length() != expectedLength) {
393                    throw new DexException("Declared length " + expectedLength
394                            + " doesn't match decoded length of " + result.length());
395                }
396                return result;
397            } catch (UTFDataFormatException e) {
398                throw new DexException(e);
399            } finally {
400                position = savedPosition;
401            }
402        }
403
404        public FieldId readFieldId() {
405            int declaringClassIndex = readUnsignedShort();
406            int typeIndex = readUnsignedShort();
407            int nameIndex = readInt();
408            return new FieldId(DexBuffer.this, declaringClassIndex, typeIndex, nameIndex);
409        }
410
411        public MethodId readMethodId() {
412            int declaringClassIndex = readUnsignedShort();
413            int protoIndex = readUnsignedShort();
414            int nameIndex = readInt();
415            return new MethodId(DexBuffer.this, declaringClassIndex, protoIndex, nameIndex);
416        }
417
418        public ProtoId readProtoId() {
419            int shortyIndex = readInt();
420            int returnTypeIndex = readInt();
421            int parametersOffset = readInt();
422            return new ProtoId(DexBuffer.this, shortyIndex, returnTypeIndex, parametersOffset);
423        }
424
425        public ClassDef readClassDef() {
426            int offset = getPosition();
427            int type = readInt();
428            int accessFlags = readInt();
429            int supertype = readInt();
430            int interfacesOffset = readInt();
431            int sourceFileIndex = readInt();
432            int annotationsOffset = readInt();
433            int classDataOffset = readInt();
434            int staticValuesOffset = readInt();
435            return new ClassDef(DexBuffer.this, offset, type, accessFlags, supertype,
436                    interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset,
437                    staticValuesOffset);
438        }
439
440        private Code readCode() {
441            int registersSize = readUnsignedShort();
442            int insSize = readUnsignedShort();
443            int outsSize = readUnsignedShort();
444            int triesSize = readUnsignedShort();
445            int debugInfoOffset = readInt();
446            int instructionsSize = readInt();
447            short[] instructions = readShortArray(instructionsSize);
448            Try[] tries;
449            CatchHandler[] catchHandlers;
450            if (triesSize > 0) {
451                if (instructions.length % 2 == 1) {
452                    readShort(); // padding
453                }
454
455                /*
456                 * We can't read the tries until we've read the catch handlers.
457                 * Unfortunately they're in the opposite order in the dex file
458                 * so we need to read them out-of-order.
459                 */
460                Section triesSection = open(position);
461                skip(triesSize * SizeOf.TRY_ITEM);
462                catchHandlers = readCatchHandlers();
463                tries = triesSection.readTries(triesSize, catchHandlers);
464            } else {
465                tries = new Try[0];
466                catchHandlers = new CatchHandler[0];
467            }
468            return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
469                    tries, catchHandlers);
470        }
471
472        private CatchHandler[] readCatchHandlers() {
473            int baseOffset = position;
474            int catchHandlersSize = readUleb128();
475            CatchHandler[] result = new CatchHandler[catchHandlersSize];
476            for (int i = 0; i < catchHandlersSize; i++) {
477                int offset = position - baseOffset;
478                result[i] = readCatchHandler(offset);
479            }
480            return result;
481        }
482
483        private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) {
484            Try[] result = new Try[triesSize];
485            for (int i = 0; i < triesSize; i++) {
486                int startAddress = readInt();
487                int instructionCount = readUnsignedShort();
488                int handlerOffset = readUnsignedShort();
489                int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset);
490                result[i] = new Try(startAddress, instructionCount, catchHandlerIndex);
491            }
492            return result;
493        }
494
495        private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) {
496            for (int i = 0; i < catchHandlers.length; i++) {
497                CatchHandler catchHandler = catchHandlers[i];
498                if (catchHandler.getOffset() == offset) {
499                    return i;
500                }
501            }
502            throw new IllegalArgumentException();
503        }
504
505        private CatchHandler readCatchHandler(int offset) {
506            int size = readSleb128();
507            int handlersCount = Math.abs(size);
508            int[] typeIndexes = new int[handlersCount];
509            int[] addresses = new int[handlersCount];
510            for (int i = 0; i < handlersCount; i++) {
511                typeIndexes[i] = readUleb128();
512                addresses[i] = readUleb128();
513            }
514            int catchAllAddress = size <= 0 ? readUleb128() : -1;
515            return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset);
516        }
517
518        private ClassData readClassData() {
519            int staticFieldsSize = readUleb128();
520            int instanceFieldsSize = readUleb128();
521            int directMethodsSize = readUleb128();
522            int virtualMethodsSize = readUleb128();
523            ClassData.Field[] staticFields = readFields(staticFieldsSize);
524            ClassData.Field[] instanceFields = readFields(instanceFieldsSize);
525            ClassData.Method[] directMethods = readMethods(directMethodsSize);
526            ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize);
527            return new ClassData(staticFields, instanceFields, directMethods, virtualMethods);
528        }
529
530        private ClassData.Field[] readFields(int count) {
531            ClassData.Field[] result = new ClassData.Field[count];
532            int fieldIndex = 0;
533            for (int i = 0; i < count; i++) {
534                fieldIndex += readUleb128(); // field index diff
535                int accessFlags = readUleb128();
536                result[i] = new ClassData.Field(fieldIndex, accessFlags);
537            }
538            return result;
539        }
540
541        private ClassData.Method[] readMethods(int count) {
542            ClassData.Method[] result = new ClassData.Method[count];
543            int methodIndex = 0;
544            for (int i = 0; i < count; i++) {
545                methodIndex += readUleb128(); // method index diff
546                int accessFlags = readUleb128();
547                int codeOff = readUleb128();
548                result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff);
549            }
550            return result;
551        }
552
553        public Annotation readAnnotation() {
554            byte visibility = readByte();
555            int typeIndex = readUleb128();
556            int size = readUleb128();
557            int[] names = new int[size];
558            EncodedValue[] values = new EncodedValue[size];
559            for (int i = 0; i < size; i++) {
560                names[i] = readUleb128();
561                values[i] = readEncodedValue();
562            }
563            return new Annotation(DexBuffer.this, visibility, typeIndex, names, values);
564        }
565
566        public EncodedValue readEncodedValue() {
567            int start = position;
568            new EncodedValueReader(this).readValue();
569            int end = position;
570            return new EncodedValue(Arrays.copyOfRange(data, start, end));
571        }
572
573        public EncodedValue readEncodedArray() {
574            int start = position;
575            new EncodedValueReader(this).readArray();
576            int end = position;
577            return new EncodedValue(Arrays.copyOfRange(data, start, end));
578        }
579
580        private void ensureCapacity(int size) {
581            if (position + size > limit) {
582                throw new DexException("Section limit " + limit + " exceeded by " + name);
583            }
584        }
585
586        public void skip(int count) {
587            if (count < 0) {
588                throw new IllegalArgumentException();
589            }
590            ensureCapacity(count);
591            position += count;
592        }
593
594        /**
595         * Writes 0x00 until the position is aligned to a multiple of 4.
596         */
597        public void alignToFourBytes() {
598            int unalignedCount = position;
599            position = DexBuffer.fourByteAlign(position);
600            for (int i = unalignedCount; i < position; i++) {
601                data[i] = 0;
602            }
603        }
604
605        public void assertFourByteAligned() {
606            if ((position & 3) != 0) {
607                throw new IllegalStateException("Not four byte aligned!");
608            }
609        }
610
611        public void write(byte[] bytes) {
612            ensureCapacity(bytes.length);
613            System.arraycopy(bytes, 0, data, position, bytes.length);
614            position += bytes.length;
615        }
616
617        public void writeByte(int b) {
618            ensureCapacity(1);
619            data[position++] = (byte) b;
620        }
621
622        public void writeShort(short i) {
623            ensureCapacity(2);
624            data[position    ] = (byte) i;
625            data[position + 1] = (byte) (i >>> 8);
626            position += 2;
627        }
628
629        public void writeUnsignedShort(int i) {
630            short s = (short) i;
631            if (i != (s & 0xffff)) {
632                throw new IllegalArgumentException("Expected an unsigned short: " + i);
633            }
634            writeShort(s);
635        }
636
637        public void write(short[] shorts) {
638            for (short s : shorts) {
639                writeShort(s);
640            }
641        }
642
643        public void writeInt(int i) {
644            ensureCapacity(4);
645            data[position    ] = (byte) i;
646            data[position + 1] = (byte) (i >>>  8);
647            data[position + 2] = (byte) (i >>> 16);
648            data[position + 3] = (byte) (i >>> 24);
649            position += 4;
650        }
651
652        public void writeUleb128(int i) {
653            try {
654                Leb128Utils.writeUnsignedLeb128(this, i);
655                ensureCapacity(0);
656            } catch (ArrayIndexOutOfBoundsException e) {
657                throw new DexException("Section limit " + limit + " exceeded by " + name);
658            }
659        }
660
661        public void writeUleb128p1(int i) {
662            writeUleb128(i + 1);
663        }
664
665        public void writeSleb128(int i) {
666            try {
667                Leb128Utils.writeSignedLeb128(this, i);
668                ensureCapacity(0);
669            } catch (ArrayIndexOutOfBoundsException e) {
670                throw new DexException("Section limit " + limit + " exceeded by " + name);
671            }
672        }
673
674        public void writeStringData(String value) {
675            try {
676                int length = value.length();
677                writeUleb128(length);
678                write(Mutf8.encode(value));
679                writeByte(0);
680            } catch (UTFDataFormatException e) {
681                throw new AssertionError();
682            }
683        }
684
685        public void writeTypeList(TypeList typeList) {
686            short[] types = typeList.getTypes();
687            writeInt(types.length);
688            for (short type : types) {
689                writeShort(type);
690            }
691            alignToFourBytes();
692        }
693
694        /**
695         * Returns the number of bytes remaining in this section.
696         */
697        public int remaining() {
698            return limit - position;
699        }
700
701        /**
702         * Returns the number of bytes used by this section.
703         */
704        public int used () {
705            return position - initialPosition;
706        }
707    }
708}
709