• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2012, Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google LLC nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 package com.android.tools.smali.dexlib2.dexbacked;
32 
33 import com.android.tools.smali.dexlib2.Opcodes;
34 import com.android.tools.smali.dexlib2.ReferenceType;
35 import com.android.tools.smali.dexlib2.dexbacked.raw.CallSiteIdItem;
36 import com.android.tools.smali.dexlib2.dexbacked.raw.ClassDefItem;
37 import com.android.tools.smali.dexlib2.dexbacked.raw.FieldIdItem;
38 import com.android.tools.smali.dexlib2.dexbacked.raw.HeaderItem;
39 import com.android.tools.smali.dexlib2.dexbacked.raw.HiddenApiClassDataItem;
40 import com.android.tools.smali.dexlib2.dexbacked.raw.ItemType;
41 import com.android.tools.smali.dexlib2.dexbacked.raw.MapItem;
42 import com.android.tools.smali.dexlib2.dexbacked.raw.MethodHandleItem;
43 import com.android.tools.smali.dexlib2.dexbacked.raw.MethodIdItem;
44 import com.android.tools.smali.dexlib2.dexbacked.raw.ProtoIdItem;
45 import com.android.tools.smali.dexlib2.dexbacked.raw.StringIdItem;
46 import com.android.tools.smali.dexlib2.dexbacked.raw.TypeIdItem;
47 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedCallSiteReference;
48 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedFieldReference;
49 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodHandleReference;
50 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodProtoReference;
51 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference;
52 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedStringReference;
53 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedTypeReference;
54 import com.android.tools.smali.dexlib2.dexbacked.util.FixedSizeList;
55 import com.android.tools.smali.dexlib2.dexbacked.util.FixedSizeSet;
56 import com.android.tools.smali.dexlib2.iface.DexFile;
57 import com.android.tools.smali.dexlib2.iface.reference.Reference;
58 import com.android.tools.smali.dexlib2.util.DexUtil;
59 import com.android.tools.smali.dexlib2.writer.DexWriter;
60 import com.android.tools.smali.util.InputStreamUtil;
61 
62 import javax.annotation.Nonnull;
63 import javax.annotation.Nullable;
64 
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.util.AbstractList;
68 import java.util.List;
69 import java.util.Set;
70 
71 public class DexBackedDexFile implements DexFile {
72 
73     private final DexBuffer dexBuffer;
74     private final DexBuffer dataBuffer;
75 
76     @Nonnull private final Opcodes opcodes;
77 
78     private final int fileSize;
79     private final int stringCount;
80     private final int stringStartOffset;
81     private final int typeCount;
82     private final int typeStartOffset;
83     private final int protoCount;
84     private final int protoStartOffset;
85     private final int fieldCount;
86     private final int fieldStartOffset;
87     private final int methodCount;
88     private final int methodStartOffset;
89     private final int classCount;
90     private final int classStartOffset;
91     private final int mapOffset;
92     private final int hiddenApiRestrictionsOffset;
93 
DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic)94     protected DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
95         this(opcodes, buf, offset, verifyMagic, 0);
96     }
97 
DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic, int header_offset)98     protected DexBackedDexFile(@Nullable Opcodes opcodes,
99                                @Nonnull byte[] buf,
100                                int offset,
101                                boolean verifyMagic,
102                                int header_offset) {
103         dexBuffer = new DexBuffer(buf, offset);
104         dataBuffer = new DexBuffer(buf, offset + getBaseDataOffset());
105 
106         int dexVersion = getVersion(buf, offset, verifyMagic);
107 
108         if (opcodes == null) {
109             this.opcodes = getDefaultOpcodes(dexVersion);
110         } else {
111             this.opcodes = opcodes;
112         }
113 
114         fileSize = dexBuffer.readSmallUint(header_offset + HeaderItem.FILE_SIZE_OFFSET);
115         stringCount = dexBuffer.readSmallUint(header_offset + HeaderItem.STRING_COUNT_OFFSET);
116         stringStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.STRING_START_OFFSET);
117         typeCount = dexBuffer.readSmallUint(header_offset + HeaderItem.TYPE_COUNT_OFFSET);
118         typeStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.TYPE_START_OFFSET);
119         protoCount = dexBuffer.readSmallUint(header_offset + HeaderItem.PROTO_COUNT_OFFSET);
120         protoStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.PROTO_START_OFFSET);
121         fieldCount = dexBuffer.readSmallUint(header_offset + HeaderItem.FIELD_COUNT_OFFSET);
122         fieldStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.FIELD_START_OFFSET);
123         methodCount = dexBuffer.readSmallUint(header_offset + HeaderItem.METHOD_COUNT_OFFSET);
124         methodStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.METHOD_START_OFFSET);
125         classCount = dexBuffer.readSmallUint(header_offset + HeaderItem.CLASS_COUNT_OFFSET);
126         classStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.CLASS_START_OFFSET);
127         mapOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.MAP_OFFSET);
128 
129         MapItem mapItem = getMapItemForSection(ItemType.HIDDENAPI_CLASS_DATA_ITEM);
130         if (mapItem != null) {
131             hiddenApiRestrictionsOffset = mapItem.getOffset();
132         } else {
133             hiddenApiRestrictionsOffset = DexWriter.NO_OFFSET;
134         }
135 
136         int container_off = 0;
137         if (dexVersion >= 41) {
138           container_off = dexBuffer.readSmallUint(header_offset + HeaderItem.CONTAINER_OFF_OFFSET);
139         }
140         if (container_off != header_offset) {
141           throw new DexUtil.InvalidFile(String.format("Unexpected container offset in header"));
142         }
143     }
144 
145     /**
146      * @return The offset that various data offsets are relative to. This is always 0 for a dex file, but may be
147      * different for other related formats (e.g. cdex).
148      */
getBaseDataOffset()149     public int getBaseDataOffset() {
150         return 0;
151     }
152 
153     /**
154      * @return Size of single dex file (out of potentially several dex files within a container).
155      */
getFileSize()156     public int getFileSize() {
157         return fileSize;
158     }
159 
getVersion(byte[] buf, int offset, boolean verifyMagic)160     protected int getVersion(byte[] buf, int offset, boolean verifyMagic) {
161         if (verifyMagic) {
162             return DexUtil.verifyDexHeader(buf, offset);
163         } else {
164             return HeaderItem.getVersion(buf, offset);
165         }
166     }
167 
getDefaultOpcodes(int version)168     protected Opcodes getDefaultOpcodes(int version) {
169         return Opcodes.forDexVersion(version);
170     }
171 
getBuffer()172     public DexBuffer getBuffer() {
173         return dexBuffer;
174     }
175 
getDataBuffer()176     public DexBuffer getDataBuffer() {
177         return dataBuffer;
178     }
179 
DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull DexBuffer buf)180     public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull DexBuffer buf) {
181         this(opcodes, buf.buf, buf.baseOffset);
182     }
183 
DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset)184     public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf, int offset) {
185         this(opcodes, buf, offset, false);
186     }
187 
DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf)188     public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf) {
189         this(opcodes, buf, 0, true);
190     }
191 
192     @Nonnull
fromInputStream(@ullable Opcodes opcodes, @Nonnull InputStream is)193     public static DexBackedDexFile fromInputStream(@Nullable Opcodes opcodes, @Nonnull InputStream is)
194             throws IOException {
195         DexUtil.verifyDexHeader(is);
196 
197         byte[] buf = InputStreamUtil.toByteArray(is);
198         return new DexBackedDexFile(opcodes, buf, 0, false);
199     }
200 
getOpcodes()201     @Nonnull public Opcodes getOpcodes() {
202         return opcodes;
203     }
204 
supportsOptimizedOpcodes()205     public boolean supportsOptimizedOpcodes() {
206         return false;
207     }
208 
209     @Nonnull
getClasses()210     public Set<? extends DexBackedClassDef> getClasses() {
211         return new FixedSizeSet<DexBackedClassDef>() {
212             @Nonnull
213             @Override
214             public DexBackedClassDef readItem(int index) {
215                 return getClassSection().get(index);
216             }
217 
218             @Override
219             public int size() {
220                 return classCount;
221             }
222         };
223     }
224 
225     public List<DexBackedStringReference> getStringReferences() {
226         return new AbstractList<DexBackedStringReference>() {
227             @Override public DexBackedStringReference get(int index) {
228                 if (index < 0 || index >= getStringSection().size()) {
229                     throw new IndexOutOfBoundsException();
230                 }
231                 return new DexBackedStringReference(DexBackedDexFile.this, index);
232             }
233 
234             @Override public int size() {
235                 return getStringSection().size();
236             }
237         };
238     }
239 
240     public List<DexBackedTypeReference> getTypeReferences() {
241         return new AbstractList<DexBackedTypeReference>() {
242             @Override public DexBackedTypeReference get(int index) {
243                 if (index < 0 || index >= getTypeSection().size()) {
244                     throw new IndexOutOfBoundsException();
245                 }
246                 return new DexBackedTypeReference(DexBackedDexFile.this, index);
247             }
248 
249             @Override public int size() {
250                 return getTypeSection().size();
251             }
252         };
253     }
254 
255     public List<? extends Reference> getReferences(int referenceType) {
256         switch (referenceType) {
257             case ReferenceType.STRING:
258                 return getStringReferences();
259             case ReferenceType.TYPE:
260                 return getTypeReferences();
261             case ReferenceType.METHOD:
262                 return getMethodSection();
263             case ReferenceType.FIELD:
264                 return getFieldSection();
265             case ReferenceType.METHOD_PROTO:
266                 return getMethodSection();
267             case ReferenceType.METHOD_HANDLE:
268                 return getMethodHandleSection();
269             case ReferenceType.CALL_SITE:
270                 return getCallSiteSection();
271             default:
272                 throw new IllegalArgumentException(String.format("Invalid reference type: %d", referenceType));
273         }
274     }
275 
276     public List<MapItem> getMapItems() {
277         final int mapSize = dataBuffer.readSmallUint(mapOffset);
278 
279         return new FixedSizeList<MapItem>() {
280             @Override
281             public MapItem readItem(int index) {
282                 int mapItemOffset = mapOffset + 4 + index * MapItem.ITEM_SIZE;
283                 return new MapItem(DexBackedDexFile.this, mapItemOffset);
284             }
285 
286             @Override public int size() {
287                 return mapSize;
288             }
289         };
290     }
291 
292     @Nullable
293     public MapItem getMapItemForSection(int itemType) {
294         for (MapItem mapItem: getMapItems()) {
295             if (mapItem.getType() == itemType) {
296                 return mapItem;
297             }
298         }
299         return null;
300     }
301 
302     public static class NotADexFile extends RuntimeException {
303         public NotADexFile() {
304         }
305 
306         public NotADexFile(Throwable cause) {
307             super(cause);
308         }
309 
310         public NotADexFile(String message) {
311             super(message);
312         }
313 
314         public NotADexFile(String message, Throwable cause) {
315             super(message, cause);
316         }
317     }
318 
319     private OptionalIndexedSection<String> stringSection = new OptionalIndexedSection<String>() {
320         @Override
321         public String get(int index) {
322             int stringOffset = getOffset(index);
323             int stringDataOffset = dexBuffer.readSmallUint(stringOffset);
324             DexReader<? extends DexBuffer> reader = dataBuffer.readerAt(stringDataOffset);
325             int utf16Length = reader.readSmallUleb128();
326             return reader.readString(utf16Length);
327         }
328 
329         @Override
330         public int size() {
331             return stringCount;
332         }
333 
334         @Nullable
335         @Override
336         public String getOptional(int index) {
337             if (index == -1) {
338                 return null;
339             }
340             return get(index);
341         }
342 
343         @Override
344         public int getOffset(int index) {
345             if (index < 0 || index >= size()) {
346                 throw new IndexOutOfBoundsException(
347                         String.format("Invalid string index %d, not in [0, %d)", index, size()));
348             }
349             return stringStartOffset + index* StringIdItem.ITEM_SIZE;
350         }
351     };
352 
353     public OptionalIndexedSection<String> getStringSection() {
354         return stringSection;
355     }
356 
357     private OptionalIndexedSection<String> typeSection = new OptionalIndexedSection<String>() {
358         @Override
359         public String get(int index) {
360             int typeOffset = getOffset(index);
361             int stringIndex = dexBuffer.readSmallUint(typeOffset);
362             return getStringSection().get(stringIndex);
363         }
364 
365         @Override
366         public int size() {
367             return typeCount;
368         }
369 
370         @Nullable
371         @Override
372         public String getOptional(int index) {
373             if (index == -1) {
374                 return null;
375             }
376             return get(index);
377         }
378 
379         @Override
380         public int getOffset(int index) {
381             if (index < 0 || index >= size()) {
382                 throw new IndexOutOfBoundsException(
383                         String.format("Invalid type index %d, not in [0, %d)", index, size()));
384             }
385             return typeStartOffset + index * TypeIdItem.ITEM_SIZE;
386         }
387     };
388 
389     public OptionalIndexedSection<String> getTypeSection() {
390         return typeSection;
391     }
392 
393     private IndexedSection<DexBackedFieldReference> fieldSection = new IndexedSection<DexBackedFieldReference>() {
394         @Override
395         public DexBackedFieldReference get(int index) {
396             return new DexBackedFieldReference(DexBackedDexFile.this, index);
397         }
398 
399         @Override
400         public int size() {
401             return fieldCount;
402         }
403 
404         @Override
405         public int getOffset(int index) {
406             if (index < 0 || index >= size()) {
407                 throw new IndexOutOfBoundsException(
408                         String.format("Invalid field index %d, not in [0, %d)", index, size()));
409             }
410 
411             return fieldStartOffset + index * FieldIdItem.ITEM_SIZE;
412         }
413     };
414 
415     public IndexedSection<DexBackedFieldReference> getFieldSection() {
416         return fieldSection;
417     }
418 
419     private IndexedSection<DexBackedMethodReference> methodSection = new IndexedSection<DexBackedMethodReference>() {
420         @Override
421         public DexBackedMethodReference get(int index) {
422             return new DexBackedMethodReference(DexBackedDexFile.this, index);
423         }
424 
425         @Override
426         public int size() {
427             return methodCount;
428         }
429 
430         @Override
431         public int getOffset(int index) {
432             if (index < 0 || index >= size()) {
433                 throw new IndexOutOfBoundsException(
434                         String.format("Invalid method index %d, not in [0, %d)", index, size()));
435             }
436 
437             return methodStartOffset + index * MethodIdItem.ITEM_SIZE;
438         }
439     };
440 
441     public IndexedSection<DexBackedMethodReference> getMethodSection() {
442         return methodSection;
443     }
444 
445     private IndexedSection<DexBackedMethodProtoReference> protoSection =
446             new IndexedSection<DexBackedMethodProtoReference>() {
447                 @Override
448                 public DexBackedMethodProtoReference get(int index) {
449                     return new DexBackedMethodProtoReference(DexBackedDexFile.this, index);
450                 }
451 
452                 @Override
453                 public int size() {
454                     return protoCount;
455                 }
456 
457                 @Override
458                 public int getOffset(int index) {
459                     if (index < 0 || index >= size()) {
460                         throw new IndexOutOfBoundsException(
461                                 String.format("Invalid proto index %d, not in [0, %d)", index, size()));
462                     }
463 
464                     return protoStartOffset + index * ProtoIdItem.ITEM_SIZE;
465                 }
466             };
467 
468     public IndexedSection<DexBackedMethodProtoReference> getProtoSection() {
469         return protoSection;
470     }
471 
472     private IndexedSection<DexBackedClassDef> classSection = new IndexedSection<DexBackedClassDef>() {
473         @Override
474         public DexBackedClassDef get(int index) {
475             return new DexBackedClassDef(DexBackedDexFile.this, getOffset(index),
476                     readHiddenApiRestrictionsOffset(index));
477         }
478 
479         @Override
480         public int size() {
481             return classCount;
482         }
483 
484         @Override
485         public int getOffset(int index) {
486             if (index < 0 || index >= size()) {
487                 throw new IndexOutOfBoundsException(
488                         String.format("Invalid class index %d, not in [0, %d)", index, size()));
489             }
490 
491             return classStartOffset + index * ClassDefItem.ITEM_SIZE;
492         }
493     };
494 
495     public IndexedSection<DexBackedClassDef> getClassSection() {
496         return classSection;
497     }
498 
499     private IndexedSection<DexBackedCallSiteReference> callSiteSection =
500             new IndexedSection<DexBackedCallSiteReference>() {
501                 @Override
502                 public DexBackedCallSiteReference get(int index) {
503                     return new DexBackedCallSiteReference(DexBackedDexFile.this, index);
504                 }
505 
506                 @Override
507                 public int size() {
508                     MapItem mapItem = getMapItemForSection(ItemType.CALL_SITE_ID_ITEM);
509                     if (mapItem == null) {
510                         return 0;
511                     }
512                     return mapItem.getItemCount();
513                 }
514 
515                 @Override
516                 public int getOffset(int index) {
517                     MapItem mapItem = getMapItemForSection(ItemType.CALL_SITE_ID_ITEM);
518                     if (index < 0 || index >= size()) {
519                         throw new IndexOutOfBoundsException(
520                                 String.format("Invalid callsite index %d, not in [0, %d)", index, size()));
521                     }
522                     return mapItem.getOffset() + index * CallSiteIdItem.ITEM_SIZE;
523                 }
524             };
525 
526     public IndexedSection<DexBackedCallSiteReference> getCallSiteSection() {
527         return callSiteSection;
528     }
529 
530     private IndexedSection<DexBackedMethodHandleReference> methodHandleSection =
531             new IndexedSection<DexBackedMethodHandleReference>() {
532                 @Override
533                 public DexBackedMethodHandleReference get(int index) {
534                     return new DexBackedMethodHandleReference(DexBackedDexFile.this, index);
535                 }
536 
537                 @Override
538                 public int size() {
539                     MapItem mapItem = getMapItemForSection(ItemType.METHOD_HANDLE_ITEM);
540                     if (mapItem == null) {
541                         return 0;
542                     }
543                     return mapItem.getItemCount();
544                 }
545 
546                 @Override
547                 public int getOffset(int index) {
548                     MapItem mapItem = getMapItemForSection(ItemType.METHOD_HANDLE_ITEM);
549                     if (index < 0 || index >= size()) {
550                         throw new IndexOutOfBoundsException(
551                                 String.format("Invalid method handle index %d, not in [0, %d)", index, size()));
552                     }
553                     return mapItem.getOffset() + index * MethodHandleItem.ITEM_SIZE;
554                 }
555             };
556 
557     public IndexedSection<DexBackedMethodHandleReference> getMethodHandleSection() {
558         return methodHandleSection;
559     }
560 
561     protected DexBackedMethodImplementation createMethodImplementation(
562             @Nonnull DexBackedDexFile dexFile, @Nonnull DexBackedMethod method, int codeOffset) {
563         return new DexBackedMethodImplementation(dexFile, method, codeOffset);
564     }
565 
566     private int readHiddenApiRestrictionsOffset(int classIndex) {
567         if (hiddenApiRestrictionsOffset == DexWriter.NO_OFFSET) {
568             return DexWriter.NO_OFFSET;
569         }
570 
571         int offset = dexBuffer.readInt(
572                 hiddenApiRestrictionsOffset +
573                         HiddenApiClassDataItem.OFFSETS_LIST_OFFSET +
574                         classIndex * HiddenApiClassDataItem.OFFSET_ITEM_SIZE);
575         if (offset == DexWriter.NO_OFFSET) {
576             return DexWriter.NO_OFFSET;
577         }
578 
579         return hiddenApiRestrictionsOffset + offset;
580     }
581 
582     public static abstract class OptionalIndexedSection<T> extends IndexedSection<T> {
583         /**
584          * @param index The index of the item, or -1 for a null item.
585          * @return The value at the given index, or null if index is -1.
586          * @throws IndexOutOfBoundsException if the index is out of bounds and is not -1.
587          */
588         @Nullable public abstract T getOptional(int index);
589     }
590 
591     public static abstract class IndexedSection<T> extends AbstractList<T> {
592         /**
593          * @param index The index of the item to get the offset for.
594          * @return The offset from the beginning of the dex file to the specified item.
595          * @throws IndexOutOfBoundsException if the index is out of bounds.
596          */
597         public abstract int getOffset(int index);
598     }
599 }
600