blob: 9159fc6b803bd93beb819a32d524baeba28ebdbf [file] [log] [blame]
/*
* 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;
}
}