blob: 9664b9977804dfe4167f051e471ac636527dd5d5 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2010 Ben Gruver (JesusFreke)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.dexlib;
import org.jf.dexlib.EncodedValue.ArrayEncodedSubValue;
import org.jf.dexlib.EncodedValue.EncodedValue;
import org.jf.dexlib.Util.AccessFlags;
import org.jf.dexlib.Util.AnnotatedOutput;
import org.jf.dexlib.Util.Input;
import org.jf.dexlib.Util.TypeUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class ClassDefItem extends Item<ClassDefItem> {
private TypeIdItem classType;
private int accessFlags;
private @Nullable TypeIdItem superType;
private @Nullable TypeListItem implementedInterfaces;
private @Nullable StringIdItem sourceFile;
private @Nullable AnnotationDirectoryItem annotations;
private @Nullable ClassDataItem classData;
private @Nullable EncodedArrayItem staticFieldInitializers;
/**
* Creates a new uninitialized <code>ClassDefItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
*/
protected ClassDefItem(DexFile dexFile) {
super(dexFile);
}
/**
* Creates a new <code>ClassDefItem</code> with the given values
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param classType The type of this class
* @param accessFlags The access flags of this class
* @param superType The superclass of this class, or null if none (only valid for java.lang.Object)
* @param implementedInterfaces A list of the interfaces that this class implements, or null if none
* @param sourceFile The main source file that this class is defined in, or null if not available
* @param annotations The annotations for this class and its fields, methods and method parameters, or null if none
* @param classData The <code>ClassDataItem</code> containing the method and field definitions for this class
* @param staticFieldInitializers The initial values for this class's static fields, or null if none. The initial
* values should be in the same order as the static fields in the <code>ClassDataItem</code>. It can contain
* fewer items than static fields, in which case the remaining static fields will be initialized with a default
* value of null/0. The initial value for any fields that don't specifically have a value can be either the
* type-appropriate null/0 encoded value, or null.
*/
private ClassDefItem(DexFile dexFile, TypeIdItem classType, int accessFlags, @Nullable TypeIdItem superType,
@Nullable TypeListItem implementedInterfaces, @Nullable StringIdItem sourceFile,
@Nullable AnnotationDirectoryItem annotations, @Nullable ClassDataItem classData,
@Nullable EncodedArrayItem staticFieldInitializers) {
super(dexFile);
assert classType != null;
this.classType = classType;
this.accessFlags = accessFlags;
this.superType = superType;
this.implementedInterfaces = implementedInterfaces;
this.sourceFile = sourceFile;
this.annotations = annotations;
this.classData = classData;
this.staticFieldInitializers = staticFieldInitializers;
}
/**
* Returns a <code>ClassDefItem</code> for the given values, and that has been interned into the given
* <code>DexFile</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param classType The type of this class
* @param accessFlags The access flags of this class
* @param superType The superclass of this class, or null if none (only valid for java.lang.Object)
* @param implementedInterfaces A list of the interfaces that this class implements, or null if none
* @param sourceFile The main source file that this class is defined in, or null if not available
* @param annotations The annotations for this class and its fields, methods and method parameters, or null if none
* @param classData The <code>ClassDataItem</code> containing the method and field definitions for this class
* @param staticFieldInitializers The initial values for this class's static fields, or null if none. If it is not
* null, it must contain the same number of items as the number of static fields in this class. The value in the
* <code>StaticFieldInitializer</code> for any field that doesn't have an explicit initial value can either be null
* or be the type-appropriate null/0 value.
* @return a <code>ClassDefItem</code> for the given values, and that has been interned into the given
* <code>DexFile</code>
*/
public static ClassDefItem internClassDefItem(DexFile dexFile, TypeIdItem classType, int accessFlags,
@Nullable TypeIdItem superType, @Nullable TypeListItem implementedInterfaces,
@Nullable StringIdItem sourceFile, @Nullable AnnotationDirectoryItem annotations,
@Nullable ClassDataItem classData,
@Nullable List<StaticFieldInitializer> staticFieldInitializers) {
EncodedArrayItem encodedArrayItem = null;
if(!dexFile.getInplace() && staticFieldInitializers != null && staticFieldInitializers.size() > 0) {
assert classData != null;
assert staticFieldInitializers.size() == classData.getStaticFieldCount();
encodedArrayItem = makeStaticFieldInitializersItem(dexFile, staticFieldInitializers);
}
ClassDefItem classDefItem = new ClassDefItem(dexFile, classType, accessFlags, superType, implementedInterfaces,
sourceFile, annotations, classData, encodedArrayItem);
return dexFile.ClassDefsSection.intern(classDefItem);
}
/** {@inheritDoc} */
protected void readItem(Input in, ReadContext readContext) {
classType = dexFile.TypeIdsSection.getItemByIndex(in.readInt());
accessFlags = in.readInt();
superType = dexFile.TypeIdsSection.getOptionalItemByIndex(in.readInt());
implementedInterfaces = (TypeListItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_TYPE_LIST,
in.readInt());
sourceFile = dexFile.StringIdsSection.getOptionalItemByIndex(in.readInt());
annotations = (AnnotationDirectoryItem)readContext.getOptionalOffsettedItemByOffset(
ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM, in.readInt());
classData = (ClassDataItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_CLASS_DATA_ITEM, in.readInt());
staticFieldInitializers = (EncodedArrayItem)readContext.getOptionalOffsettedItemByOffset(
ItemType.TYPE_ENCODED_ARRAY_ITEM, in.readInt());
}
/** {@inheritDoc} */
protected int placeItem(int offset) {
return offset + 32;
}
/** {@inheritDoc} */
protected void writeItem(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate(4, "class_type: " + classType.getTypeDescriptor());
out.annotate(4, "access_flags: " + AccessFlags.formatAccessFlagsForClass(accessFlags));
out.annotate(4, "superclass_type: " + (superType==null?"":superType.getTypeDescriptor()));
out.annotate(4, "interfaces: " +
(implementedInterfaces==null?"":implementedInterfaces.getTypeListString(" ")));
out.annotate(4, "source_file: " + (sourceFile==null?"":sourceFile.getStringValue()));
out.annotate(4, "annotations_off: " +
(annotations==null?"":"0x"+Integer.toHexString(annotations.getOffset())));
out.annotate(4, "class_data_off:" +
(classData==null?"":"0x"+Integer.toHexString(classData.getOffset())));
out.annotate(4, "static_values_off: " +
(staticFieldInitializers==null?"":"0x"+Integer.toHexString(staticFieldInitializers.getOffset())));
}
out.writeInt(classType.getIndex());
out.writeInt(accessFlags);
out.writeInt(superType==null?-1:superType.getIndex());
out.writeInt(implementedInterfaces==null?0:implementedInterfaces.getOffset());
out.writeInt(sourceFile==null?-1:sourceFile.getIndex());
out.writeInt(annotations==null?0:annotations.getOffset());
out.writeInt(classData==null?0:classData.getOffset());
out.writeInt(staticFieldInitializers==null?0:staticFieldInitializers.getOffset());
}
/** {@inheritDoc} */
public ItemType getItemType() {
return ItemType.TYPE_CLASS_DEF_ITEM;
}
/** {@inheritDoc} */
public String getConciseIdentity() {
return "class_def_item: " + classType.getTypeDescriptor();
}
/** {@inheritDoc} */
public int compareTo(ClassDefItem o) {
//The actual sorting for this class is done during the placement phase, in ClassDefPlacer.
//This method is just used for sorting the associated ClassDataItem items after the ClassDefItems have been
//placed, so we can just do the comparison based on the offsets
return this.getOffset() - o.getOffset();
}
public TypeIdItem getClassType() {
return classType;
}
public int getAccessFlags() {
return accessFlags;
}
@Nullable
public TypeIdItem getSuperclass() {
return superType;
}
@Nullable
public TypeListItem getInterfaces() {
return implementedInterfaces;
}
@Nullable
public StringIdItem getSourceFile() {
return sourceFile;
}
@Nullable
public AnnotationDirectoryItem getAnnotations() {
return annotations;
}
@Nullable
public ClassDataItem getClassData() {
return classData;
}
@Nullable
public EncodedArrayItem getStaticFieldInitializers() {
return staticFieldInitializers;
}
public static int placeClassDefItems(IndexedSection<ClassDefItem> section, int offset) {
ClassDefPlacer cdp = new ClassDefPlacer(section);
return cdp.placeSection(offset);
}
/**
* This class places the items within a ClassDefItem section, such that superclasses and interfaces are
* placed before sub/implementing classes
*/
private static class ClassDefPlacer {
private final IndexedSection<ClassDefItem> section;
private final HashMap<TypeIdItem, ClassDefItem> unplacedClassDefsByType =
new HashMap<TypeIdItem, ClassDefItem>();
private int currentIndex = 0;
private int currentOffset;
public ClassDefPlacer(IndexedSection<ClassDefItem> section) {
this.section = section;
for (ClassDefItem classDefItem: section.items) {
TypeIdItem typeIdItem = classDefItem.classType;
unplacedClassDefsByType.put(typeIdItem, classDefItem);
}
}
public int placeSection(int offset) {
currentOffset = offset;
if (section.DexFile.getSortAllItems()) {
//presort the list, to guarantee a unique ordering
Collections.sort(section.items, new Comparator<ClassDefItem>() {
public int compare(ClassDefItem a, ClassDefItem b) {
return a.getClassType().compareTo(b.getClassType());
}
});
}
//we need to initialize the offset for all the classes to -1, so we can tell which ones
//have been placed
for (ClassDefItem classDefItem: section.items) {
classDefItem.offset = -1;
}
for (ClassDefItem classDefItem: section.items) {
placeClass(classDefItem);
}
for (ClassDefItem classDefItem: unplacedClassDefsByType.values()) {
section.items.set(classDefItem.getIndex(), classDefItem);
}
return currentOffset;
}
private void placeClass(ClassDefItem classDefItem) {
if (!classDefItem.isPlaced()) {
TypeIdItem superType = classDefItem.superType;
ClassDefItem superClassDefItem = unplacedClassDefsByType.get(superType);
if (superClassDefItem != null) {
placeClass(superClassDefItem);
}
TypeListItem interfaces = classDefItem.implementedInterfaces;
if (interfaces != null) {
for (TypeIdItem interfaceType: interfaces.getTypes()) {
ClassDefItem interfaceClass = unplacedClassDefsByType.get(interfaceType);
if (interfaceClass != null) {
placeClass(interfaceClass);
}
}
}
currentOffset = classDefItem.placeAt(currentOffset, currentIndex++);
unplacedClassDefsByType.remove(classDefItem.classType);
}
}
}
public static class StaticFieldInitializer implements Comparable<StaticFieldInitializer> {
public final EncodedValue value;
public final ClassDataItem.EncodedField field;
public StaticFieldInitializer(EncodedValue value, ClassDataItem.EncodedField field) {
this.value = value;
this.field = field;
}
public int compareTo(StaticFieldInitializer other) {
return field.compareTo(other.field);
}
}
/**
* A helper method to sort the static field initializers and populate the default values as needed
* @param dexFile the <code>DexFile</code>
* @param staticFieldInitializers the initial values
* @return an interned EncodedArrayItem containing the static field initializers
*/
private static EncodedArrayItem makeStaticFieldInitializersItem(DexFile dexFile,
@Nonnull List<StaticFieldInitializer> staticFieldInitializers) {
if (staticFieldInitializers.size() == 0) {
return null;
}
int len = staticFieldInitializers.size();
// make a copy before sorting. we don't want to modify the list passed to us
staticFieldInitializers = new ArrayList<StaticFieldInitializer>(staticFieldInitializers);
Collections.sort(staticFieldInitializers);
int lastIndex = -1;
for (int i=len-1; i>=0; i--) {
StaticFieldInitializer staticFieldInitializer = staticFieldInitializers.get(i);
if (staticFieldInitializer.value != null &&
(staticFieldInitializer.value.compareTo(TypeUtils.makeDefaultValueForType(
staticFieldInitializer.field.field.getFieldType())) != 0)) {
lastIndex = i;
break;
}
}
//we don't have any non-null/non-default values, so we don't need to create an EncodedArrayItem
if (lastIndex == -1) {
return null;
}
EncodedValue[] values = new EncodedValue[lastIndex+1];
for (int i=0; i<=lastIndex; i++) {
StaticFieldInitializer staticFieldInitializer = staticFieldInitializers.get(i);
EncodedValue encodedValue = staticFieldInitializer.value;
if (encodedValue == null) {
encodedValue = TypeUtils.makeDefaultValueForType(staticFieldInitializer.field.field.getFieldType());
}
values[i] = encodedValue;
}
ArrayEncodedSubValue encodedArrayValue = new ArrayEncodedSubValue(values);
return EncodedArrayItem.internEncodedArrayItem(dexFile, encodedArrayValue);
}
}