/* * [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 com.google.common.base.Preconditions; import org.jf.dexlib.Util.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; public class ClassDataItem extends Item { @Nullable private EncodedField[] staticFields = null; @Nullable private EncodedField[] instanceFields = null; @Nullable private EncodedMethod[] directMethods = null; @Nullable private EncodedMethod[] virtualMethods = null; /** * Creates a new uninitialized ClassDataItem * @param dexFile The DexFile that this item belongs to */ public ClassDataItem(final DexFile dexFile) { super(dexFile); } /** * Creates a new ClassDataItem with the given values * @param dexFile The DexFile that this item belongs to * @param staticFields The static fields for this class * @param instanceFields The instance fields for this class * @param directMethods The direct methods for this class * @param virtualMethods The virtual methods for this class */ private ClassDataItem(DexFile dexFile, @Nullable EncodedField[] staticFields, @Nullable EncodedField[] instanceFields, @Nullable EncodedMethod[] directMethods, @Nullable EncodedMethod[] virtualMethods) { super(dexFile); this.staticFields = staticFields; this.instanceFields = instanceFields; this.directMethods = directMethods; this.virtualMethods = virtualMethods; } /** * Creates a new ClassDataItem with the given values * @param dexFile The DexFile that this item belongs to * @param staticFields The static fields for this class * @param instanceFields The instance fields for this class * @param directMethods The direct methods for this class * @param virtualMethods The virtual methods for this class * @return a new ClassDataItem with the given values */ public static ClassDataItem internClassDataItem(DexFile dexFile, @Nullable List staticFields, @Nullable List instanceFields, @Nullable List directMethods, @Nullable List virtualMethods) { EncodedField[] staticFieldsArray = null; EncodedField[] instanceFieldsArray = null; EncodedMethod[] directMethodsArray = null; EncodedMethod[] virtualMethodsArray = null; if (staticFields != null && staticFields.size() > 0) { SortedSet staticFieldsSet = new TreeSet(); for (EncodedField staticField: staticFields) { if (staticFieldsSet.contains(staticField)) { System.err.println(String.format("Ignoring duplicate static field definition: %s", staticField.field.getFieldString())); continue; } staticFieldsSet.add(staticField); } staticFieldsArray = new EncodedField[staticFieldsSet.size()]; staticFieldsArray = staticFieldsSet.toArray(staticFieldsArray); } if (instanceFields != null && instanceFields.size() > 0) { SortedSet instanceFieldsSet = new TreeSet(); for (EncodedField instanceField: instanceFields) { if (instanceFieldsSet.contains(instanceField)) { System.err.println(String.format("Ignoring duplicate instance field definition: %s", instanceField.field.getFieldString())); continue; } instanceFieldsSet.add(instanceField); } instanceFieldsArray = new EncodedField[instanceFieldsSet.size()]; instanceFieldsArray = instanceFieldsSet.toArray(instanceFieldsArray); } TreeSet directMethodSet = new TreeSet(); if (directMethods != null && directMethods.size() > 0) { for (EncodedMethod directMethod: directMethods) { if (directMethodSet.contains(directMethod)) { System.err.println(String.format("Ignoring duplicate direct method definition: %s", directMethod.method.getMethodString())); continue; } directMethodSet.add(directMethod); } directMethodsArray = new EncodedMethod[directMethodSet.size()]; directMethodsArray = directMethodSet.toArray(directMethodsArray); } if (virtualMethods != null && virtualMethods.size() > 0) { TreeSet virtualMethodSet = new TreeSet(); for (EncodedMethod virtualMethod: virtualMethods) { if (directMethodSet.contains(virtualMethod)) { // If both a direct and virtual definition is present, dalvik's behavior seems to be undefined, // so we can't gracefully handle this case, like we can if the duplicates are all direct or all // virtual -- in which case, we ignore all but the first definition throw new RuntimeException(String.format("Duplicate direct+virtual method definition: %s", virtualMethod.method.getMethodString())); } if (virtualMethodSet.contains(virtualMethod)) { System.err.println(String.format("Ignoring duplicate virtual method definition: %s", virtualMethod.method.getMethodString())); continue; } virtualMethodSet.add(virtualMethod); } virtualMethodsArray = new EncodedMethod[virtualMethodSet.size()]; virtualMethodsArray = virtualMethodSet.toArray(virtualMethodsArray); } ClassDataItem classDataItem = new ClassDataItem(dexFile, staticFieldsArray, instanceFieldsArray, directMethodsArray, virtualMethodsArray); return dexFile.ClassDataSection.intern(classDataItem); } /** {@inheritDoc} */ protected void readItem(Input in, ReadContext readContext) { int staticFieldsCount = in.readUnsignedLeb128(); int instanceFieldsCount = in.readUnsignedLeb128(); int directMethodsCount = in.readUnsignedLeb128(); int virtualMethodsCount = in.readUnsignedLeb128(); if (staticFieldsCount > 0) { staticFields = new EncodedField[staticFieldsCount]; EncodedField previousEncodedField = null; for (int i=0; i 0) { instanceFields = new EncodedField[instanceFieldsCount]; EncodedField previousEncodedField = null; for (int i=0; i 0) { directMethods = new EncodedMethod[directMethodsCount]; EncodedMethod previousEncodedMethod = null; for (int i=0; i 0) { virtualMethods = new EncodedMethod[virtualMethodsCount]; EncodedMethod previousEncodedMethod = null; for (int i=0; i 0) { return staticFields[0].field.getContainingClass(); } if (instanceFields != null && instanceFields.length > 0) { return instanceFields[0].field.getContainingClass(); } if (directMethods != null && directMethods.length > 0) { return directMethods[0].method.getContainingClass(); } if (virtualMethods != null && virtualMethods.length > 0) { return virtualMethods[0].method.getContainingClass(); } return null; } /** * @return the static fields for this class */ @Nonnull public List getStaticFields() { if (staticFields == null) { return Collections.emptyList(); } return ReadOnlyArrayList.of(staticFields); } /** * @return the instance fields for this class */ @Nonnull public List getInstanceFields() { if (instanceFields == null) { return Collections.emptyList(); } return ReadOnlyArrayList.of(instanceFields); } /** * @return the direct methods for this class */ @Nonnull public List getDirectMethods() { if (directMethods == null) { return Collections.emptyList(); } return ReadOnlyArrayList.of(directMethods); } /** * @return the virtual methods for this class */ @Nonnull public List getVirtualMethods() { if (virtualMethods == null) { return Collections.emptyList(); } return ReadOnlyArrayList.of(virtualMethods); } /** * @return The number of static fields in this ClassDataItem */ public int getStaticFieldCount() { if (staticFields == null) { return 0; } return staticFields.length; } /** * @return The number of instance fields in this ClassDataItem */ public int getInstanceFieldCount() { if (instanceFields == null) { return 0; } return instanceFields.length; } /** * @return The number of direct methods in this ClassDataItem */ public int getDirectMethodCount() { if (directMethods == null) { return 0; } return directMethods.length; } /** * @return The number of virtual methods in this ClassDataItem */ public int getVirtualMethodCount() { if (virtualMethods == null) { return 0; } return virtualMethods.length; } /** * @return true if this is an empty ClassDataItem */ public boolean isEmpty() { return (getStaticFieldCount() + getInstanceFieldCount() + getDirectMethodCount() + getVirtualMethodCount()) == 0; } /** * Performs a binary search for the definition of the specified direct method * @param methodIdItem The MethodIdItem of the direct method to search for * @return The EncodedMethod for the specified direct method, or null if not found */ public EncodedMethod findDirectMethodByMethodId(MethodIdItem methodIdItem) { return findMethodByMethodIdInternal(methodIdItem.index, directMethods); } /** * Performs a binary search for the definition of the specified virtual method * @param methodIdItem The MethodIdItem of the virtual method to search for * @return The EncodedMethod for the specified virtual method, or null if not found */ public EncodedMethod findVirtualMethodByMethodId(MethodIdItem methodIdItem) { return findMethodByMethodIdInternal(methodIdItem.index, virtualMethods); } /** * Performs a binary search for the definition of the specified method. It can be either direct or virtual * @param methodIdItem The MethodIdItem of the virtual method to search for * @return The EncodedMethod for the specified virtual method, or null if not found */ public EncodedMethod findMethodByMethodId(MethodIdItem methodIdItem) { EncodedMethod encodedMethod = findMethodByMethodIdInternal(methodIdItem.index, directMethods); if (encodedMethod != null) { return encodedMethod; } return findMethodByMethodIdInternal(methodIdItem.index, virtualMethods); } private static EncodedMethod findMethodByMethodIdInternal(int methodIdItemIndex, EncodedMethod[] encodedMethods) { int min = 0; int max = encodedMethods.length; while (min>1; EncodedMethod encodedMethod = encodedMethods[index]; int encodedMethodIndex = encodedMethod.method.getIndex(); if (encodedMethodIndex == methodIdItemIndex) { return encodedMethod; } else if (encodedMethodIndex < methodIdItemIndex) { if (min == index) { break; } min = index; } else { if (max == index) { break; } max = index; } } return null; } public static class EncodedField implements Comparable { /** * The FieldIdItem that this EncodedField is associated with */ public final FieldIdItem field; /** * The access flags for this field */ public final int accessFlags; /** * Constructs a new EncodedField with the given values * @param field The FieldIdItem that this EncodedField is associated with * @param accessFlags The access flags for this field */ public EncodedField(FieldIdItem field, int accessFlags) { this.field = field; this.accessFlags = accessFlags; } /** * This is used internally to construct a new EncodedField while reading in a DexFile * @param dexFile The DexFile that is being read in * @param in the Input object to read the EncodedField from * @param previousEncodedField The previous EncodedField in the list containing this * EncodedField. */ private EncodedField(DexFile dexFile, Input in, @Nullable EncodedField previousEncodedField) { int previousIndex = previousEncodedField==null?0:previousEncodedField.field.getIndex(); field = dexFile.FieldIdsSection.getItemByIndex(in.readUnsignedLeb128() + previousIndex); accessFlags = in.readUnsignedLeb128(); } /** * Writes the EncodedField to the given AnnotatedOutput object * @param out the AnnotatedOutput object to write to * @param previousEncodedField The previous EncodedField in the list containing this * EncodedField. */ private void writeTo(AnnotatedOutput out, EncodedField previousEncodedField) { int previousIndex = previousEncodedField==null?0:previousEncodedField.field.getIndex(); if (out.annotates()) { out.annotate("field: " + field.getFieldString()); out.writeUnsignedLeb128(field.getIndex() - previousIndex); out.annotate("access_flags: " + AccessFlags.formatAccessFlagsForField(accessFlags)); out.writeUnsignedLeb128(accessFlags); }else { out.writeUnsignedLeb128(field.getIndex() - previousIndex); out.writeUnsignedLeb128(accessFlags); } } /** * Calculates the size of this EncodedField and returns the offset * immediately following it * @param offset the offset of this EncodedField in the DexFile * @param previousEncodedField The previous EncodedField in the list containing this * EncodedField. * @return the offset immediately following this EncodedField */ private int place(int offset, EncodedField previousEncodedField) { int previousIndex = previousEncodedField==null?0:previousEncodedField.field.getIndex(); offset += Leb128Utils.unsignedLeb128Size(field.getIndex() - previousIndex); offset += Leb128Utils.unsignedLeb128Size(accessFlags); return offset; } /** * Compares this EncodedField to another, based on the comparison of the associated * FieldIdItem * @param other The EncodedField to compare against * @return a standard integer comparison value indicating the relationship */ public int compareTo(EncodedField other) { return field.compareTo(other.field); } /** * Determines if this EncodedField is equal to other, based on the equality of the associated * FieldIdItem * @param other The EncodedField to test for equality * @return true if other is equal to this instance, otherwise false */ public boolean equals(Object other) { if (other instanceof EncodedField) { return compareTo((EncodedField)other) == 0; } return false; } /** * @return true if this is a static field */ public boolean isStatic() { return (accessFlags & AccessFlags.STATIC.getValue()) != 0; } } public static class EncodedMethod implements Comparable { /** * The MethodIdItem that this EncodedMethod is associated with */ public final MethodIdItem method; /** * The access flags for this method */ public final int accessFlags; /** * The CodeItem containing the code for this method, or null if there is no code for this method * (i.e. an abstract method) */ public final CodeItem codeItem; /** * Constructs a new EncodedMethod with the given values * @param method The MethodIdItem that this EncodedMethod is associated with * @param accessFlags The access flags for this method * @param codeItem The CodeItem containing the code for this method, or null if there is no code * for this method (i.e. an abstract method) */ public EncodedMethod(MethodIdItem method, int accessFlags, CodeItem codeItem) { this.method = method; this.accessFlags = accessFlags; this.codeItem = codeItem; if (codeItem != null) { codeItem.setParent(this); } } /** * This is used internally to construct a new EncodedMethod while reading in a DexFile * @param dexFile The DexFile that is being read in * @param readContext a ReadContext object to hold information that is only needed while reading * in a file * @param in the Input object to read the EncodedMethod from * @param previousEncodedMethod The previous EncodedMethod in the list containing this * EncodedMethod. */ public EncodedMethod(DexFile dexFile, ReadContext readContext, Input in, EncodedMethod previousEncodedMethod) { int previousIndex = previousEncodedMethod==null?0:previousEncodedMethod.method.getIndex(); method = dexFile.MethodIdsSection.getItemByIndex(in.readUnsignedLeb128() + previousIndex); accessFlags = in.readUnsignedLeb128(); if (dexFile.skipInstructions()) { in.readUnsignedLeb128(); codeItem = null; } else { codeItem = (CodeItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_CODE_ITEM, in.readUnsignedLeb128()); } if (codeItem != null) { codeItem.setParent(this); } } /** * Writes the EncodedMethod to the given AnnotatedOutput object * @param out the AnnotatedOutput object to write to * @param previousEncodedMethod The previous EncodedMethod in the list containing this * EncodedMethod. */ private void writeTo(AnnotatedOutput out, EncodedMethod previousEncodedMethod) { int previousIndex = previousEncodedMethod==null?0:previousEncodedMethod.method.getIndex(); if (out.annotates()) { out.annotate("method: " + method.getMethodString()); out.writeUnsignedLeb128(method.getIndex() - previousIndex); out.annotate("access_flags: " + AccessFlags.formatAccessFlagsForMethod(accessFlags)); out.writeUnsignedLeb128(accessFlags); if (codeItem != null) { out.annotate("code_off: 0x" + Integer.toHexString(codeItem.getOffset())); out.writeUnsignedLeb128(codeItem.getOffset()); } else { out.annotate("code_off: 0x0"); out.writeUnsignedLeb128(0); } }else { out.writeUnsignedLeb128(method.getIndex() - previousIndex); out.writeUnsignedLeb128(accessFlags); out.writeUnsignedLeb128(codeItem==null?0:codeItem.getOffset()); } } /** * Calculates the size of this EncodedMethod and returns the offset * immediately following it * @param offset the offset of this EncodedMethod in the DexFile * @param previousEncodedMethod The previous EncodedMethod in the list containing this * EncodedMethod. * @return the offset immediately following this EncodedField */ private int place(int offset, EncodedMethod previousEncodedMethod) { int previousIndex = previousEncodedMethod==null?0:previousEncodedMethod.method.getIndex(); offset += Leb128Utils.unsignedLeb128Size(method.getIndex() - previousIndex); offset += Leb128Utils.unsignedLeb128Size(accessFlags); offset += codeItem==null?1:Leb128Utils.unsignedLeb128Size(codeItem.getOffset()); return offset; } /** * Compares this EncodedMethod to another, based on the comparison of the associated * MethodIdItem * @param other The EncodedMethod to compare against * @return a standard integer comparison value indicating the relationship */ public int compareTo(EncodedMethod other) { return method.compareTo(other.method); } /** * Determines if this EncodedMethod is equal to other, based on the equality of the associated * MethodIdItem * @param other The EncodedMethod to test for equality * @return true if other is equal to this instance, otherwise false */ public boolean equals(Object other) { if (other instanceof EncodedMethod) { return compareTo((EncodedMethod)other) == 0; } return false; } /** * @return true if this is a direct method */ public boolean isDirect() { return ((accessFlags & (AccessFlags.STATIC.getValue() | AccessFlags.PRIVATE.getValue() | AccessFlags.CONSTRUCTOR.getValue())) != 0); } } }