/* * [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.Util.*; import java.util.Collections; import java.util.List; public class ClassDataItem extends Item<ClassDataItem> { private EncodedField[] staticFields; private EncodedField[] instanceFields; private EncodedMethod[] directMethods; private EncodedMethod[] virtualMethods; private ClassDefItem parent = null; /** * Creates a new uninitialized <code>ClassDataItem</code> * @param dexFile The <code>DexFile</code> that this item belongs to */ public ClassDataItem(final DexFile dexFile) { super(dexFile); } /** * Creates a new <code>ClassDataItem</code> with the given values * @param dexFile The <code>DexFile</code> 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, EncodedField[] staticFields, EncodedField[] instanceFields, EncodedMethod[] directMethods, EncodedMethod[] virtualMethods) { super(dexFile); this.staticFields = staticFields==null?new EncodedField[0]:staticFields; this.instanceFields = instanceFields==null?new EncodedField[0]:instanceFields; this.directMethods = directMethods==null?new EncodedMethod[0]:directMethods; this.virtualMethods = virtualMethods==null?new EncodedMethod[0]:virtualMethods; } /** * Creates a new <code>ClassDataItem</code> with the given values * @param dexFile The <code>DexFile</code> 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 <code>ClassDataItem</code> with the given values */ public static ClassDataItem internClassDataItem(DexFile dexFile, List<EncodedField> staticFields, List<EncodedField> instanceFields, List<EncodedMethod> directMethods, List<EncodedMethod> virtualMethods) { EncodedField[] staticFieldsArray = null; EncodedField[] instanceFieldsArray = null; EncodedMethod[] directMethodsArray = null; EncodedMethod[] virtualMethodsArray = null; if (staticFields != null && staticFields.size() > 0) { Collections.sort(staticFields); staticFieldsArray = new EncodedField[staticFields.size()]; staticFields.toArray(staticFieldsArray); } if (instanceFields != null && instanceFields.size() > 0) { Collections.sort(instanceFields); instanceFieldsArray = new EncodedField[instanceFields.size()]; instanceFields.toArray(instanceFieldsArray); } if (directMethods != null && directMethods.size() > 0) { Collections.sort(directMethods); directMethodsArray = new EncodedMethod[directMethods.size()]; directMethods.toArray(directMethodsArray); } if (virtualMethods != null && virtualMethods.size() > 0) { Collections.sort(virtualMethods); virtualMethodsArray = new EncodedMethod[virtualMethods.size()]; virtualMethods.toArray(virtualMethodsArray); } ClassDataItem classDataItem = new ClassDataItem(dexFile, staticFieldsArray, instanceFieldsArray, directMethodsArray, virtualMethodsArray); return dexFile.ClassDataSection.intern(classDataItem); } /** {@inheritDoc} */ protected void readItem(Input in, ReadContext readContext) { staticFields = new EncodedField[in.readUnsignedLeb128()]; instanceFields = new EncodedField[in.readUnsignedLeb128()]; directMethods = new EncodedMethod[in.readUnsignedLeb128()]; virtualMethods = new EncodedMethod[in.readUnsignedLeb128()]; EncodedField previousEncodedField = null; for (int i=0; i<staticFields.length; i++) { try { staticFields[i] = previousEncodedField = new EncodedField(dexFile, in, previousEncodedField); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Error while reading static field at index " + i); } } previousEncodedField = null; for (int i=0; i<instanceFields.length; i++) { try { instanceFields[i] = previousEncodedField = new EncodedField(dexFile, in, previousEncodedField); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Error while reading instance field at index " + i); } } EncodedMethod previousEncodedMethod = null; for (int i=0; i<directMethods.length; i++) { try { directMethods[i] = previousEncodedMethod = new EncodedMethod(dexFile, readContext, in, previousEncodedMethod); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Error while reading direct method at index " + i); } } previousEncodedMethod = null; for (int i=0; i<virtualMethods.length; i++) { try { virtualMethods[i] = previousEncodedMethod = new EncodedMethod(dexFile, readContext, in, previousEncodedMethod); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Error while reading virtual method at index " + i); } } } /** {@inheritDoc} */ protected int placeItem(int offset) { offset += Leb128Utils.unsignedLeb128Size(staticFields.length); offset += Leb128Utils.unsignedLeb128Size(instanceFields.length); offset += Leb128Utils.unsignedLeb128Size(directMethods.length); offset += Leb128Utils.unsignedLeb128Size(virtualMethods.length); EncodedField previousEncodedField = null; for (EncodedField encodedField: staticFields) { offset = encodedField.place(offset, previousEncodedField); previousEncodedField = encodedField; } previousEncodedField = null; for (EncodedField encodedField: instanceFields) { offset = encodedField.place(offset, previousEncodedField); previousEncodedField = encodedField; } EncodedMethod previousEncodedMethod = null; for (EncodedMethod encodedMethod: directMethods) { offset = encodedMethod.place(offset, previousEncodedMethod); previousEncodedMethod = encodedMethod; } previousEncodedMethod = null; for (EncodedMethod encodedMethod: virtualMethods) { offset = encodedMethod.place(offset, previousEncodedMethod); previousEncodedMethod = encodedMethod; } return offset; } /** {@inheritDoc} */ protected void writeItem(AnnotatedOutput out) { if (out.annotates()) { out.annotate("static_fields_size: 0x" + Integer.toHexString(staticFields.length) + " (" + staticFields.length + ")"); out.writeUnsignedLeb128(staticFields.length); out.annotate("instance_fields_size: 0x" + Integer.toHexString(instanceFields.length) + " (" + instanceFields.length + ")"); out.writeUnsignedLeb128(instanceFields.length); out.annotate("direct_methods_size: 0x" + Integer.toHexString(directMethods.length) + " (" + directMethods.length + ")"); out.writeUnsignedLeb128(directMethods.length); out.annotate("virtual_methods_size: 0x" + Integer.toHexString(virtualMethods.length) + " (" + virtualMethods.length + ")"); out.writeUnsignedLeb128(virtualMethods.length); int index = 0; EncodedField previousEncodedField = null; for (EncodedField encodedField: staticFields) { out.annotate("[" + index++ + "] static_field"); out.indent(); encodedField.writeTo(out, previousEncodedField); out.deindent(); previousEncodedField = encodedField; } index = 0; previousEncodedField = null; for (EncodedField encodedField: instanceFields) { out.annotate("[" + index++ + "] instance_field"); out.indent(); encodedField.writeTo(out, previousEncodedField); out.deindent(); previousEncodedField = encodedField; } index = 0; EncodedMethod previousEncodedMethod = null; for (EncodedMethod encodedMethod: directMethods) { out.annotate("[" + index++ + "] direct_method"); out.indent(); encodedMethod.writeTo(out, previousEncodedMethod); out.deindent(); previousEncodedMethod = encodedMethod; } index = 0; previousEncodedMethod = null; for (EncodedMethod encodedMethod: virtualMethods) { out.annotate("[" + index++ + "] virtual_method"); out.indent(); encodedMethod.writeTo(out, previousEncodedMethod); out.deindent(); previousEncodedMethod = encodedMethod; } } else { out.writeUnsignedLeb128(staticFields.length); out.writeUnsignedLeb128(instanceFields.length); out.writeUnsignedLeb128(directMethods.length); out.writeUnsignedLeb128(virtualMethods.length); EncodedField previousEncodedField = null; for (EncodedField encodedField: staticFields) { encodedField.writeTo(out, previousEncodedField); previousEncodedField = encodedField; } previousEncodedField = null; for (EncodedField encodedField: instanceFields) { encodedField.writeTo(out, previousEncodedField); previousEncodedField = encodedField; } EncodedMethod previousEncodedMethod = null; for (EncodedMethod encodedMethod: directMethods) { encodedMethod.writeTo(out, previousEncodedMethod); previousEncodedMethod = encodedMethod; } previousEncodedMethod = null; for (EncodedMethod encodedMethod: virtualMethods) { encodedMethod.writeTo(out, previousEncodedMethod); previousEncodedMethod = encodedMethod; } } } /** {@inheritDoc} */ public ItemType getItemType() { return ItemType.TYPE_CLASS_DATA_ITEM; } /** {@inheritDoc} */ public String getConciseIdentity() { if (parent == null) { return "class_data_item @0x" + Integer.toHexString(getOffset()); } return "class_data_item @0x" + Integer.toHexString(getOffset()) + " (" + parent.getClassType() +")"; } /** {@inheritDoc} */ public int compareTo(ClassDataItem other) { if (parent == null) { if (other.parent == null) { return 0; } return -1; } if (other.parent == null) { return 1; } return parent.compareTo(other.parent); } /** * Sets the <code>ClassDefItem</code> that this <code>ClassDataItem</code> is associated with * @param classDefItem the <code>ClassDefItem</code> that this <code>ClassDataItem</code> is associated with */ protected void setParent(ClassDefItem classDefItem) { this.parent = classDefItem; } /** * @return the static fields for this class */ public EncodedField[] getStaticFields() { return staticFields; } /** * @return the instance fields for this class */ public EncodedField[] getInstanceFields() { return instanceFields; } /** * @return the direct methods for this class */ public EncodedMethod[] getDirectMethods() { return directMethods; } /** * @return the virtual methods for this class */ public EncodedMethod[] getVirtualMethods() { return virtualMethods; } public static class EncodedField implements Comparable<EncodedField> { /** * The <code>FieldIdItem</code> that this <code>EncodedField</code> is associated with */ public final FieldIdItem field; /** * The access flags for this field */ public final int accessFlags; /** * Constructs a new <code>EncodedField</code> with the given values * @param field The <code>FieldIdItem</code> that this <code>EncodedField</code> 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 <code>EncodedField</code> while reading in a <code>DexFile</code> * @param dexFile The <code>DexFile</code> that is being read in * @param in the Input object to read the <code>EncodedField</code> from * @param previousEncodedField The previous <code>EncodedField</code> in the list containing this * <code>EncodedField</code>. */ private EncodedField(DexFile dexFile, Input in, EncodedField previousEncodedField) { int previousIndex = previousEncodedField==null?0:previousEncodedField.field.getIndex(); field = dexFile.FieldIdsSection.getItemByIndex(in.readUnsignedLeb128() + previousIndex); accessFlags = in.readUnsignedLeb128(); } /** * Writes the <code>EncodedField</code> to the given <code>AnnotatedOutput</code> object * @param out the <code>AnnotatedOutput</code> object to write to * @param previousEncodedField The previous <code>EncodedField</code> in the list containing this * <code>EncodedField</code>. */ 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 <code>EncodedField</code> and returns the offset * immediately following it * @param offset the offset of this <code>EncodedField</code> in the <code>DexFile</code> * @param previousEncodedField The previous <code>EncodedField</code> in the list containing this * <code>EncodedField</code>. * @return the offset immediately following this <code>EncodedField</code> */ 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 <code>EncodedField</code> to another, based on the comparison of the associated * <code>FieldIdItem</code> * @param other The <code>EncodedField</code> to compare against * @return a standard integer comparison value indicating the relationship */ public int compareTo(EncodedField other) { return field.compareTo(other.field); } /** * @return true if this is a static field */ public boolean isStatic() { return (accessFlags & AccessFlags.STATIC.getValue()) != 0; } } public static class EncodedMethod implements Comparable<EncodedMethod> { /** * The <code>MethodIdItem</code> that this <code>EncodedMethod</code> is associated with */ public final MethodIdItem method; /** * The access flags for this method */ public final int accessFlags; /** * The <code>CodeItem</code> 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 <code>EncodedMethod</code> with the given values * @param method The <code>MethodIdItem</code> that this <code>EncodedMethod</code> is associated with * @param accessFlags The access flags for this method * @param codeItem The <code>CodeItem</code> 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 <code>EncodedMethod</code> while reading in a <code>DexFile</code> * @param dexFile The <code>DexFile</code> that is being read in * @param readContext a <code>ReadContext</code> object to hold information that is only needed while reading * in a file * @param in the Input object to read the <code>EncodedMethod</code> from * @param previousEncodedMethod The previous <code>EncodedMethod</code> in the list containing this * <code>EncodedMethod</code>. */ 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 <code>EncodedMethod</code> to the given <code>AnnotatedOutput</code> object * @param out the <code>AnnotatedOutput</code> object to write to * @param previousEncodedMethod The previous <code>EncodedMethod</code> in the list containing this * <code>EncodedMethod</code>. */ 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 <code>EncodedMethod</code> and returns the offset * immediately following it * @param offset the offset of this <code>EncodedMethod</code> in the <code>DexFile</code> * @param previousEncodedMethod The previous <code>EncodedMethod</code> in the list containing this * <code>EncodedMethod</code>. * @return the offset immediately following this <code>EncodedField</code> */ 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 <code>EncodedMethod</code> to another, based on the comparison of the associated * <code>MethodIdItem</code> * @param other The <code>EncodedMethod</code> to compare against * @return a standard integer comparison value indicating the relationship */ public int compareTo(EncodedMethod other) { return method.compareTo(other.method); } /** * @return true if this is a direct method */ public boolean isDirect() { return ((accessFlags & (AccessFlags.STATIC.getValue() | AccessFlags.PRIVATE.getValue() | AccessFlags.CONSTRUCTOR.getValue())) != 0); } } }