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.collect.ImmutableList;
35import com.google.common.collect.ImmutableSet;
36import com.google.common.collect.Iterables;
37import org.jf.dexlib2.base.reference.BaseTypeReference;
38import org.jf.dexlib2.dexbacked.raw.ClassDefItem;
39import org.jf.dexlib2.dexbacked.util.AnnotationsDirectory;
40import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
41import org.jf.dexlib2.dexbacked.util.StaticInitialValueIterator;
42import org.jf.dexlib2.dexbacked.util.VariableSizeLookaheadIterator;
43import org.jf.dexlib2.iface.ClassDef;
44import org.jf.dexlib2.iface.reference.FieldReference;
45import org.jf.dexlib2.iface.reference.MethodReference;
46import org.jf.dexlib2.immutable.reference.ImmutableFieldReference;
47import org.jf.dexlib2.immutable.reference.ImmutableMethodReference;
48
49import javax.annotation.Nonnull;
50import javax.annotation.Nullable;
51import java.util.AbstractList;
52import java.util.Iterator;
53import java.util.List;
54import java.util.Set;
55
56public class DexBackedClassDef extends BaseTypeReference implements ClassDef {
57    @Nonnull public final DexBackedDexFile dexFile;
58    private final int classDefOffset;
59
60    private final int staticFieldsOffset;
61    private int instanceFieldsOffset = 0;
62    private int directMethodsOffset = 0;
63    private int virtualMethodsOffset = 0;
64
65    private final int staticFieldCount;
66    private final int instanceFieldCount;
67    private final int directMethodCount;
68    private final int virtualMethodCount;
69
70    @Nullable private AnnotationsDirectory annotationsDirectory;
71
72    public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile,
73                             int classDefOffset) {
74        this.dexFile = dexFile;
75        this.classDefOffset = classDefOffset;
76
77        int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET);
78        if (classDataOffset == 0) {
79            staticFieldsOffset = -1;
80            staticFieldCount = 0;
81            instanceFieldCount = 0;
82            directMethodCount = 0;
83            virtualMethodCount = 0;
84        } else {
85            DexReader reader = dexFile.readerAt(classDataOffset);
86            staticFieldCount = reader.readSmallUleb128();
87            instanceFieldCount = reader.readSmallUleb128();
88            directMethodCount = reader.readSmallUleb128();
89            virtualMethodCount = reader.readSmallUleb128();
90            staticFieldsOffset = reader.getOffset();
91        }
92
93    }
94
95    @Nonnull
96    @Override
97    public String getType() {
98        return dexFile.getType(dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_OFFSET));
99    }
100
101    @Nullable
102    @Override
103    public String getSuperclass() {
104        return dexFile.getOptionalType(dexFile.readOptionalUint(classDefOffset + ClassDefItem.SUPERCLASS_OFFSET));
105    }
106
107    @Override
108    public int getAccessFlags() {
109        return dexFile.readSmallUint(classDefOffset + ClassDefItem.ACCESS_FLAGS_OFFSET);
110    }
111
112    @Nullable
113    @Override
114    public String getSourceFile() {
115        return dexFile.getOptionalString(dexFile.readOptionalUint(classDefOffset + ClassDefItem.SOURCE_FILE_OFFSET));
116    }
117
118    @Nonnull
119    @Override
120    public List<String> getInterfaces() {
121        final int interfacesOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.INTERFACES_OFFSET);
122        if (interfacesOffset > 0) {
123            final int size = dexFile.readSmallUint(interfacesOffset);
124            return new AbstractList<String>() {
125                @Override
126                @Nonnull
127                public String get(int index) {
128                    return dexFile.getType(dexFile.readUshort(interfacesOffset + 4 + (2*index)));
129                }
130
131                @Override public int size() { return size; }
132            };
133        }
134        return ImmutableList.of();
135    }
136
137    @Nonnull
138    @Override
139    public Set<? extends DexBackedAnnotation> getAnnotations() {
140        return getAnnotationsDirectory().getClassAnnotations();
141    }
142
143    @Nonnull
144    @Override
145    public Iterable<? extends DexBackedField> getStaticFields() {
146        return getStaticFields(true);
147    }
148
149    @Nonnull
150    public Iterable<? extends DexBackedField> getStaticFields(final boolean skipDuplicates) {
151        if (staticFieldCount > 0) {
152            DexReader reader = dexFile.readerAt(staticFieldsOffset);
153
154            final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
155            final int staticInitialValuesOffset =
156                    dexFile.readSmallUint(classDefOffset + ClassDefItem.STATIC_VALUES_OFFSET);
157            final int fieldsStartOffset = reader.getOffset();
158
159            return new Iterable<DexBackedField>() {
160                @Nonnull
161                @Override
162                public Iterator<DexBackedField> iterator() {
163                    final AnnotationsDirectory.AnnotationIterator annotationIterator =
164                            annotationsDirectory.getFieldAnnotationIterator();
165                    final StaticInitialValueIterator staticInitialValueIterator =
166                            StaticInitialValueIterator.newOrEmpty(dexFile, staticInitialValuesOffset);
167
168                    return new VariableSizeLookaheadIterator<DexBackedField>(dexFile, fieldsStartOffset) {
169                        private int count;
170                        @Nullable private FieldReference previousField;
171                        private int previousIndex;
172
173                        @Nullable
174                        @Override
175                        protected DexBackedField readNextItem(@Nonnull DexReader reader) {
176                            while (true) {
177                                if (++count > staticFieldCount) {
178                                    instanceFieldsOffset = reader.getOffset();
179                                    return endOfData();
180                                }
181
182                                DexBackedField item = new DexBackedField(reader, DexBackedClassDef.this,
183                                        previousIndex, staticInitialValueIterator, annotationIterator);
184                                FieldReference currentField = previousField;
185                                FieldReference nextField = ImmutableFieldReference.of(item);
186
187                                previousField = nextField;
188                                previousIndex = item.fieldIndex;
189
190                                if (skipDuplicates && currentField != null && currentField.equals(nextField)) {
191                                    continue;
192                                }
193
194                                return item;
195                            }
196                        }
197                    };
198                }
199            };
200        } else {
201            instanceFieldsOffset = staticFieldsOffset;
202            return ImmutableSet.of();
203        }
204    }
205
206    @Nonnull
207    @Override
208    public Iterable<? extends DexBackedField> getInstanceFields() {
209        return getInstanceFields(true);
210    }
211
212    @Nonnull
213    public Iterable<? extends DexBackedField> getInstanceFields(final boolean skipDuplicates) {
214        if (instanceFieldCount > 0) {
215            DexReader reader = dexFile.readerAt(getInstanceFieldsOffset());
216
217            final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
218            final int fieldsStartOffset = reader.getOffset();
219
220            return new Iterable<DexBackedField>() {
221                @Nonnull
222                @Override
223                public Iterator<DexBackedField> iterator() {
224                    final AnnotationsDirectory.AnnotationIterator annotationIterator =
225                            annotationsDirectory.getFieldAnnotationIterator();
226
227                    return new VariableSizeLookaheadIterator<DexBackedField>(dexFile, fieldsStartOffset) {
228                        private int count;
229                        @Nullable private FieldReference previousField;
230                        private int previousIndex;
231
232                        @Nullable
233                        @Override
234                        protected DexBackedField readNextItem(@Nonnull DexReader reader) {
235                            while (true) {
236                                if (++count > instanceFieldCount) {
237                                    directMethodsOffset = reader.getOffset();
238                                    return endOfData();
239                                }
240
241                                DexBackedField item = new DexBackedField(reader, DexBackedClassDef.this,
242                                        previousIndex, annotationIterator);
243                                FieldReference currentField = previousField;
244                                FieldReference nextField = ImmutableFieldReference.of(item);
245
246                                previousField = nextField;
247                                previousIndex = item.fieldIndex;
248
249                                if (skipDuplicates && currentField != null && currentField.equals(nextField)) {
250                                    continue;
251                                }
252
253                                return item;
254                            }
255                        }
256                    };
257                }
258            };
259        } else {
260            if (instanceFieldsOffset > 0) {
261                directMethodsOffset = instanceFieldsOffset;
262            }
263            return ImmutableSet.of();
264        }
265    }
266
267    @Nonnull
268    @Override
269    public Iterable<? extends DexBackedField> getFields() {
270        return Iterables.concat(getStaticFields(), getInstanceFields());
271    }
272
273    @Nonnull
274    @Override
275    public Iterable<? extends DexBackedMethod> getDirectMethods() {
276        return getDirectMethods(true);
277    }
278
279    @Nonnull
280    public Iterable<? extends DexBackedMethod> getDirectMethods(final boolean skipDuplicates) {
281        if (directMethodCount > 0) {
282            DexReader reader = dexFile.readerAt(getDirectMethodsOffset());
283
284            final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
285            final int methodsStartOffset = reader.getOffset();
286
287            return new Iterable<DexBackedMethod>() {
288                @Nonnull
289                @Override
290                public Iterator<DexBackedMethod> iterator() {
291                    final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator =
292                            annotationsDirectory.getMethodAnnotationIterator();
293                    final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator =
294                            annotationsDirectory.getParameterAnnotationIterator();
295
296                    return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) {
297                        private int count;
298                        @Nullable private MethodReference previousMethod;
299                        private int previousIndex;
300
301                        @Nullable
302                        @Override
303                        protected DexBackedMethod readNextItem(@Nonnull DexReader reader) {
304                            while (true) {
305                                if (++count > directMethodCount) {
306                                    virtualMethodsOffset = reader.getOffset();
307                                    return endOfData();
308                                }
309
310                                DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this,
311                                        previousIndex, methodAnnotationIterator, parameterAnnotationIterator);
312                                MethodReference currentMethod = previousMethod;
313                                MethodReference nextMethod = ImmutableMethodReference.of(item);
314
315                                previousMethod = nextMethod;
316                                previousIndex = item.methodIndex;
317
318                                if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) {
319                                    continue;
320
321                                }
322                                return item;
323                            }
324                        }
325                    };
326                }
327            };
328        } else {
329            if (directMethodsOffset > 0) {
330                virtualMethodsOffset = directMethodsOffset;
331            }
332            return ImmutableSet.of();
333        }
334    }
335
336    @Nonnull
337    public Iterable<? extends DexBackedMethod> getVirtualMethods(final boolean skipDuplicates) {
338        if (virtualMethodCount > 0) {
339            DexReader reader = dexFile.readerAt(getVirtualMethodsOffset());
340
341            final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
342            final int methodsStartOffset = reader.getOffset();
343
344            return new Iterable<DexBackedMethod>() {
345                final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator =
346                        annotationsDirectory.getMethodAnnotationIterator();
347                final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator =
348                        annotationsDirectory.getParameterAnnotationIterator();
349
350                @Nonnull
351                @Override
352                public Iterator<DexBackedMethod> iterator() {
353                    return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) {
354                        private int count;
355                        @Nullable private MethodReference previousMethod;
356                        private int previousIndex;
357
358                        @Nullable
359                        @Override
360                        protected DexBackedMethod readNextItem(@Nonnull DexReader reader) {
361                            while (true) {
362                                if (++count > virtualMethodCount) {
363                                    return endOfData();
364                                }
365
366                                DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this,
367                                        previousIndex, methodAnnotationIterator, parameterAnnotationIterator);
368                                MethodReference currentMethod = previousMethod;
369                                MethodReference nextMethod = ImmutableMethodReference.of(item);
370
371                                previousMethod = nextMethod;
372                                previousIndex = item.methodIndex;
373
374                                if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) {
375                                    continue;
376                                }
377                                return item;
378                            }
379                        }
380                    };
381                }
382            };
383        } else {
384            return ImmutableSet.of();
385        }
386    }
387
388    @Nonnull
389    @Override
390    public Iterable<? extends DexBackedMethod> getVirtualMethods() {
391        return getVirtualMethods(true);
392    }
393
394    @Nonnull
395    @Override
396    public Iterable<? extends DexBackedMethod> getMethods() {
397        return Iterables.concat(getDirectMethods(), getVirtualMethods());
398    }
399
400    private AnnotationsDirectory getAnnotationsDirectory() {
401        if (annotationsDirectory == null) {
402            int annotationsDirectoryOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.ANNOTATIONS_OFFSET);
403            annotationsDirectory = AnnotationsDirectory.newOrEmpty(dexFile, annotationsDirectoryOffset);
404        }
405        return annotationsDirectory;
406    }
407
408    private int getInstanceFieldsOffset() {
409        if (instanceFieldsOffset > 0) {
410            return instanceFieldsOffset;
411        }
412        DexReader reader = new DexReader(dexFile, staticFieldsOffset);
413        DexBackedField.skipFields(reader, staticFieldCount);
414        instanceFieldsOffset = reader.getOffset();
415        return instanceFieldsOffset;
416    }
417
418    private int getDirectMethodsOffset() {
419        if (directMethodsOffset > 0) {
420            return directMethodsOffset;
421        }
422        DexReader reader = dexFile.readerAt(getInstanceFieldsOffset());
423        DexBackedField.skipFields(reader, instanceFieldCount);
424        directMethodsOffset = reader.getOffset();
425        return directMethodsOffset;
426    }
427
428    private int getVirtualMethodsOffset() {
429        if (virtualMethodsOffset > 0) {
430            return virtualMethodsOffset;
431        }
432        DexReader reader = dexFile.readerAt(getDirectMethodsOffset());
433        DexBackedMethod.skipMethods(reader, directMethodCount);
434        virtualMethodsOffset = reader.getOffset();
435        return virtualMethodsOffset;
436    }
437}
438