Simplify deodexing

This makes it possible to deodex an odex file using only the dependencies
listed in the odex file itself. Adding extra dependencies via the -c
option should no longer be needed.
This commit is contained in:
Ben Gruver 2012-07-05 21:55:54 -07:00
parent 5b4073a85c
commit 343df2f456
5 changed files with 116 additions and 125 deletions

View File

@ -290,6 +290,15 @@ public class MethodDefinition {
return false; return false;
} }
private boolean needsAnalyzed() {
for (Instruction instruction: encodedMethod.codeItem.getInstructions()) {
if (instruction.opcode.odexOnly()) {
return true;
}
}
return false;
}
private List<MethodItem> getMethodItems() { private List<MethodItem> getMethodItems() {
ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
@ -297,7 +306,8 @@ public class MethodDefinition {
return methodItems; return methodItems;
} }
if (baksmali.registerInfo != 0 || baksmali.deodex || baksmali.verify) { if ((baksmali.registerInfo != 0) || baksmali.verify ||
(baksmali.deodex && needsAnalyzed())) {
addAnalyzedInstructionMethodItems(methodItems); addAnalyzedInstructionMethodItems(methodItems);
} else { } else {
addInstructionMethodItems(methodItems); addInstructionMethodItems(methodItems);

View File

@ -75,16 +75,6 @@ public class baksmali {
baksmali.bootClassPath = bootClassPath; baksmali.bootClassPath = bootClassPath;
baksmali.verify = verify; baksmali.verify = verify;
ClassPath.ClassPathErrorHandler classPathErrorHandler = null;
if (ignoreErrors) {
classPathErrorHandler = new ClassPath.ClassPathErrorHandler() {
public void ClassPathError(String className, Exception ex) {
System.err.println(String.format("Skipping %s", className));
ex.printStackTrace(System.err);
}
};
}
if (registerInfo != 0 || deodex || verify) { if (registerInfo != 0 || deodex || verify) {
try { try {
String[] extraBootClassPathArray = null; String[] extraBootClassPathArray = null;
@ -101,15 +91,14 @@ public class baksmali {
if (extraBootClassPathArray == null && isExtJar(dexFilePath)) { if (extraBootClassPathArray == null && isExtJar(dexFilePath)) {
extraBootClassPathArray = new String[] {"framework.jar"}; extraBootClassPathArray = new String[] {"framework.jar"};
} }
ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile, ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile);
classPathErrorHandler);
} else { } else {
String[] bootClassPathArray = null; String[] bootClassPathArray = null;
if (bootClassPath != null) { if (bootClassPath != null) {
bootClassPathArray = bootClassPath.split(":"); bootClassPathArray = bootClassPath.split(":");
} }
ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray,
dexFilePath, dexFile, classPathErrorHandler); dexFilePath, dexFile);
} }
if (inlineTable != null) { if (inlineTable != null) {
@ -156,15 +145,6 @@ public class baksmali {
* package name are separated by '/' * package name are separated by '/'
*/ */
if (registerInfo != 0 || deodex || verify) {
//If we are analyzing the bytecode, make sure that this class is loaded into the ClassPath. If it isn't
//then there was some error while loading it, and we should skip it
ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false);
if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) {
continue;
}
}
String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); String classDescriptor = classDefItem.getClassType().getTypeDescriptor();
//validate that the descriptor is formatted like we expect //validate that the descriptor is formatted like we expect
@ -206,6 +186,7 @@ public class baksmali {
} catch (Exception ex) { } catch (Exception ex) {
System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class");
ex.printStackTrace(); ex.printStackTrace();
smaliFile.delete();
} }
finally finally
{ {

View File

@ -33,6 +33,8 @@ import org.jf.dexlib.Util.AccessFlags;
import org.jf.dexlib.Util.ExceptionWithContext; import org.jf.dexlib.Util.ExceptionWithContext;
import org.jf.dexlib.Util.SparseArray; import org.jf.dexlib.Util.SparseArray;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -45,19 +47,13 @@ public class ClassPath {
private static ClassPath theClassPath = null; private static ClassPath theClassPath = null;
private final HashMap<String, ClassDef> classDefs; private final HashMap<String, ClassDef> classDefs;
protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object; protected ClassDef javaLangObjectClassDef; //cached ClassDef for Ljava/lang/Object;
//This is only used while initialing the class path. It is set to null after initialization has finished.
private LinkedHashMap<String, TempClassInfo> tempClasses;
// Contains the classes that we haven't loaded yet
private HashMap<String, UnresolvedClassInfo> unloadedClasses;
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$"); private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
public static interface ClassPathErrorHandler {
void ClassPathError(String className, Exception ex);
}
/** /**
* Initialize the class path using the dependencies from an odex file * Initialize the class path using the dependencies from an odex file
* @param classPathDirs The directories to search for boot class path files * @param classPathDirs The directories to search for boot class path files
@ -65,12 +61,9 @@ public class ClassPath {
* from the odex file * from the odex file
* @param dexFilePath The path of the dex file (used for error reporting purposes only) * @param dexFilePath The path of the dex file (used for error reporting purposes only)
* @param dexFile The DexFile to load - it must represents an odex file * @param dexFile The DexFile to load - it must represents an odex file
* @param errorHandler a ClassPathErrorHandler object to receive and handle any errors that occur while loading
* classes
*/ */
public static void InitializeClassPathFromOdex(String[] classPathDirs, String[] extraBootClassPathEntries, public static void InitializeClassPathFromOdex(String[] classPathDirs, String[] extraBootClassPathEntries,
String dexFilePath, DexFile dexFile, String dexFilePath, DexFile dexFile) {
ClassPathErrorHandler errorHandler) {
if (!dexFile.isOdex()) { if (!dexFile.isOdex()) {
throw new ExceptionWithContext("Cannot use InitialiazeClassPathFromOdex with a non-odex DexFile"); throw new ExceptionWithContext("Cannot use InitialiazeClassPathFromOdex with a non-odex DexFile");
} }
@ -107,8 +100,7 @@ public class ClassPath {
} }
theClassPath = new ClassPath(); theClassPath = new ClassPath();
theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile, theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile);
errorHandler);
} }
/** /**
@ -121,15 +113,13 @@ public class ClassPath {
* classes * classes
*/ */
public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath, public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath,
String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile, String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile) {
ClassPathErrorHandler errorHandler) {
if (theClassPath != null) { if (theClassPath != null) {
throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
} }
theClassPath = new ClassPath(); theClassPath = new ClassPath();
theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile, theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile);
errorHandler);
} }
private ClassPath() { private ClassPath() {
@ -137,8 +127,8 @@ public class ClassPath {
} }
private void initClassPath(String[] classPathDirs, String[] bootClassPath, String[] extraBootClassPathEntries, private void initClassPath(String[] classPathDirs, String[] bootClassPath, String[] extraBootClassPathEntries,
String dexFilePath, DexFile dexFile, ClassPathErrorHandler errorHandler) { String dexFilePath, DexFile dexFile) {
tempClasses = new LinkedHashMap<String, TempClassInfo>(); unloadedClasses = new LinkedHashMap<String, UnresolvedClassInfo>();
if (bootClassPath != null) { if (bootClassPath != null) {
for (String bootClassPathEntry: bootClassPath) { for (String bootClassPathEntry: bootClassPath) {
@ -156,32 +146,12 @@ public class ClassPath {
loadDexFile(dexFilePath, dexFile); loadDexFile(dexFilePath, dexFile);
} }
javaLangObjectClassDef = getClassDef("Ljava/lang/Object;", false);
for (String classType: tempClasses.keySet()) {
ClassDef classDef = null;
try {
classDef = ClassPath.loadClassDef(classType);
assert classDef != null;
} catch (Exception ex) {
if (errorHandler != null) {
errorHandler.ClassPathError(classType, ex);
} else {
throw ExceptionWithContext.withContext(ex,
String.format("Error while loading ClassPath class %s", classType));
}
}
if (classType.equals("Ljava/lang/Object;")) {
this.javaLangObjectClassDef = classDef;
}
}
for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) { for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) {
ClassDef classDef = new PrimitiveClassDef(primitiveType); ClassDef classDef = new PrimitiveClassDef(primitiveType);
classDefs.put(primitiveType, classDef); classDefs.put(primitiveType, classDef);
} }
tempClasses = null;
} }
private void loadBootClassPath(String[] classPathDirs, String bootClassPathEntry) { private void loadBootClassPath(String[] classPathDirs, String bootClassPathEntry) {
@ -240,10 +210,10 @@ public class ClassPath {
private void loadDexFile(String dexFilePath, DexFile dexFile) { private void loadDexFile(String dexFilePath, DexFile dexFile) {
for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) { for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
try { try {
TempClassInfo tempClassInfo = new TempClassInfo(dexFilePath, classDefItem); UnresolvedClassInfo unresolvedClassInfo = new UnresolvedClassInfo(dexFilePath, classDefItem);
if (!tempClasses.containsKey(tempClassInfo.classType)) { if (!unloadedClasses.containsKey(unresolvedClassInfo.classType)) {
tempClasses.put(tempClassInfo.classType, tempClassInfo); unloadedClasses.put(unresolvedClassInfo.classType, unresolvedClassInfo);
} }
} catch (Exception ex) { } catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s", throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s",
@ -252,42 +222,33 @@ public class ClassPath {
} }
} }
private static class ClassNotFoundException extends ExceptionWithContext {
public ClassNotFoundException(String message) {
super(message);
}
}
public static ClassDef getClassDef(String classType) {
return getClassDef(classType, true);
}
/** /**
* This method checks if the given class has been loaded yet. If it has, it returns the loaded ClassDef. If not, * This method loads the given class (and any dependent classes, as needed), removing them from unloadedClasses
* then it looks up the TempClassItem for the given class and (possibly recursively) loads the ClassDef.
* @param classType the class to load * @param classType the class to load
* @return the existing or newly loaded ClassDef object for the given class, or null if the class cannot be found * @return the newly loaded ClassDef object for the given class, or null if the class cannot be found
*/ */
@Nullable
private static ClassDef loadClassDef(String classType) { private static ClassDef loadClassDef(String classType) {
ClassDef classDef = getClassDef(classType, false); ClassDef classDef = null;
if (classDef == null) { UnresolvedClassInfo classInfo = theClassPath.unloadedClasses.get(classType);
TempClassInfo classInfo = theClassPath.tempClasses.get(classType); if (classInfo == null) {
if (classInfo == null) { return null;
return null;
}
try {
classDef = new ClassDef(classInfo);
theClassPath.classDefs.put(classDef.classType, classDef);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s",
classInfo.classType, classInfo.dexFilePath));
}
} }
try {
classDef = new ClassDef(classInfo);
theClassPath.classDefs.put(classDef.classType, classDef);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s",
classInfo.classType, classInfo.dexFilePath));
}
theClassPath.unloadedClasses.remove(classType);
return classDef; return classDef;
} }
@Nonnull
public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) { public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) {
ClassDef classDef = theClassPath.classDefs.get(classType); ClassDef classDef = theClassPath.classDefs.get(classType);
if (classDef == null) { if (classDef == null) {
@ -295,17 +256,31 @@ public class ClassPath {
if (classType.charAt(0) == '[') { if (classType.charAt(0) == '[') {
return theClassPath.createArrayClassDef(classType); return theClassPath.createArrayClassDef(classType);
} else { } else {
if (createUnresolvedClassDef) { try {
//TODO: we should output a warning classDef = loadClassDef(classType);
return theClassPath.createUnresolvedClassDef(classType); if (classDef == null) {
} else { throw new ExceptionWithContext(
return null; String.format("Could not find definition for class %s", classType));
}
} catch (Exception ex) {
RuntimeException exWithContext = ExceptionWithContext.withContext(ex,
String.format("Error while loading ClassPath class %s", classType));
if (createUnresolvedClassDef) {
//TODO: add warning message
return theClassPath.createUnresolvedClassDef(classType);
} else {
throw exWithContext;
}
} }
} }
} }
return classDef; return classDef;
} }
public static ClassDef getClassDef(String classType) {
return getClassDef(classType, true);
}
public static ClassDef getClassDef(TypeIdItem classType) { public static ClassDef getClassDef(TypeIdItem classType) {
return getClassDef(classType.getTypeDescriptor()); return getClassDef(classType.getTypeDescriptor());
} }
@ -322,6 +297,14 @@ public class ClassPath {
return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType); return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType);
} }
private static ClassDef unresolvedObjectClassDef = null;
public static ClassDef getUnresolvedObjectClassDef() {
if (unresolvedObjectClassDef == null) {
unresolvedObjectClassDef = new UnresolvedClassDef("Ljava/lang/Object;");
}
return unresolvedObjectClassDef;
}
private ClassDef createUnresolvedClassDef(String classType) { private ClassDef createUnresolvedClassDef(String classType) {
assert classType.charAt(0) == 'L'; assert classType.charAt(0) == 'L';
@ -449,7 +432,7 @@ public class ClassPath {
try { try {
elementClass = ClassPath.getClassDef(arrayClassType.substring(i)); elementClass = ClassPath.getClassDef(arrayClassType.substring(i));
} catch (ClassNotFoundException ex) { } catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType); throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType);
} }
arrayDimensions = i; arrayDimensions = i;
@ -662,7 +645,7 @@ public class ClassPath {
} else /*if (classFlavor == UnresolvedClassDef)*/ { } else /*if (classFlavor == UnresolvedClassDef)*/ {
assert classType.charAt(0) == 'L'; assert classType.charAt(0) == 'L';
this.classType = classType; this.classType = classType;
this.superclass = ClassPath.theClassPath.javaLangObjectClassDef; this.superclass = ClassPath.getClassDef("Ljava/lang/Object;");
implementedInterfaces = new TreeSet<ClassDef>(); implementedInterfaces = new TreeSet<ClassDef>();
isInterface = false; isInterface = false;
@ -677,7 +660,7 @@ public class ClassPath {
} }
} }
protected ClassDef(TempClassInfo classInfo) { protected ClassDef(UnresolvedClassInfo classInfo) {
classType = classInfo.classType; classType = classInfo.classType;
isInterface = classInfo.isInterface; isInterface = classInfo.isInterface;
@ -812,7 +795,7 @@ public class ClassPath {
fields[position2] = tempField; fields[position2] = tempField;
} }
private ClassDef loadSuperclass(TempClassInfo classInfo) { private ClassDef loadSuperclass(UnresolvedClassInfo classInfo) {
if (classInfo.classType.equals("Ljava/lang/Object;")) { if (classInfo.classType.equals("Ljava/lang/Object;")) {
if (classInfo.superclassType != null) { if (classInfo.superclassType != null) {
throw new ExceptionWithContext("Invalid superclass " + throw new ExceptionWithContext("Invalid superclass " +
@ -826,9 +809,12 @@ public class ClassPath {
throw new ExceptionWithContext(classInfo.classType + " has no superclass"); throw new ExceptionWithContext(classInfo.classType + " has no superclass");
} }
ClassDef superclass = ClassPath.loadClassDef(superclassType); ClassDef superclass;
if (superclass == null) { try {
throw new ClassNotFoundException(String.format("Could not find superclass %s", superclassType)); superclass = ClassPath.getClassDef(superclassType);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
String.format("Could not find superclass %s", superclassType));
} }
if (!isInterface && superclass.isInterface) { if (!isInterface && superclass.isInterface) {
@ -845,7 +831,7 @@ public class ClassPath {
} }
} }
private TreeSet<ClassDef> loadAllImplementedInterfaces(TempClassInfo classInfo) { private TreeSet<ClassDef> loadAllImplementedInterfaces(UnresolvedClassInfo classInfo) {
assert classType != null; assert classType != null;
assert classType.equals("Ljava/lang/Object;") || superclass != null; assert classType.equals("Ljava/lang/Object;") || superclass != null;
assert classInfo != null; assert classInfo != null;
@ -861,9 +847,12 @@ public class ClassPath {
if (classInfo.interfaces != null) { if (classInfo.interfaces != null) {
for (String interfaceType: classInfo.interfaces) { for (String interfaceType: classInfo.interfaces) {
ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); ClassDef interfaceDef;
if (interfaceDef == null) { try {
throw new ClassNotFoundException(String.format("Could not find interface %s", interfaceType)); interfaceDef = ClassPath.getClassDef(interfaceType);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
String.format("Could not find interface %s", interfaceType));
} }
assert interfaceDef.isInterface(); assert interfaceDef.isInterface();
implementedInterfaceSet.add(interfaceDef); implementedInterfaceSet.add(interfaceDef);
@ -880,7 +869,7 @@ public class ClassPath {
return implementedInterfaceSet; return implementedInterfaceSet;
} }
private LinkedHashMap<String, ClassDef> loadInterfaceTable(TempClassInfo classInfo) { private LinkedHashMap<String, ClassDef> loadInterfaceTable(UnresolvedClassInfo classInfo) {
if (classInfo.interfaces == null) { if (classInfo.interfaces == null) {
return null; return null;
} }
@ -889,9 +878,12 @@ public class ClassPath {
for (String interfaceType: classInfo.interfaces) { for (String interfaceType: classInfo.interfaces) {
if (!interfaceTable.containsKey(interfaceType)) { if (!interfaceTable.containsKey(interfaceType)) {
ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); ClassDef interfaceDef;
if (interfaceDef == null) { try {
throw new ClassNotFoundException(String.format("Could not find interface %s", interfaceType)); interfaceDef = ClassPath.getClassDef(interfaceType);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
String.format("Could not find interface %s", interfaceType));
} }
interfaceTable.put(interfaceType, interfaceDef); interfaceTable.put(interfaceType, interfaceDef);
@ -908,7 +900,7 @@ public class ClassPath {
return interfaceTable; return interfaceTable;
} }
private String[] loadVtable(TempClassInfo classInfo) { private String[] loadVtable(UnresolvedClassInfo classInfo) {
//TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry //TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry
List<String> virtualMethodList = new LinkedList<String>(); List<String> virtualMethodList = new LinkedList<String>();
//use a temp hash table, so that we can construct the final lookup with an appropriate //use a temp hash table, so that we can construct the final lookup with an appropriate
@ -981,7 +973,7 @@ public class ClassPath {
} }
} }
private SparseArray<FieldDef> loadFields(TempClassInfo classInfo) { private SparseArray<FieldDef> loadFields(UnresolvedClassInfo classInfo) {
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing). //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets() //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
@ -1175,10 +1167,10 @@ public class ClassPath {
} }
/** /**
* In some cases, classes can reference another class (i.e. a superclass or an interface) that is in a *later* * This aggregates the basic information about a class in an easy-to-use format, without requiring references
* boot class path entry. So we load all classes from all boot class path entries before starting to process them * to any other class.
*/ */
private static class TempClassInfo { private static class UnresolvedClassInfo {
public final String dexFilePath; public final String dexFilePath;
public final String classType; public final String classType;
public final boolean isInterface; public final boolean isInterface;
@ -1189,7 +1181,7 @@ public class ClassPath {
public final String[] virtualMethods; public final String[] virtualMethods;
public final String[][] instanceFields; public final String[][] instanceFields;
public TempClassInfo(String dexFilePath, ClassDefItem classDefItem) { public UnresolvedClassInfo(String dexFilePath, ClassDefItem classDefItem) {
this.dexFilePath = dexFilePath; this.dexFilePath = dexFilePath;
classType = classDefItem.getClassType().getTypeDescriptor(); classType = classDefItem.getClassType().getTypeDescriptor();

View File

@ -91,7 +91,11 @@ public class DeodexUtil {
String methodParams = m.group(2); String methodParams = m.group(2);
String methodRet = m.group(3); String methodRet = m.group(3);
if (classDef.isInterface()) { if (classDef instanceof ClassPath.UnresolvedClassDef) {
//if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual
//method being looked up was a method on java.lang.Object.
classDef = ClassPath.getClassDef("Ljava/lang/Object;");
} else if (classDef.isInterface()) {
classDef = classDef.getSuperclass(); classDef = classDef.getSuperclass();
assert classDef != null; assert classDef != null;
} }

View File

@ -272,9 +272,13 @@ public class RegisterType {
ClassDef mergedType = null; ClassDef mergedType = null;
if (mergedCategory == Category.Reference) { if (mergedCategory == Category.Reference) {
mergedType = ClassPath.getCommonSuperclass(this.type, type.type); if (this.type instanceof ClassPath.UnresolvedClassDef ||
} type.type instanceof ClassPath.UnresolvedClassDef) {
if (mergedCategory == Category.UninitRef || mergedCategory == Category.UninitThis) { mergedType = ClassPath.getUnresolvedObjectClassDef();
} else {
mergedType = ClassPath.getCommonSuperclass(this.type, type.type);
}
} else if (mergedCategory == Category.UninitRef || mergedCategory == Category.UninitThis) {
if (this.category == Category.Unknown) { if (this.category == Category.Unknown) {
return type; return type;
} }