DexBackedDexFile.java revision d45a6a60921ac27a4f13360a68e02e8f5fc28454
1/*
2 * Copyright 2012, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.dexbacked;
33
34import com.google.common.io.ByteStreams;
35import org.jf.dexlib2.dexbacked.raw.*;
36import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
37import org.jf.dexlib2.iface.DexFile;
38import org.jf.util.ExceptionWithContext;
39
40import javax.annotation.Nonnull;
41import javax.annotation.Nullable;
42import java.io.EOFException;
43import java.io.IOException;
44import java.io.InputStream;
45import java.util.Set;
46
47public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
48    private final int stringCount;
49    private final int stringStartOffset;
50    private final int typeCount;
51    private final int typeStartOffset;
52    private final int protoCount;
53    private final int protoStartOffset;
54    private final int fieldCount;
55    private final int fieldStartOffset;
56    private final int methodCount;
57    private final int methodStartOffset;
58    private final int classCount;
59    private final int classStartOffset;
60
61    private DexBackedDexFile(@Nonnull byte[] buf, int offset, boolean verifyMagic) {
62        super(buf);
63
64        if (verifyMagic) {
65            verifyMagicAndByteOrder(buf, offset);
66        }
67
68        stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
69        stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET);
70        typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET);
71        typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET);
72        protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET);
73        protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET);
74        fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET);
75        fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET);
76        methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET);
77        methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET);
78        classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET);
79        classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET);
80    }
81
82    public DexBackedDexFile(@Nonnull BaseDexBuffer buf) {
83        this(buf.buf);
84    }
85
86    public DexBackedDexFile(@Nonnull byte[] buf, int offset) {
87        this(buf, offset, false);
88    }
89
90    public DexBackedDexFile(@Nonnull byte[] buf) {
91        this(buf, 0, true);
92    }
93
94    public static DexBackedDexFile fromInputStream(@Nonnull InputStream is) throws IOException {
95        if (!is.markSupported()) {
96            throw new IllegalArgumentException("InputStream must support mark");
97        }
98        is.mark(44);
99        byte[] partialHeader = new byte[44];
100        try {
101            ByteStreams.readFully(is, partialHeader);
102        } catch (EOFException ex) {
103            throw new NotADexFile("File is too short");
104        } finally {
105            is.reset();
106        }
107
108        verifyMagicAndByteOrder(partialHeader, 0);
109
110        byte[] buf = ByteStreams.toByteArray(is);
111        return new DexBackedDexFile(buf, 0, false);
112    }
113
114    public boolean isOdexFile() {
115        return false;
116    }
117
118    @Nonnull
119    @Override
120    public Set<? extends DexBackedClassDef> getClasses() {
121        return new FixedSizeSet<DexBackedClassDef>() {
122            @Nonnull
123            @Override
124            public DexBackedClassDef readItem(int index) {
125                return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index));
126            }
127
128            @Override
129            public int size() {
130                return classCount;
131            }
132        };
133    }
134
135    private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
136        if (!HeaderItem.verifyMagic(buf, offset)) {
137            StringBuilder sb = new StringBuilder("Invalid magic value:");
138            for (int i=0; i<8; i++) {
139                sb.append(String.format(" %02x", buf[i]));
140            }
141            throw new NotADexFile(sb.toString());
142        }
143
144        int endian = HeaderItem.getEndian(buf, offset);
145        if (endian == HeaderItem.BIG_ENDIAN_TAG) {
146            throw new ExceptionWithContext("Big endian dex files are not currently supported");
147        }
148
149        if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
150            throw new ExceptionWithContext("Invalid endian tag: 0x%x", endian);
151        }
152    }
153
154    public int getStringIdItemOffset(int stringIndex) {
155        if (stringIndex < 0 || stringIndex >= stringCount) {
156            throw new ExceptionWithContext("String index out of bounds: %d", stringIndex);
157        }
158        return stringStartOffset + stringIndex*StringIdItem.ITEM_SIZE;
159    }
160
161    public int getTypeIdItemOffset(int typeIndex) {
162        if (typeIndex < 0 || typeIndex >= typeCount) {
163            throw new ExceptionWithContext("Type index out of bounds: %d", typeIndex);
164        }
165        return typeStartOffset + typeIndex*TypeIdItem.ITEM_SIZE;
166    }
167
168    public int getFieldIdItemOffset(int fieldIndex) {
169        if (fieldIndex < 0 || fieldIndex >= fieldCount) {
170            throw new ExceptionWithContext("Field index out of bounds: %d", fieldIndex);
171        }
172        return fieldStartOffset + fieldIndex*FieldIdItem.ITEM_SIZE;
173    }
174
175    public int getMethodIdItemOffset(int methodIndex) {
176        if (methodIndex < 0 || methodIndex >= methodCount) {
177            throw new ExceptionWithContext("Method index out of bounds: %d", methodIndex);
178        }
179        return methodStartOffset + methodIndex*MethodIdItem.ITEM_SIZE;
180    }
181
182    public int getProtoIdItemOffset(int protoIndex) {
183        if (protoIndex < 0 || protoIndex >= protoCount) {
184            throw new ExceptionWithContext("Proto index out of bounds: %d", protoIndex);
185        }
186        return protoStartOffset + protoIndex*ProtoIdItem.ITEM_SIZE;
187    }
188
189    public int getClassDefItemOffset(int classIndex) {
190        if (classIndex < 0 || classIndex >= classCount) {
191            throw new ExceptionWithContext("Class index out of bounds: %d", classIndex);
192        }
193        return classStartOffset + classIndex*ClassDefItem.ITEM_SIZE;
194    }
195
196    public int getClassCount() {
197        return classCount;
198    }
199
200    @Nonnull
201    public String getString(int stringIndex) {
202        int stringOffset = getStringIdItemOffset(stringIndex);
203        int stringDataOffset = readSmallUint(stringOffset);
204        DexReader reader = readerAt(stringDataOffset);
205        int utf16Length = reader.readSmallUleb128();
206        return reader.readString(utf16Length);
207    }
208
209    @Nullable
210    public String getOptionalString(int stringIndex) {
211        if (stringIndex == -1) {
212            return null;
213        }
214        return getString(stringIndex);
215    }
216
217    @Nonnull
218    public String getType(int typeIndex) {
219        int typeOffset = getTypeIdItemOffset(typeIndex);
220        int stringIndex = readSmallUint(typeOffset);
221        return getString(stringIndex);
222    }
223
224    @Nullable
225    public String getOptionalType(int typeIndex) {
226        if (typeIndex == -1) {
227            return null;
228        }
229        return getType(typeIndex);
230    }
231
232    @Override
233    @Nonnull
234    public DexReader readerAt(int offset) {
235        return new DexReader(this, offset);
236    }
237
238    public static class NotADexFile extends RuntimeException {
239        public NotADexFile() {
240        }
241
242        public NotADexFile(Throwable cause) {
243            super(cause);
244        }
245
246        public NotADexFile(String message) {
247            super(message);
248        }
249
250        public NotADexFile(String message, Throwable cause) {
251            super(message, cause);
252        }
253    }
254}
255