/* * Copyright 2012, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jf.dexlib2.dexbacked; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import org.jf.dexlib2.base.reference.BaseTypeReference; import org.jf.dexlib2.dexbacked.raw.ClassDefItem; import org.jf.dexlib2.dexbacked.raw.TypeIdItem; import org.jf.dexlib2.dexbacked.util.AnnotationsDirectory; import org.jf.dexlib2.dexbacked.util.FixedSizeSet; import org.jf.dexlib2.dexbacked.util.StaticInitialValueIterator; import org.jf.dexlib2.dexbacked.util.VariableSizeLookaheadIterator; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.immutable.reference.ImmutableFieldReference; import org.jf.dexlib2.immutable.reference.ImmutableMethodReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.AbstractList; import java.util.Iterator; import java.util.List; import java.util.Set; public class DexBackedClassDef extends BaseTypeReference implements ClassDef { @Nonnull public final DexBackedDexFile dexFile; private final int classDefOffset; private final int staticFieldsOffset; private int instanceFieldsOffset = 0; private int directMethodsOffset = 0; private int virtualMethodsOffset = 0; private final int staticFieldCount; private final int instanceFieldCount; private final int directMethodCount; private final int virtualMethodCount; @Nullable private AnnotationsDirectory annotationsDirectory; public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile, int classDefOffset) { this.dexFile = dexFile; this.classDefOffset = classDefOffset; int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET); if (classDataOffset == 0) { staticFieldsOffset = -1; staticFieldCount = 0; instanceFieldCount = 0; directMethodCount = 0; virtualMethodCount = 0; } else { DexReader reader = dexFile.readerAt(classDataOffset); staticFieldCount = reader.readSmallUleb128(); instanceFieldCount = reader.readSmallUleb128(); directMethodCount = reader.readSmallUleb128(); virtualMethodCount = reader.readSmallUleb128(); staticFieldsOffset = reader.getOffset(); } } @Nonnull @Override public String getType() { return dexFile.getType(dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_OFFSET)); } @Nullable @Override public String getSuperclass() { return dexFile.getOptionalType(dexFile.readOptionalUint(classDefOffset + ClassDefItem.SUPERCLASS_OFFSET)); } @Override public int getAccessFlags() { return dexFile.readSmallUint(classDefOffset + ClassDefItem.ACCESS_FLAGS_OFFSET); } @Nullable @Override public String getSourceFile() { return dexFile.getOptionalString(dexFile.readOptionalUint(classDefOffset + ClassDefItem.SOURCE_FILE_OFFSET)); } @Nonnull @Override public List<String> getInterfaces() { final int interfacesOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.INTERFACES_OFFSET); if (interfacesOffset > 0) { final int size = dexFile.readSmallUint(interfacesOffset); return new AbstractList<String>() { @Override @Nonnull public String get(int index) { return dexFile.getType(dexFile.readUshort(interfacesOffset + 4 + (2*index))); } @Override public int size() { return size; } }; } return ImmutableList.of(); } @Nonnull @Override public Set<? extends DexBackedAnnotation> getAnnotations() { return getAnnotationsDirectory().getClassAnnotations(); } @Nonnull @Override public Iterable<? extends DexBackedField> getStaticFields() { return getStaticFields(true); } @Nonnull public Iterable<? extends DexBackedField> getStaticFields(final boolean skipDuplicates) { if (staticFieldCount > 0) { DexReader reader = dexFile.readerAt(staticFieldsOffset); final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory(); final int staticInitialValuesOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.STATIC_VALUES_OFFSET); final int fieldsStartOffset = reader.getOffset(); return new Iterable<DexBackedField>() { @Nonnull @Override public Iterator<DexBackedField> iterator() { final AnnotationsDirectory.AnnotationIterator annotationIterator = annotationsDirectory.getFieldAnnotationIterator(); final StaticInitialValueIterator staticInitialValueIterator = StaticInitialValueIterator.newOrEmpty(dexFile, staticInitialValuesOffset); return new VariableSizeLookaheadIterator<DexBackedField>(dexFile, fieldsStartOffset) { private int count; @Nullable private FieldReference previousField; private int previousIndex; @Nullable @Override protected DexBackedField readNextItem(@Nonnull DexReader reader) { while (true) { if (++count > staticFieldCount) { instanceFieldsOffset = reader.getOffset(); return endOfData(); } DexBackedField item = new DexBackedField(reader, DexBackedClassDef.this, previousIndex, staticInitialValueIterator, annotationIterator); FieldReference currentField = previousField; FieldReference nextField = ImmutableFieldReference.of(item); previousField = nextField; previousIndex = item.fieldIndex; if (skipDuplicates && currentField != null && currentField.equals(nextField)) { continue; } return item; } } }; } }; } else { instanceFieldsOffset = staticFieldsOffset; return ImmutableSet.of(); } } @Nonnull @Override public Iterable<? extends DexBackedField> getInstanceFields() { return getInstanceFields(true); } @Nonnull public Iterable<? extends DexBackedField> getInstanceFields(final boolean skipDuplicates) { if (instanceFieldCount > 0) { DexReader reader = dexFile.readerAt(getInstanceFieldsOffset()); final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory(); final int fieldsStartOffset = reader.getOffset(); return new Iterable<DexBackedField>() { @Nonnull @Override public Iterator<DexBackedField> iterator() { final AnnotationsDirectory.AnnotationIterator annotationIterator = annotationsDirectory.getFieldAnnotationIterator(); return new VariableSizeLookaheadIterator<DexBackedField>(dexFile, fieldsStartOffset) { private int count; @Nullable private FieldReference previousField; private int previousIndex; @Nullable @Override protected DexBackedField readNextItem(@Nonnull DexReader reader) { while (true) { if (++count > instanceFieldCount) { directMethodsOffset = reader.getOffset(); return endOfData(); } DexBackedField item = new DexBackedField(reader, DexBackedClassDef.this, previousIndex, annotationIterator); FieldReference currentField = previousField; FieldReference nextField = ImmutableFieldReference.of(item); previousField = nextField; previousIndex = item.fieldIndex; if (skipDuplicates && currentField != null && currentField.equals(nextField)) { continue; } return item; } } }; } }; } else { if (instanceFieldsOffset > 0) { directMethodsOffset = instanceFieldsOffset; } return ImmutableSet.of(); } } @Nonnull @Override public Iterable<? extends DexBackedField> getFields() { return Iterables.concat(getStaticFields(), getInstanceFields()); } @Nonnull @Override public Iterable<? extends DexBackedMethod> getDirectMethods() { return getDirectMethods(true); } @Nonnull public Iterable<? extends DexBackedMethod> getDirectMethods(final boolean skipDuplicates) { if (directMethodCount > 0) { DexReader reader = dexFile.readerAt(getDirectMethodsOffset()); final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory(); final int methodsStartOffset = reader.getOffset(); return new Iterable<DexBackedMethod>() { @Nonnull @Override public Iterator<DexBackedMethod> iterator() { final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator = annotationsDirectory.getMethodAnnotationIterator(); final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator = annotationsDirectory.getParameterAnnotationIterator(); return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) { private int count; @Nullable private MethodReference previousMethod; private int previousIndex; @Nullable @Override protected DexBackedMethod readNextItem(@Nonnull DexReader reader) { while (true) { if (++count > directMethodCount) { virtualMethodsOffset = reader.getOffset(); return endOfData(); } DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this, previousIndex, methodAnnotationIterator, parameterAnnotationIterator); MethodReference currentMethod = previousMethod; MethodReference nextMethod = ImmutableMethodReference.of(item); previousMethod = nextMethod; previousIndex = item.methodIndex; if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) { continue; } return item; } } }; } }; } else { if (directMethodsOffset > 0) { virtualMethodsOffset = directMethodsOffset; } return ImmutableSet.of(); } } @Nonnull public Iterable<? extends DexBackedMethod> getVirtualMethods(final boolean skipDuplicates) { if (virtualMethodCount > 0) { DexReader reader = dexFile.readerAt(getVirtualMethodsOffset()); final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory(); final int methodsStartOffset = reader.getOffset(); return new Iterable<DexBackedMethod>() { final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator = annotationsDirectory.getMethodAnnotationIterator(); final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator = annotationsDirectory.getParameterAnnotationIterator(); @Nonnull @Override public Iterator<DexBackedMethod> iterator() { return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) { private int count; @Nullable private MethodReference previousMethod; private int previousIndex; @Nullable @Override protected DexBackedMethod readNextItem(@Nonnull DexReader reader) { while (true) { if (++count > virtualMethodCount) { return endOfData(); } DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this, previousIndex, methodAnnotationIterator, parameterAnnotationIterator); MethodReference currentMethod = previousMethod; MethodReference nextMethod = ImmutableMethodReference.of(item); previousMethod = nextMethod; previousIndex = item.methodIndex; if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) { continue; } return item; } } }; } }; } else { return ImmutableSet.of(); } } @Nonnull @Override public Iterable<? extends DexBackedMethod> getVirtualMethods() { return getVirtualMethods(true); } @Nonnull @Override public Iterable<? extends DexBackedMethod> getMethods() { return Iterables.concat(getDirectMethods(), getVirtualMethods()); } private AnnotationsDirectory getAnnotationsDirectory() { if (annotationsDirectory == null) { int annotationsDirectoryOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.ANNOTATIONS_OFFSET); annotationsDirectory = AnnotationsDirectory.newOrEmpty(dexFile, annotationsDirectoryOffset); } return annotationsDirectory; } private int getInstanceFieldsOffset() { if (instanceFieldsOffset > 0) { return instanceFieldsOffset; } DexReader reader = new DexReader(dexFile, staticFieldsOffset); DexBackedField.skipFields(reader, staticFieldCount); instanceFieldsOffset = reader.getOffset(); return instanceFieldsOffset; } private int getDirectMethodsOffset() { if (directMethodsOffset > 0) { return directMethodsOffset; } DexReader reader = dexFile.readerAt(getInstanceFieldsOffset()); DexBackedField.skipFields(reader, instanceFieldCount); directMethodsOffset = reader.getOffset(); return directMethodsOffset; } private int getVirtualMethodsOffset() { if (virtualMethodsOffset > 0) { return virtualMethodsOffset; } DexReader reader = dexFile.readerAt(getDirectMethodsOffset()); DexBackedMethod.skipMethods(reader, directMethodCount); virtualMethodsOffset = reader.getOffset(); return virtualMethodsOffset; } /** * Calculate and return the private size of a class definition. * * Calculated as: class_def_item size + type_id size + interfaces type_list + * annotations_directory_item overhead + class_data_item + static values overhead + * methods size + fields size * * @return size in bytes */ public int getSize() { int size = 8 * 4; //class_def_item has 8 uint fields in dex files size += TypeIdItem.ITEM_SIZE; //type_ids size //add interface list size if any int interfacesLength = getInterfaces().size(); if (interfacesLength > 0) { //add size of the type_list size += 4; //uint for size size += interfacesLength * 2; //ushort per type_item } //annotations directory size if it exists AnnotationsDirectory directory = getAnnotationsDirectory(); if (!AnnotationsDirectory.EMPTY.equals(directory)) { size += 4 * 4; //4 uints in annotations_directory_item Set<? extends DexBackedAnnotation> classAnnotations = directory.getClassAnnotations(); if (!classAnnotations.isEmpty()) { size += 4; //uint for size size += classAnnotations.size() * 4; //uint per annotation_off //TODO: should we add annotation_item size? what if it's shared? } } //static values and/or metadata int staticInitialValuesOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.STATIC_VALUES_OFFSET); if (staticInitialValuesOffset != 0) { DexReader reader = dexFile.readerAt(staticInitialValuesOffset); size += reader.peekSmallUleb128Size(); //encoded_array size field } //class_data_item int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET); if (classDataOffset > 0) { DexReader reader = dexFile.readerAt(classDataOffset); reader.readSmallUleb128(); //staticFieldCount reader.readSmallUleb128(); //instanceFieldCount reader.readSmallUleb128(); //directMethodCount reader.readSmallUleb128(); //virtualMethodCount size += reader.getOffset() - classDataOffset; } for (DexBackedField dexBackedField : getFields()) { size += dexBackedField.getSize(); } for (DexBackedMethod dexBackedMethod : getMethods()) { size += dexBackedMethod.getSize(); } return size; } }