diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java new file mode 100644 index 00000000..fa9b5c36 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013, 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.analysis; + +import com.google.common.base.Strings; +import org.jf.dexlib2.util.TypeUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArrayProto implements TypeProto { + protected final ClassPath classPath; + protected final int dimensions; + protected final String elementType; + + public ArrayProto(@Nonnull ClassPath classPath, @Nonnull String type) { + this.classPath = classPath; + int i=0; + while (type.charAt(i) == '[') { + i++; + if (i == type.length()) { + throw new ExceptionWithContext("Invalid array type: %s", type); + } + } + + dimensions = i; + elementType = type.substring(i); + } + + @Override public String toString() { return getType(); } + @Nonnull @Override public ClassPath getClassPath() { return classPath; } + @Nonnull @Override public String getType() { return makeArrayType(elementType, dimensions); } + @Nonnull public String getElementType() { return elementType; } + @Override public boolean isInterface() { return false; } + + @Override public boolean implementsInterface(@Nonnull String iface) { + return iface.equals("Ljava/lang/Cloneable;") || iface.equals("Ljava/io/Serializable;"); + } + + @Nullable @Override + public String getSuperclass() { + return "Ljava/lang/Object;"; + } + + @Nonnull @Override + public TypeProto getCommonSuperclass(@Nonnull TypeProto other) { + if (other instanceof ArrayProto) { + if (TypeUtils.isPrimitiveType(getElementType()) || + TypeUtils.isPrimitiveType(((ArrayProto)other).getElementType())) { + if (dimensions == ((ArrayProto)other).dimensions && + getElementType().equals(((ArrayProto)other).getElementType())) { + return this; + } + return classPath.getClass("Ljava/lang/Object;"); + } + + if (dimensions == ((ArrayProto)other).dimensions) { + TypeProto thisClass = classPath.getClass(elementType); + TypeProto otherClass = classPath.getClass(((ArrayProto)other).elementType); + TypeProto mergedClass = thisClass.getCommonSuperclass(otherClass); + if (thisClass == mergedClass) { + return this; + } + if (otherClass == mergedClass) { + return other; + } + return classPath.getClass(makeArrayType(mergedClass.getType(), dimensions)); + } + + int dimensions = Math.min(this.dimensions, ((ArrayProto)other).dimensions); + return classPath.getClass(makeArrayType("Ljava/lang/Object;", dimensions)); + } + + if (other instanceof ClassProto) { + try { + if (other.isInterface()) { + if (implementsInterface(other.getType())) { + return other; + } + } + } catch (UnresolvedClassException ex) { + // ignore + } + return classPath.getClass("Ljava/lang/Object;"); + } + + // otherwise, defer to the other class' getCommonSuperclass + return other.getCommonSuperclass(this); + } + + private static final String BRACKETS = Strings.repeat("[", 256); + + @Nonnull + private static String makeArrayType(@Nonnull String elementType, int dimensions) { + return BRACKETS.substring(0, dimensions) + elementType; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index 877fe780..fbd150c4 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -31,18 +31,22 @@ package org.jf.dexlib2.analysis; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.analysis.reflection.ReflectionClassDef; import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.DexFile; +import org.jf.dexlib2.immutable.ImmutableDexFile; import javax.annotation.Nonnull; import java.io.IOException; +import java.io.Serializable; import java.util.HashMap; public class ClassPath { - private DexBackedDexFile[] dexFiles; - - private HashMap loadedClasses = Maps.newHashMap(); + @Nonnull private final TypeProto unknownClass; + @Nonnull private DexFile[] dexFiles; + @Nonnull private HashMap loadedClasses = Maps.newHashMap(); /** * Creates a new ClassPath instance that can load classes from the given dex files @@ -50,25 +54,44 @@ public class ClassPath { * @param classPath An array of DexBackedDexFile objects. When loading a class, these dex files will be searched * in order */ - public ClassPath(DexBackedDexFile[] classPath) throws IOException { - dexFiles = new DexBackedDexFile[classPath.length]; + public ClassPath(DexFile... classPath) throws IOException { + dexFiles = new DexFile[classPath.length+1]; System.arraycopy(classPath, 0, dexFiles, 0, classPath.length); + // add fallbacks for certain special classes that must be present + dexFiles[dexFiles.length - 1] = getBasicClasses(); + + unknownClass = new UnknownClassProto(this); + loadedClasses.put(unknownClass.getType(), unknownClass); + } + + private static DexFile getBasicClasses() { + return new ImmutableDexFile(ImmutableSet.of( + new ReflectionClassDef(Object.class), + new ReflectionClassDef(Cloneable.class), + new ReflectionClassDef(Serializable.class))); } @Nonnull - public ClassProto getClass(String type) { - ClassProto loadedClass = loadedClasses.get(type); - if (loadedClass != null) { - return loadedClass; + public TypeProto getClass(String type) { + TypeProto typeProto = loadedClasses.get(type); + if (typeProto != null) { + return typeProto; } - ClassProto classProto = new ClassProto(this, type); - loadedClasses.put(type, classProto); - return classProto; + + if (type.charAt(0) == '[') { + typeProto = new ArrayProto(this, type); + } else { + typeProto = new ClassProto(this, type); + } + + loadedClasses.put(type, typeProto); + return typeProto; } @Nonnull public ClassDef getClassDef(String type) { - for (DexBackedDexFile dexFile: dexFiles) { + // TODO: need a <= O(log) way to look up classes + for (DexFile dexFile: dexFiles) { for (ClassDef classDef: dexFile.getClasses()) { if (classDef.getType().equals(type)) { return classDef; @@ -77,4 +100,37 @@ public class ClassPath { } throw new UnresolvedClassException("Could not resolve class %s", type); } + + @Nonnull + public RegisterType getRegisterTypeForType(@Nonnull String type) { + switch (type.charAt(0)) { + case 'Z': + return RegisterType.getRegisterType(RegisterType.BOOLEAN, null); + case 'B': + return RegisterType.getRegisterType(RegisterType.BYTE, null); + case 'S': + return RegisterType.getRegisterType(RegisterType.SHORT, null); + case 'C': + return RegisterType.getRegisterType(RegisterType.CHAR, null); + case 'I': + return RegisterType.getRegisterType(RegisterType.INTEGER, null); + case 'F': + return RegisterType.getRegisterType(RegisterType.FLOAT, null); + case 'J': + return RegisterType.getRegisterType(RegisterType.LONG_LO, null); + case 'D': + return RegisterType.getRegisterType(RegisterType.DOUBLE_LO, null); + case 'L': + case 'U': + case '[': + return RegisterType.getRegisterType(RegisterType.REFERENCE, getClass(type)); + default: + throw new RuntimeException("Invalid type: " + type); + } + } + + @Nonnull + public TypeProto getUnknownClass() { + return unknownClass; + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index baaab476..4626587d 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -31,177 +31,237 @@ package org.jf.dexlib2.analysis; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.iface.ClassDef; -import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; +import java.util.Set; /** * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields * and their offsets. */ -public class ClassProto { - @Nonnull public final ClassPath classPath; - @Nonnull public final String type; - @Nullable private ClassDef classDef; +public class ClassProto implements TypeProto { + @Nonnull protected final ClassPath classPath; + @Nonnull protected final String type; + @Nullable protected ClassDef classDef; + @Nullable protected Set interfaces; + protected boolean interfacesFullyResolved = true; public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { this.classPath = classPath; this.type = type; } + @Override public String toString() { return type; } + @Nonnull @Override public ClassPath getClassPath() { return classPath; } + @Nonnull @Override public String getType() { return type; } + @Nonnull - public ClassDef getClassDef() { + protected ClassDef getClassDef() { if (classDef == null) { classDef = classPath.getClassDef(type); } return classDef; } - @Nonnull - public String getType() { - return type; - } - + /** + * Returns true if this class is an interface. + * + * If this class is not defined, then this will throw an UnresolvedClassException + * + * @return True if this class is an interface + */ public boolean isInterface() { - // TODO: implement - return false; + ClassDef classDef = getClassDef(); + return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; } - public boolean implementsInterface(String iface) { - // TODO: implement - return false; + private void addInterfacesRecursively(@Nonnull ClassDef classDef) { + assert interfaces != null; + for (String iface: classDef.getInterfaces()) { + interfaces.add(iface); + addInterfacesRecursively(iface); + } + } + + private void addInterfacesRecursively(@Nonnull String cls) { + ClassDef classDef; + try { + classDef = classPath.getClassDef(cls); + addInterfacesRecursively(classDef); + } catch (UnresolvedClassException ex) { + interfacesFullyResolved = false; + } + } + + @Nonnull + protected Set getInterfaces() { + if (interfaces != null) { + return interfaces; + } + + interfaces = Sets.newHashSet(); + + try { + ClassDef classDef = getClassDef(); + + if (isInterface()) { + interfaces.add(getType()); + } + + while (true) { + addInterfacesRecursively(classDef); + + String superclass = classDef.getSuperclass(); + if (superclass != null) { + classDef = classPath.getClassDef(superclass); + } else { + break; + } + } + } catch (UnresolvedClassException ex) { + interfacesFullyResolved = false; + } + + return interfaces; } /** - * Get the chain of superclasses of this class. The first element will be the immediate superclass followed by - * it's superclass, etc. up to java.lang.Object. + * Checks if this class implements the given interface. * - * Returns an empty iterable if called on java.lang.Object. + * If the interfaces of this class cannot be fully resolved then this + * method will either return true or throw an UnresolvedClassException * - * @return An iterable containing the superclasses of this class. - * @throws UnresolvedClassException if any class in the chain can't be resolved + * @param iface The interface to check for + * @return true if this class implements the given interface, otherwise false */ - @Nonnull - public Iterable getSuperclassChain() { - final ClassDef topClassDef = this.getClassDef(); - - return new Iterable() { - private ClassDef classDef = topClassDef; - - @Override public Iterator iterator() { - return new Iterator() { - @Override public boolean hasNext() { - return classDef == null || classDef.getSuperclass() == null; - } - - @Override public String next() { - if (classDef == null) { - throw new NoSuchElementException(); - } - - String next = classDef.getSuperclass(); - if (next == null) { - throw new NoSuchElementException(); - } - - classDef = classPath.getClassDef(next); - return next; - } - - @Override public void remove() { - throw new UnsupportedOperationException(); - } - }; + @Override + public boolean implementsInterface(@Nonnull String iface) { + for (String implementIface: getInterfaces()) { + if (implementIface.equals(iface)) { + return true; } - }; + } + if (!interfacesFullyResolved) { + throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); + } + return false; } - @Nonnull public ClassProto getCommonSuperclass(@Nonnull ClassProto other) { + @Nullable @Override + public String getSuperclass() { + return getClassDef().getSuperclass(); + } + + /** + * This is a helper method for getCommonSuperclass + * + * It checks if this class is an interface, and if so, if other implements it. + * + * If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an + * UndefinedClassException + * + * If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found, + * we throw an UndefinedClassException + * + * @param other The class to check the interfaces of + * @return true if this class is an interface (or is undefined) other implements this class + * + */ + private boolean checkInterface(@Nonnull ClassProto other) { + boolean isResolved = true; + boolean isInterface = true; + try { + isInterface = isInterface(); + } catch (UnresolvedClassException ex) { + isResolved = false; + // if we don't know if this class is an interface or not, + // we can still try to call other.implementsInterface(this) + } + if (isInterface) { + try { + if (other.implementsInterface(getType())) { + return true; + } + } catch (UnresolvedClassException ex) { + // There are 2 possibilities here, depending on whether we were able to resolve this class. + // 1. If this class is resolved, then we know it is an interface class. The other class either + // isn't defined, or its interfaces couldn't be fully resolved. + // In this case, we throw an UnresolvedClassException + // 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't + // know for sure if this class is an interface or not. We return false, and let processing + // continue in getCommonSuperclass + if (isResolved) { + throw ex; + } + } + } + return false; + } + + @Override @Nonnull + public TypeProto getCommonSuperclass(@Nonnull TypeProto other) { + // use the other type's more specific implementation + if (!(other instanceof ClassProto)) { + return other.getCommonSuperclass(this); + } + if (this == other || getType().equals(other.getType())) { return this; } - if (isInterface()) { - if (other.implementsInterface(getType())) { + if (this.getType().equals("Ljava/lang/Object;")) { + return this; + } + + if (other.getType().equals("Ljava/lang/Object;")) { + return other; + } + + boolean gotException = false; + try { + if (checkInterface((ClassProto)other)) { return this; } - return classPath.getClass("Ljava/lang/Object;"); + } catch (UnresolvedClassException ex) { + gotException = true; } - if (other.isInterface()) { - if (implementsInterface(other.getType())) { + try { + if (((ClassProto)other).checkInterface(this)) { return other; } - return classPath.getClass("Ljava/lang/Object;"); - } - - boolean thisResolved = true; - boolean otherResolved = true; - List thisChain = Lists.newArrayList(getType()); - List otherChain = Lists.newArrayList(other.getType()); - - // grab as much of the superclass chain as we can for both types, - // and keep track of whether we were able to get all of it - try { - for (String type: getSuperclassChain()) { - thisChain.add(type); - } } catch (UnresolvedClassException ex) { - thisResolved = false; + gotException = true; + } + if (gotException) { + return classPath.getUnknownClass(); } - try { - for (String type: other.getSuperclassChain()) { - otherChain.add(type); - } - } catch (UnresolvedClassException ex) { - otherResolved = false; - } + List thisChain = Lists.newArrayList(this); + Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this)); - // if both were resolved, then we start looking backwards from the end of the shorter chain, until - // we find a pair of entries in the chains that match - if (thisResolved && otherResolved) { - for (int i=Math.min(thisChain.size(), otherChain.size()); i>=0; i--) { - String type = thisChain.get(i); - if (type.equals(otherChain.get(i))) { - return classPath.getClass(type); - } - } - // "This should never happen" - throw new ExceptionWithContext("Wasn't able to find a common superclass for %s and %s", this.getType(), - other.getType()); - } + List otherChain = Lists.newArrayList(other); + Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other)); - // we weren't able to fully resolve both classes. Let's see if we can find a common superclass in what we - // were able to resolve - for (String thisType: thisChain) { - for (String otherType: otherChain) { - if (thisType.equals(otherType)) { - return classPath.getClass(thisType); - } + // reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object; + thisChain = Lists.reverse(thisChain); + otherChain = Lists.reverse(otherChain); + + for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) { + TypeProto typeProto = thisChain.get(i); + if (typeProto.getType().equals(otherChain.get(i).getType())) { + return typeProto; } } - // Nope. We'll throw an UnresolvedClassException. The caller can catch the exception and use java.lang.Object - // as the superclass, if it is appropriate to do so - if (!thisResolved) { - if (!otherResolved) { - throw new UnresolvedClassException( - "Could not fully resolve %s or %s while getting their common superclass", - getType(), other.getType()); - } else { - throw new UnresolvedClassException( - "Could not fully resolve %s while getting common superclass with %s", - getType(), other.getType()); - } - } - throw new UnresolvedClassException( - "Could not fully resolve %s while getting common superclass with %s", other.getType(), getType()); + return classPath.getUnknownClass(); } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/PrimitiveProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/PrimitiveProto.java new file mode 100644 index 00000000..c0b17270 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/PrimitiveProto.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013, 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.analysis; + +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrimitiveProto implements TypeProto { + protected final ClassPath classPath; + protected final String type; + + public PrimitiveProto(@Nonnull ClassPath classPath, @Nonnull String type) { + this.classPath = classPath; + this.type = type; + } + + @Override public String toString() { return type; } + @Nonnull @Override public ClassPath getClassPath() { return classPath; } + @Nonnull @Override public String getType() { return type; } + @Override public boolean isInterface() { return false; } + @Override public boolean implementsInterface(@Nonnull String iface) { return false; } + @Nullable @Override public String getSuperclass() { return null; } + @Nonnull @Override public TypeProto getCommonSuperclass(@Nonnull TypeProto other) { + throw new ExceptionWithContext("Cannot call getCommonSuperclass on PrimitiveProto"); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java index 9fb3b2d1..df3d03e1 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java @@ -41,9 +41,9 @@ import java.io.Writer; public class RegisterType { public final byte category; - @Nullable public final String type; + @Nullable public final TypeProto type; - private RegisterType(byte category, @Nullable String type) { + private RegisterType(byte category, @Nullable TypeProto type) { assert ((category == REFERENCE || category == UNINIT_REF || category == UNINIT_THIS) && type != null) || ((category != REFERENCE && category != UNINIT_REF && category != UNINIT_THIS) && type == null); @@ -61,7 +61,7 @@ public class RegisterType { writer.write(CATEGORY_NAMES[category]); if (type != null) { writer.write(','); - writer.write(type); + writer.write(type.getType()); } writer.write(')'); } @@ -184,34 +184,6 @@ public class RegisterType { public static final RegisterType DOUBLE_HI_TYPE = new RegisterType(DOUBLE_HI, null); public static final RegisterType CONFLICTED_TYPE = new RegisterType(CONFLICTED, null); - @Nonnull - public static RegisterType getRegisterTypeForType(@Nonnull String type) { - switch (type.charAt(0)) { - case 'Z': - return getRegisterType(BOOLEAN, null); - case 'B': - return getRegisterType(BYTE, null); - case 'S': - return getRegisterType(SHORT, null); - case 'C': - return getRegisterType(CHAR, null); - case 'I': - return getRegisterType(INTEGER, null); - case 'F': - return getRegisterType(FLOAT, null); - case 'J': - return getRegisterType(LONG_LO, null); - case 'D': - return getRegisterType(DOUBLE_LO, null); - case 'L': - case 'U': - case '[': - return getRegisterType(REFERENCE, type); - default: - throw new RuntimeException("Invalid type: " + type); - } - } - @Nonnull public static RegisterType getWideRegisterTypeForType(@Nonnull String type, boolean firstRegister) { switch (type.charAt(0)) { @@ -260,18 +232,18 @@ public class RegisterType { return getRegisterType(INTEGER, null); } - public RegisterType merge(RegisterType other) { - if (other == null || other == this) { + public RegisterType merge(@Nonnull RegisterType other) { + if (other == this) { return this; } byte mergedCategory = mergeTable[this.category][other.category]; - String mergedType = null; + TypeProto mergedType = null; if (mergedCategory == REFERENCE) { - // TODO: uncomment - // TODO: make sure to handle unresolved types in getCommonSuperclass (using U prefix?) - //mergedType = ClassPath.getCommonSuperclass(this.type, other.type); + assert type != null; + assert other.type != null; + mergedType = this.type.getCommonSuperclass(other.type); } else if (mergedCategory == UNINIT_REF || mergedCategory == UNINIT_THIS) { if (this.category == UNKNOWN) { return other; @@ -291,7 +263,7 @@ public class RegisterType { return RegisterType.getRegisterType(mergedCategory, mergedType); } - public static RegisterType getRegisterType(byte category, String classType) { + public static RegisterType getRegisterType(byte category, @Nullable TypeProto typeProto) { switch (category) { case UNKNOWN: return UNKNOWN_TYPE; @@ -310,7 +282,7 @@ public class RegisterType { case SHORT: return SHORT_TYPE; case POS_SHORT: - return POS_BYTE_TYPE; + return POS_SHORT_TYPE; case CHAR: return CHAR_TYPE; case INTEGER: @@ -329,6 +301,6 @@ public class RegisterType { return CONFLICTED_TYPE; } - return new RegisterType(category, classType); + return new RegisterType(category, typeProto); } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/TypeProto.java similarity index 79% rename from dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayClassProto.java rename to dexlib2/src/main/java/org/jf/dexlib2/analysis/TypeProto.java index 668a729d..3e3bb9a5 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/TypeProto.java @@ -32,17 +32,13 @@ package org.jf.dexlib2.analysis; import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public class ArrayClassProto extends ClassProto { - public ArrayClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { - super(classPath, type); - } - - @Nonnull @Override - public ClassProto getCommonSuperclass(@Nonnull ClassProto other) { - if (other instanceof ArrayClassProto) { - // TODO: implement this - } - return super.getCommonSuperclass(other); - } +public interface TypeProto { + @Nonnull ClassPath getClassPath(); + @Nonnull String getType(); + boolean isInterface(); + boolean implementsInterface(@Nonnull String iface); + @Nullable String getSuperclass(); + @Nonnull TypeProto getCommonSuperclass(@Nonnull TypeProto other); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/UnknownClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/UnknownClassProto.java new file mode 100644 index 00000000..c6e91802 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/UnknownClassProto.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013, 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.analysis; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UnknownClassProto implements TypeProto { + @Nonnull protected final ClassPath classPath; + + public UnknownClassProto(@Nonnull ClassPath classPath) { + this.classPath = classPath; + } + + @Override public String toString() { return "Ujava/lang/Object;"; } + @Nonnull @Override public ClassPath getClassPath() { return classPath; } + @Nullable @Override public String getSuperclass() { return null; } + @Override public boolean isInterface() { return false; } + @Override public boolean implementsInterface(@Nonnull String iface) { return false; } + + @Nonnull @Override public TypeProto getCommonSuperclass(@Nonnull TypeProto other) { + if (other.getType().equals("Ljava/lang/Object;")) { + return other; + } + if (other instanceof ArrayProto) { + // if it's an array class, it's safe to assume this unknown class isn't related, and so + // java.lang.Object is the only possible superclass + return classPath.getClass("Ljava/lang/Object;"); + } + return this; + } + + @Nonnull @Override public String getType() { + // use the otherwise used U prefix for an unknown/unresolvable class + return "Ujava/lang/Object;"; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionClassDef.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionClassDef.java new file mode 100644 index 00000000..68b10597 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionClassDef.java @@ -0,0 +1,154 @@ +/* + * Copyright 2013, 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.analysis.reflection; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import org.jf.dexlib2.analysis.reflection.util.ReflectionUtils; +import org.jf.dexlib2.base.reference.BaseTypeReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.Method; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Wraps a ClassDef around a class loaded in the current VM + * + * Only supports the basic information exposed by ClassProto + */ +public class ReflectionClassDef extends BaseTypeReference implements ClassDef { + private final Class cls; + + public ReflectionClassDef(Class cls) { + this.cls = cls; + } + + @Override public int getAccessFlags() { + // the java modifiers appear to be the same as the dex access flags + return cls.getModifiers(); + } + + @Nullable @Override public String getSuperclass() { + if (Modifier.isInterface(cls.getModifiers())) { + return "Ljava/lang/Object;"; + } + Class superClass = cls.getSuperclass(); + if (superClass == null) { + return null; + } + return ReflectionUtils.javaToDexName(superClass.getName()); + } + + @Nonnull @Override public Set getInterfaces() { + return new AbstractSet() { + @Nonnull @Override public Iterator iterator() { + return Iterators.transform(Iterators.forArray(cls.getInterfaces()), new Function() { + @Nullable @Override public String apply(@Nullable Class input) { + if (input == null) { + return null; + } + return ReflectionUtils.javaToDexName(input.getName()); + } + }); + } + + @Override public int size() { + return cls.getInterfaces().length; + } + }; + } + + @Nullable @Override public String getSourceFile() { + return null; + } + + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nonnull @Override public Set getFields() { + return new AbstractSet() { + @Nonnull @Override public Iterator iterator() { + return Iterators.transform(Iterators.forArray(cls.getDeclaredFields()), + new Function() { + @Nullable @Override public Field apply(@Nullable java.lang.reflect.Field input) { + return new ReflectionField(input); + } + }); + } + + @Override public int size() { + return cls.getDeclaredFields().length; + } + }; + } + + @Nonnull @Override public Set getMethods() { + return new AbstractSet() { + @Nonnull @Override public Iterator iterator() { + Iterator constructorIterator = + Iterators.transform(Iterators.forArray(cls.getDeclaredConstructors()), + new Function() { + @Nullable @Override public Method apply(@Nullable Constructor input) { + return new ReflectionConstructor(input); + } + }); + + Iterator methodIterator = + Iterators.transform(Iterators.forArray(cls.getDeclaredMethods()), + new Function() { + @Nullable @Override public Method apply(@Nullable java.lang.reflect.Method input) { + return new ReflectionMethod(input); + } + }); + return Iterators.concat(constructorIterator, methodIterator); + } + + @Override public int size() { + return cls.getDeclaredMethods().length + cls.getDeclaredConstructors().length; + } + }; + } + + @Nonnull @Override public String getType() { + return ReflectionUtils.javaToDexName(cls.getName()); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionConstructor.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionConstructor.java new file mode 100644 index 00000000..84219599 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionConstructor.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013, 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.analysis.reflection; + +import com.google.common.collect.ImmutableSet; +import org.jf.dexlib2.analysis.reflection.util.ReflectionUtils; +import org.jf.dexlib2.base.BaseMethodParameter; +import org.jf.dexlib2.base.reference.BaseMethodReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.MethodParameter; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.util.AbstractList; +import java.util.List; +import java.util.Set; + +public class ReflectionConstructor extends BaseMethodReference implements Method { + private final Constructor constructor; + + public ReflectionConstructor(Constructor constructor) { + this.constructor = constructor; + } + + @Nonnull @Override public List getParameters() { + final Constructor method = this.constructor; + return new AbstractList() { + private final Class[] parameters = method.getParameterTypes(); + + @Override public MethodParameter get(final int index) { + return new BaseMethodParameter() { + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nullable @Override public String getName() { + return null; + } + + @Nonnull @Override public String getType() { + return ReflectionUtils.javaToDexName(parameters[index].getName()); + } + }; + } + + @Override public int size() { + return parameters.length; + } + }; + } + + @Override public int getAccessFlags() { + return constructor.getModifiers(); + } + + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nullable @Override public MethodImplementation getImplementation() { + return null; + } + + @Nonnull @Override public String getDefiningClass() { + return ReflectionUtils.javaToDexName(constructor.getDeclaringClass().getName()); + } + + @Nonnull @Override public String getName() { + return constructor.getName(); + } + + @Nonnull @Override public List getParameterTypes() { + return new AbstractList() { + private final List parameters = getParameters(); + + @Override public String get(int index) { + return parameters.get(index).getType(); + } + + @Override public int size() { + return parameters.size(); + } + }; + } + + @Nonnull @Override public String getReturnType() { + return "V"; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionField.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionField.java new file mode 100644 index 00000000..c3a0e8c9 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionField.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013, 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.analysis.reflection; + +import com.google.common.collect.ImmutableSet; +import org.jf.dexlib2.analysis.reflection.util.ReflectionUtils; +import org.jf.dexlib2.base.reference.BaseFieldReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.value.EncodedValue; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Set; + +public class ReflectionField extends BaseFieldReference implements Field { + private final java.lang.reflect.Field field; + + public ReflectionField(java.lang.reflect.Field field) { + this.field = field; + } + + @Override public int getAccessFlags() { + return field.getModifiers(); + } + + @Nullable @Override public EncodedValue getInitialValue() { + return null; + } + + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nonnull @Override public String getDefiningClass() { + return ReflectionUtils.javaToDexName(field.getDeclaringClass().getName()); + } + + @Nonnull @Override public String getName() { + return field.getName(); + } + + @Nonnull @Override public String getType() { + return ReflectionUtils.javaToDexName(field.getType().getName()); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionMethod.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionMethod.java new file mode 100644 index 00000000..b7fb4756 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/ReflectionMethod.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013, 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.analysis.reflection; + +import com.google.common.collect.ImmutableSet; +import org.jf.dexlib2.analysis.reflection.util.ReflectionUtils; +import org.jf.dexlib2.base.BaseMethodParameter; +import org.jf.dexlib2.base.reference.BaseMethodReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.MethodParameter; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.AbstractList; +import java.util.List; +import java.util.Set; + +public class ReflectionMethod extends BaseMethodReference implements Method { + private final java.lang.reflect.Method method; + + public ReflectionMethod(java.lang.reflect.Method method) { + this.method = method; + } + + @Nonnull @Override public List getParameters() { + final java.lang.reflect.Method method = this.method; + return new AbstractList() { + private final Class[] parameters = method.getParameterTypes(); + + @Override public MethodParameter get(final int index) { + return new BaseMethodParameter() { + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nullable @Override public String getName() { + return null; + } + + @Nonnull @Override public String getType() { + return ReflectionUtils.javaToDexName(parameters[index].getName()); + } + }; + } + + @Override public int size() { + return parameters.length; + } + }; + } + + @Override public int getAccessFlags() { + return method.getModifiers(); + } + + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nullable @Override public MethodImplementation getImplementation() { + return null; + } + + @Nonnull @Override public String getDefiningClass() { + return ReflectionUtils.javaToDexName(method.getDeclaringClass().getName()); + } + + @Nonnull @Override public String getName() { + return method.getName(); + } + + @Nonnull @Override public List getParameterTypes() { + return new AbstractList() { + private final List parameters = getParameters(); + + @Override public String get(int index) { + return parameters.get(index).getType(); + } + + @Override public int size() { + return parameters.size(); + } + }; + } + + @Nonnull @Override public String getReturnType() { + return ReflectionUtils.javaToDexName(method.getReturnType().getName()); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java new file mode 100644 index 00000000..4a4615a6 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013, 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.analysis.reflection.util; + +public class ReflectionUtils { + public static String javaToDexName(String javaName) { + javaName = javaName.replace('.', '/'); + if (javaName.length() > 1 && javaName.charAt(javaName.length()-1) != ';') { + javaName = 'L' + javaName + ';'; + } + return javaName; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/util/TypeProtoUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/util/TypeProtoUtils.java new file mode 100644 index 00000000..0313c7c3 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/util/TypeProtoUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013, 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.analysis.util; + +import org.jf.dexlib2.analysis.TypeProto; +import org.jf.dexlib2.analysis.UnresolvedClassException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class TypeProtoUtils { + /** + * Get the chain of superclasses of the given class. The first element will be the immediate superclass followed by + * it's superclass, etc. up to java.lang.Object. + * + * Returns an empty iterable if called on java.lang.Object or a primitive. + * + * If any class in the superclass chain can't be resolved, the iterable will return Ujava/lang/Object; to represent + * the unknown class. + * + * @return An iterable containing the superclasses of this class. + */ + @Nonnull + public static Iterable getSuperclassChain(@Nonnull final TypeProto typeProto) { + return new Iterable() { + + @Override public Iterator iterator() { + return new Iterator() { + @Nullable private TypeProto type = getSuperclassAsTypeProto(typeProto); + + @Override public boolean hasNext() { + return type != null; + } + + @Override public TypeProto next() { + TypeProto type = this.type; + if (type == null) { + throw new NoSuchElementException(); + } + + this.type = getSuperclassAsTypeProto(type); + return type; + } + + @Override public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Nullable + public static TypeProto getSuperclassAsTypeProto(@Nonnull TypeProto type) { + try { + String next = type.getSuperclass(); + if (next != null) { + return type.getClassPath().getClass(next); + } else { + return null; + } + } catch (UnresolvedClassException ex) { + return type.getClassPath().getUnknownClass(); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java index 097af5cc..2f77228f 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java @@ -37,5 +37,9 @@ public final class TypeUtils { return c == 'J' || c == 'D'; } + public static boolean isPrimitiveType(String type) { + return type.length() == 1; + } + private TypeUtils() {} } diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java new file mode 100644 index 00000000..f3b1094e --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java @@ -0,0 +1,314 @@ +/* + * Copyright 2013, 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.analysis; + +import com.google.common.collect.ImmutableSet; +import junit.framework.Assert; +import org.jf.dexlib2.immutable.ImmutableDexFile; +import org.junit.Test; + +import java.io.IOException; + +public class CommonSuperclassTest { + // object tree: + // object + // one + // onetwo + // onetwothree + // onethree + // five (undefined class) + // fivetwo + // fivetwothree + // fivethree + + private final ClassPath classPath; + + public CommonSuperclassTest() throws IOException { + classPath = new ClassPath(new ImmutableDexFile(ImmutableSet.of( + TestUtils.makeClassDef("Ljava/lang/Object;", null), + TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"), + TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"), + TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"), + TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"), + TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"), + TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"), + TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"), + TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"), + TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"), + TestUtils.makeInterfaceDef("Ljava/io/Serializable;"), + + // basic class and interface + TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"), + TestUtils.makeInterfaceDef("Liface/iface1;"), + + // a more complex interface tree + TestUtils.makeInterfaceDef("Liface/base1;"), + // implements undefined interface + TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"), + // this implements sub1, so that its interfaces can't be fully resolved either + TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"), + TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"), + TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"), + TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"), + TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"), + TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;", "Liface/base;"), + TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;", "Liface/sub4;"), + TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"), + TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;", "Liface/sub2;", + "Liface/sub3;", "Liface/sub4;") + ))); + } + + public void superclassTest(String commonSuperclass, + String type1, String type2) { + TypeProto commonSuperclassProto = classPath.getClass(commonSuperclass); + TypeProto type1Proto = classPath.getClass(type1); + TypeProto type2Proto = classPath.getClass(type2); + + Assert.assertSame(commonSuperclassProto, type1Proto.getCommonSuperclass(type2Proto)); + Assert.assertSame(commonSuperclassProto, type2Proto.getCommonSuperclass(type1Proto)); + } + + @Test + public void testGetCommonSuperclass() throws IOException { + String object = "Ljava/lang/Object;"; + String unknown = "Ujava/lang/Object;"; + String one = "Ltest/one;"; + String two = "Ltest/two;"; + String onetwo = "Ltest/onetwo;"; + String onetwothree = "Ltest/onetwothree;"; + String onethree = "Ltest/onethree;"; + String five = "Ltest/five;"; + String fivetwo = "Ltest/fivetwo;"; + String fivetwothree = "Ltest/fivetwothree;"; + String fivethree = "Ltest/fivethree;"; + + // same object + superclassTest(object, object, object); + superclassTest(unknown, unknown, unknown); + superclassTest(one, one, one); + superclassTest(onetwo, onetwo, onetwo); + superclassTest(onetwothree, onetwothree, onetwothree); + superclassTest(onethree, onethree, onethree); + superclassTest(five, five, five); + superclassTest(fivetwo, fivetwo, fivetwo); + superclassTest(fivetwothree, fivetwothree, fivetwothree); + superclassTest(fivethree, fivethree, fivethree); + + // same value, but different object + Assert.assertEquals( + onetwo, + classPath.getClass(onetwo).getCommonSuperclass(new ClassProto(classPath, onetwo)).getType()); + + // other object is superclass + superclassTest(object, object, one); + + // other object is superclass two levels up + superclassTest(object, object, onetwo); + + // unknown and non-object class + superclassTest(unknown, one, unknown); + + // unknown and object class + superclassTest(object, object, unknown); + + // siblings + superclassTest(one, onetwo, onethree); + + // nephew + superclassTest(one, onethree, onetwothree); + + // unrelated + superclassTest(object, one, two); + + // undefined superclass and object + superclassTest(object, fivetwo, object); + + // undefined class and unrelated type + superclassTest(unknown, one, five); + + // undefined superclass and unrelated type + superclassTest(unknown, one, fivetwo); + + // undefined ancestor and unrelated type + superclassTest(unknown, one, fivetwothree); + + // undefined class and direct subclass + superclassTest(five, five, fivetwo); + + // undefined class and descendent + superclassTest(five, five, fivetwothree); + + // undefined superclass and direct subclass + superclassTest(fivetwo, fivetwo, fivetwothree); + + // siblings with undefined superclass + superclassTest(five, fivetwo, fivethree); + + // undefined superclass and nephew + superclassTest(five, fivethree, fivetwothree); + } + + @Test + public void testGetCommonSuperclass_interfaces() { + String classiface1 = "Liface/classiface1;"; + String iface1 = "Liface/iface1;"; + String base1 = "Liface/base1;"; + String base2 = "Liface/base2;"; + String sub1 = "Liface/sub1;"; + String sub2 = "Liface/sub2;"; + String sub3 = "Liface/sub3;"; + String sub4 = "Liface/sub4;"; + String classsub1 = "Liface/classsub1;"; + String classsub2 = "Liface/classsub2;"; + String classsub3 = "Liface/classsub3;"; + String classsub4 = "Liface/classsub4;"; + String classsubsub4 = "Liface/classsubsub4;"; + String classsub1234 = "Liface/classsub1234;"; + String object = "Ljava/lang/Object;"; + String unknown = "Ujava/lang/Object;"; + + superclassTest(iface1, classiface1, iface1); + + superclassTest(base1, base1, base1); + superclassTest(base1, base1, sub1); + superclassTest(base1, base1, classsub1); + superclassTest(base1, base1, sub2); + superclassTest(base1, base1, classsub2); + superclassTest(base1, base1, sub3); + superclassTest(base1, base1, classsub3); + superclassTest(base1, base1, sub4); + superclassTest(base1, base1, classsub4); + superclassTest(base1, base1, classsubsub4); + superclassTest(base1, base1, classsub1234); + + superclassTest(object, sub3, iface1); + superclassTest(unknown, sub2, iface1); + superclassTest(unknown, sub1, iface1); + + superclassTest(base2, base2, sub1); + superclassTest(base2, base2, classsub1); + superclassTest(base2, base2, sub2); + superclassTest(base2, base2, classsub2); + superclassTest(base2, base2, classsub1234); + + superclassTest(unknown, iface1, classsub1234); + + superclassTest(sub1, sub1, classsub1); + + superclassTest(sub2, sub2, classsub2); + superclassTest(sub1, sub1, classsub2); + + superclassTest(sub3, sub3, classsub3); + + superclassTest(sub4, sub4, classsub4); + superclassTest(sub3, sub3, classsub4); + superclassTest(object, sub2, classsub4); + superclassTest(object, sub1, classsub4); + + superclassTest(sub1, sub2, sub1); + + superclassTest(sub1, sub1, classsub1234); + superclassTest(sub2, sub2, classsub1234); + superclassTest(sub3, sub3, classsub1234); + superclassTest(sub4, sub4, classsub1234); + + superclassTest(unknown, sub3, classsub1); + superclassTest(unknown, sub4, classsub1); + superclassTest(unknown, sub3, classsub2); + superclassTest(unknown, sub4, classsub2); + + superclassTest(unknown, sub4, base2); + superclassTest(unknown, classsub4, base2); + } + + @Test + public void testGetCommonSuperclass_arrays() throws IOException { + String object = "Ljava/lang/Object;"; + String one = "Ltest/one;"; + String unknown = "Ujava/lang/Object;"; + + String cloneable = "Ljava/lang/Cloneable;"; + String serializable = "Ljava/io/Serializable;"; + + String object1 = "[Ljava/lang/Object;"; + String one1 = "[Ltest/one;"; + String one2 = "[[Ltest/one;"; + String two1 = "[Ltest/two;"; + String onetwo1 = "[Ltest/onetwo;"; + String onetwo2 = "[[Ltest/onetwo;"; + String onethree1 = "[Ltest/onethree;"; + String onethree2 = "[[Ltest/onethree;"; + String five = "Ltest/five;"; + String five1 = "[Ltest/five;"; + String unknown1 = "[Ujava/lang/Object;"; + + String int1 = "[I"; + String int2 = "[[I"; + String float1 = "[F"; + + superclassTest(one1, one1, one1); + superclassTest(object1, object1, one1); + superclassTest(one1, onetwo1, onethree1); + superclassTest(one1, one1, onethree1); + superclassTest(object1, one1, two1); + + superclassTest(one2, one2, one2); + superclassTest(one2, one2, onetwo2); + superclassTest(one2, onetwo2, onethree2); + superclassTest(object1, one1, one2); + superclassTest(object1, two1, one2); + + superclassTest(unknown1, five1, one1); + superclassTest(object1, five1, one2); + + superclassTest(unknown1, one1, unknown1); + + superclassTest(object, one1, one); + superclassTest(object, object1, one); + superclassTest(object, onetwo1, one); + superclassTest(object, five1, one); + superclassTest(object, one2, one); + + superclassTest(object, one1, unknown); + superclassTest(object, unknown1, unknown); + + superclassTest(cloneable, one1, cloneable); + superclassTest(serializable, one1, serializable); + + superclassTest(object, one1, five); + + superclassTest(int1, int1, int1); + superclassTest(object, int1, float1); + superclassTest(object, int1, int2); + } +} diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/TestUtils.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/TestUtils.java new file mode 100644 index 00000000..9d7fd3e2 --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/TestUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013, 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.analysis; + +import com.google.common.collect.ImmutableSet; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.immutable.ImmutableClassDef; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TestUtils { + public static ClassDef makeClassDef(@Nonnull String classType, @Nullable String superType, String... interfaces) { + return new ImmutableClassDef(classType, 0, superType, ImmutableSet.copyOf(interfaces), null, null, null, null); + } + + public static ClassDef makeInterfaceDef(@Nonnull String classType, String... interfaces) { + return new ImmutableClassDef(classType, AccessFlags.INTERFACE.getValue(), "Ljava/lang/Object;", + ImmutableSet.copyOf(interfaces), null, null, null, null); + } +} diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java new file mode 100644 index 00000000..59b0d276 --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013, 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.analysis.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import junit.framework.Assert; +import org.jf.dexlib2.analysis.ClassPath; +import org.jf.dexlib2.analysis.TestUtils; +import org.jf.dexlib2.analysis.TypeProto; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.immutable.ImmutableDexFile; +import org.junit.Test; + +import java.io.IOException; + +public class SuperclassChainTest { + + + @Test + public void testGetSuperclassChain() throws IOException { + ClassDef objectClassDef = TestUtils.makeClassDef("Ljava/lang/Object;", null); + ClassDef oneClassDef = TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"); + ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;"); + ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;"); + + ImmutableSet classes = ImmutableSet.of( + objectClassDef, oneClassDef, twoClassDef, threeClassDef); + + ClassPath classPath = new ClassPath(new ImmutableDexFile(classes)); + + TypeProto objectClassProto = classPath.getClass("Ljava/lang/Object;"); + TypeProto oneClassProto = classPath.getClass("Ltest/one;"); + TypeProto twoClassProto = classPath.getClass("Ltest/two;"); + TypeProto threeClassProto = classPath.getClass("Ltest/three;"); + + Assert.assertEquals( + ImmutableList.of(), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(objectClassProto))); + + Assert.assertEquals( + ImmutableList.of(objectClassProto), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(oneClassProto))); + + Assert.assertEquals( + ImmutableList.of(oneClassProto, objectClassProto), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(twoClassProto))); + + Assert.assertEquals( + ImmutableList.of(twoClassProto, oneClassProto, objectClassProto), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(threeClassProto))); + } + + @Test + public void testGetSuperclassChain_Unresolved() throws IOException { + // Ltest/one; isn't defined + + ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;"); + ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;"); + ImmutableSet classes = ImmutableSet.of(twoClassDef, threeClassDef); + ClassPath classPath = new ClassPath(new ImmutableDexFile(classes)); + + TypeProto unknownClassProto = classPath.getUnknownClass(); + TypeProto oneClassProto = classPath.getClass("Ltest/one;"); + TypeProto twoClassProto = classPath.getClass("Ltest/two;"); + TypeProto threeClassProto = classPath.getClass("Ltest/three;"); + + Assert.assertEquals( + ImmutableList.of(oneClassProto, unknownClassProto), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(twoClassProto))); + + Assert.assertEquals( + ImmutableList.of(twoClassProto, oneClassProto, unknownClassProto), + ImmutableList.copyOf(TypeProtoUtils.getSuperclassChain(threeClassProto))); + } +}