All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmlvm.util.analytics.JDKAnalyzer Maven / Gradle / Ivy

There is a newer version: 0.96-beta4
Show newest version
/* Copyright (c) 2002-2011 by XMLVM.org
 *
 * Project Info:  http://www.xmlvm.org
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 */

package org.xmlvm.util.analytics;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.xmlvm.Log;
import org.xmlvm.util.analytics.data.Dependencies;
import org.xmlvm.util.analytics.data.TypeHierarchy;
import org.xmlvm.util.analytics.data.Util;
import org.xmlvm.util.universalfile.UniversalFile;
import org.xmlvm.util.universalfile.UniversalFileCreator;
import org.xmlvm.util.universalfile.UniversalFileFilter;

import com.android.dx.cf.code.ConcreteMethod;
import com.android.dx.cf.code.Ropper;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.Method;
import com.android.dx.cf.iface.MethodList;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.dex.code.CatchHandlerList;
import com.android.dx.dex.code.CatchTable;
import com.android.dx.dex.code.CatchTable.Entry;
import com.android.dx.dex.code.CstInsn;
import com.android.dx.dex.code.DalvCode;
import com.android.dx.dex.code.DalvInsn;
import com.android.dx.dex.code.DalvInsnList;
import com.android.dx.dex.code.Dop;
import com.android.dx.dex.code.Dops;
import com.android.dx.dex.code.HighRegisterPrefix;
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.code.RopTranslator;
import com.android.dx.dex.code.SimpleInsn;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.code.DexTranslationAdvice;
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.TranslationAdvice;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.rop.cst.CstMemberRef;
import com.android.dx.rop.cst.CstMethodRef;
import com.android.dx.rop.type.Prototype;
import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.TypeList;

/**
 * The JDKAnalyzer is a swiss army knife for analyzing dependencies between
 * classes in a library. Given a black- and white-list of classes and packages, 
 * it analyzes the border betweteen these two groups of classes. All
 * dependencies between classes are mapped and can be written as a graph file,
 * for visual analysis.
 */
public class JDKAnalyzer
{
	public static final String TAG= JDKAnalyzer.class.getSimpleName();
	public static Map badClassCount= new HashMap();
	public static final String RESULTS_FILENAME= "results.bin";
	public static final String RESULTS2_FILENAME= "results2.bin";
	public static final String GRAPH_FILENAME= "fulldeps.gdf";
	final static String[] GOOD_PACKAGES= { "java.lang", "java.util", "java.math", "java.net", "java.io", "org.apache.harmony.luni.util" };

	/**
	 * For manual classification override: Classes that will always be
	 * classified as "good", regardless of their package.
	 */
	public final static Set GOOD_CLASSES= new HashSet();

	/**
	 * For manual classification override: Classes that will always be
	 * classified as "bad", regardless of their package.
	 */
	public final static Set BAD_CLASSES= new HashSet();

	public static void main(String[] args)
	{
		// The JDK JAR archive should be given as the argument.
		if (args.length != 1)
		{
			Log.error(TAG, "Invalid usage.");
			System.exit(-1);
		}
		fillGoodBadOverrides();
		String libraryPath= args[0];

		// Get a complete dependency map.
		Dependencies dependencies= loadDepencencies(libraryPath);

		writeDepsToGraphFile(dependencies);

		// Note(Sascha): The analysis showed, that in the JDK there are not a
		// lot of red/green mixed hierarchies. So for now, we skip this part.
		// It might become interesting for other libraries or for when the rules
		// change.

		// Compute the type hierarchy of the given set of classes.
		HierarchyAnalyzer hierarchyAnalyzer= new HierarchyAnalyzer(libraryPath);
		TypeHierarchy hierarchy= hierarchyAnalyzer.analyze();

		// First we determine the list of mock methods we need to insert into
		// green classes to prevent bubbling up of calls into red methods.
		Set mockMethods= determineGreenMockMethods(dependencies, hierarchy);

		Pair, Set> orangeLists= constructOrangeList(dependencies);
		Set redList= constructRedList(dependencies, orangeLists.first);
		Set greenList= constructGreenList(dependencies);
		writeSetToFile(orangeLists.first, "orange_classes.txt");
		writeSetToFile(orangeLists.second, "orange_details.txt");
		writeSetToFile(redList, "red.txt");
		writeSetToFile(greenList, "green.txt");

		// Find all good classes with bad references in them.
		// Log.debug(TAG, "Getting good classes with bad deps...");
		// Set goodClassesWithBadDeps =
		// getGoodClassesWithBadReferences(dependencies);
		// Log.debug(TAG, "Found: " + goodClassesWithBadDeps.size());

		// Bad references that are referenced directly from good classes.
		// printDirectBadRefs();
	}

	private static void fillGoodBadOverrides()
	{
		// Problematic:
		// java.security.BasicPermission. Used by many different
		// classes and doing an import on all of java.security.
		// sun.misc.Unsafe: Very low-level functionality.

		GOOD_CLASSES.add("java.security.Permission");
		// Interfaces
		GOOD_CLASSES.add("java.security.Guard");

		// Super-class of a few good classes and not much functionality as it is
		// primarily abstract.
		GOOD_CLASSES.add("java.security.PermissionCollection");

		GOOD_CLASSES.add("java.security.BasicPermission");
		GOOD_CLASSES.add("java.security.PrivilegedAction");
		GOOD_CLASSES.add("java.security.PrivilegedActionException");
		GOOD_CLASSES.add("java.security.PrivilegedExceptionAction");

		// Interfaces from which good classes are created.
		// TODO(Sascha): Maybe do this automatically.
		GOOD_CLASSES.add("sun.reflect.LangReflectAccess");
		GOOD_CLASSES.add("sun.misc.JavaNetAccess");
		GOOD_CLASSES.add("sun.misc.JavaIOAccess");
		GOOD_CLASSES.add("org.xml.sax.EntityResolver");
		GOOD_CLASSES.add("sun.net.spi.nameservice.NameService");
		GOOD_CLASSES.add("org.xml.sax.ErrorHandler");
		GOOD_CLASSES.add("sun.misc.JavaIODeleteOnExitAccess");
		GOOD_CLASSES.add("sun.misc.SignalHandler");
		GOOD_CLASSES.add("sun.misc.JavaLangAccess");
		GOOD_CLASSES.add("sun.misc.JavaUtilJarAccess");
		GOOD_CLASSES.add("sun.misc.JavaIOFileDescriptorAccess");
		GOOD_CLASSES.add("sun.util.LocaleServiceProviderPool$LocalizedObjectGetter");
		GOOD_CLASSES.add("java.nio.channels.Channel");

		GOOD_CLASSES.add("org.apache.harmony.luni.internal.nls.Messages");
		GOOD_CLASSES.add("org.apache.harmony.math.internal.nls.Messages");
		GOOD_CLASSES.add("org.apache.harmony.regex.internal.nls.Messages");
		GOOD_CLASSES.add("org.apache.harmony.archive.internal.nls.Messages");

		BAD_CLASSES.add("java.util.jar.JarVerifier");
		BAD_CLASSES.add("java.lang.management.ManagementFactory");
		BAD_CLASSES.add("java.util.JapaneseImperialCalendar");
		BAD_CLASSES.add("java.lang.ClassLoader");
		BAD_CLASSES.add("java.net.URLClassLoader");
		BAD_CLASSES.add("java.net.URLClassLoader$SubURLClassLoader");
		BAD_CLASSES.add("java.net.FactoryURLClassLoader");

		// This one is a subclass of java.lang.ClassLoader, so it must go as
		// well.
		BAD_CLASSES.add("java.util.ResourceBundle$RBClassLoader");

		// Sub-class of bad class sun.misc.LRUCache.
		BAD_CLASSES.add("java.util.Scanner$1");

		// Sub-class of bad class org.apache.harmony.luni.util.ThreadLocalCache
		BAD_CLASSES.add("java.io.ObjectStreamClass$OSCThreadLocalCache");
		BAD_CLASSES.add("java.io.ObjectStreamClass$OSCThreadLocalCache$1");

		// DEX cannot parse this.
		BAD_CLASSES.add("org.apache.xerces.impl.xpath.regex.ParserForXMLSchema");
	}

	/**
	 * Constructs a set of classes that can be completely removed.
	 */
	private static Set constructRedList(Dependencies dependencies, Set orangeList)
	{
		Set result= new HashSet();
		for (String clazz : dependencies.keySet())
		{
			// Red classes are all bad classes that are not orange.
			if (!isGoodClass(clazz) && !orangeList.contains(clazz))
			{
				result.add(clazz);
			}

		}
		return result;
	}

	private static Set constructGreenList(Dependencies dependencies)
	{
		Set result= new HashSet();
		for (String clazz : dependencies.keySet())
		{
			// Red classes are all bad classes that are not orange.
			if (isGoodClass(clazz))
			{
				result.add(clazz);
			}
		}
		return result;
	}

	/**
	 * Constructs a list of  pairs that need to be mocked with
	 * assertions, as these members will be accessed by the remaining good
	 * classes.
	 */
	private static Pair, Set> constructOrangeList(Dependencies dependencies)
	{
		// These are just the orange class names. This can be used to remove
		// these classes from he red list.
		Set orangeClasses= new HashSet();

		// These are the details about the classes and members that need to be
		// mocked.
		Set details= new HashSet();

		for (String clazz : dependencies.keySet())
		{
			if (!isGoodClass(clazz))
			{
				continue;
			}

			Dependencies.ClassDeps classDeps= dependencies.getDepsForClass(clazz);
			for (String method : classDeps.methodSet())
			{
				Dependencies.MethodDeps methodDeps= classDeps.getMethodDeps(method);
				for (String dep : methodDeps.classSet())
				{
					// Only bad dependencies are relevant here.
					// TODO: Add super-type relationship mocks.
					if (isGoodClass(dep))
					{
						continue;
					}
					orangeClasses.add(dep);
					for (String methodDep : methodDeps.getMethods(dep))
					{
						// MethodDep can be empty for e.g. the SUPER
						// relationship, which we don't care about for the
						// mocks at this point.
						if (!methodDep.isEmpty())
						{
							details.add(dep + "::" + methodDep);
						}
					}
				}
			}
		}
		return new Pair, Set>(orangeClasses, details);
	}

	private static void writeDepsToGraphFile(Dependencies dependencies)
	{
		Log.debug(TAG, "Writing GDF file ...");
		StringBuilder nodeDef= new StringBuilder();
		StringBuilder edgeDef= new StringBuilder();

		nodeDef.append("nodedef> name,color,style\n");
		edgeDef.append("edgedef> node1,node2,method VARCHAR(32)\n");

		Set allNodes= new HashSet();
		for (String clazz : dependencies.keySet())
		{
			// Only write out good classes.
			if (!isGoodClass(clazz))
			{
				continue;
			}
			boolean hasDepsToGraph= false;
			Dependencies.ClassDeps classDeps= dependencies.getDepsForClass(clazz);
			for (String method : classDeps.methodSet())
			{
				Dependencies.MethodDeps methodDeps= classDeps.getMethodDeps(method);
				for (String dep : methodDeps.classSet())
				{
					// To reduce complexity in the graph, only map bad deps.
					if (isGoodClass(dep))
					{
						continue;
					}
					for (String methodDep : methodDeps.getMethods(dep))
					{
						allNodes.add(dep);
						edgeDef.append(clazz);
						edgeDef.append(",");
						edgeDef.append(dep);
						edgeDef.append(",");
						edgeDef.append(method + "->" + methodDep);
						edgeDef.append("\n");
						hasDepsToGraph= true;
					}
				}
			}
			if (hasDepsToGraph)
			{
				allNodes.add(clazz);
			}
		}

		for (String node : allNodes)
		{
			nodeDef.append(node);
			if (isGoodClass(node))
			{
				nodeDef.append(",");
				nodeDef.append("blue");
				nodeDef.append(",");
				nodeDef.append("1");
			}
			else
			{
				nodeDef.append(",");
				nodeDef.append("red");
				nodeDef.append(",");
				nodeDef.append("2");
			}
			nodeDef.append("\n");
		}

		FileWriter fw;
		try
		{
			fw= new FileWriter(GRAPH_FILENAME);
			fw.write(nodeDef.toString());
			fw.write(edgeDef.toString());
			fw.close();
			Log.debug(TAG, "Writing GDF file done.");
		}
		catch (IOException e)
		{
			Log.error(TAG, "Writing GDF file failed: " + e.getMessage());
		}
	}

	private static void printDirectBadRefs()
	{
		List clazzes= new ArrayList(badClassCount.keySet());

		Collections.sort(clazzes, new Comparator()
		{

			public int compare(String o1, String o2)
			{
				return badClassCount.get(o1) - badClassCount.get(o2);
			}
		});

		Log.debug(TAG, "Top direct bad refs:");
		for (String badDep : clazzes)
		{
			int count= badClassCount.get(badDep);
			if (count > 10)
			{
				Log.debug(TAG, count + " - " + badDep);
			}
		}
	}

	/**
	 * Returns a list of classes that contain bad dependencies.
	 */
	private static Set getGoodClassesWithBadReferences(Dependencies data)
	{
		Set result= new HashSet();

		// We try to cache the result of this analysis on disk as well. If we
		// find this cache, we load it.
		File resultsFile= new File(RESULTS2_FILENAME);
		if (resultsFile.exists())
		{
			Log.debug(TAG, "Attempting to read second result from " + RESULTS2_FILENAME);
			try
			{
				ObjectInputStream ois= new ObjectInputStream(new FileInputStream(resultsFile));
				result= (Set) ois.readObject();
			}
			catch (FileNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (IOException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
		}
		else
		{
			// If the cache could not be loaded, we perform the actual analysis
			// and store the result on disk for further calls.
			int goods= 0;
			for (String className : data.keySet())
			{
				if (isGoodClass(className))
				{
					goods++;
					if (hasBadDep(className, data))
					{
						result.add(className);
					}
					else
					{
						Log.debug(TAG, "A good class without bad rep!");
					}
				}
			}

			Log.debug(TAG, "Good classes: " + goods);
			try
			{
				ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream(resultsFile));
				oos.writeObject(result);
			}
			catch (FileNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (IOException e)
			{
				e.printStackTrace();
				return null;
			}
		}
		return result;
	}

	private static boolean hasBadDep(String className, Dependencies data)
	{
		boolean result= false;
		for (String dep : data.getAllDepsForClass(className))
		{
			for (String goodDep : GOOD_PACKAGES)
			{
				if (!dep.startsWith(goodDep))
				{
					if (!badClassCount.containsKey(dep))
					{
						badClassCount.put(dep, 0);
					}
					badClassCount.put(dep, badClassCount.get(dep) + 1);
					result= true;
					break;
				}
			}
		}
		return result;
	}

	/**
	 * Returns whether the given class name is a good class.
	 */
	private static boolean isGoodClass(String className)
	{
		// First check the overrides.
		for (String good : GOOD_CLASSES)
		{
			if (className.equals(good))
			{
				return true;
			}
		}
		for (String bad : BAD_CLASSES)
		{
			if (className.equals(bad))
			{
				return false;
			}
		}

		for (String goodDep : GOOD_PACKAGES)
		{
			if (className.startsWith(goodDep))
			{
				return true;
			}
		}
		return false;
	}

	private static Dependencies loadDepencencies(String jdkFileName)
	{
		Dependencies dependencies= new Dependencies();

		// This is a shortcut, in case we already have the analytical data about
		// the dependencies loaded and stored on the file system. If so, we use
		// this data.
		File resultsFile= new File(RESULTS_FILENAME);
		if (resultsFile.exists())
		{
			Log.debug(TAG, "Loading results from " + RESULTS_FILENAME + " ...");
			ObjectInputStream ois;
			try
			{
				ois= new ObjectInputStream(new FileInputStream(resultsFile));
				dependencies= (Dependencies) ois.readObject();
				ois.close();
				Log.debug(TAG, "Loaded results from file: " + dependencies.size());
			}
			catch (FileNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (IOException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
		}
		else
		{
			// If there is no stored result, we nee to analyze the classes
			// first.
			// First the class files are loaded.
			UniversalFile library= UniversalFileCreator.createDirectory(null, jdkFileName);
			UniversalFile[] classes= library.listFilesRecursively(new UniversalFileFilter()
			{

				public boolean accept(UniversalFile file)
				{
					return file.getName().toLowerCase().endsWith(".class");
				}
			});
			Log.debug(TAG, "Analyzing " + classes.length + " classes.");
			final String basePath= library.getAbsolutePath();

			// We go through all the class files and get their dependencies.
			for (UniversalFile clazz : classes)
			{
				String fileName= clazz.getRelativePath(basePath).replace('\\', '.');
				Dependencies.ClassDeps classDeps= dependencies.getDepsForClass(fileName.substring(0, fileName.length() - 6).replace(File.separatorChar, '.'));
				getAllDependencies(clazz.getFileAsBytes(), fileName, classDeps);
			}

			// Store results on disk so we save time when executing this a
			// second time.
			try
			{
				Log.debug(TAG, "Writing result to file " + RESULTS_FILENAME);
				ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream(RESULTS_FILENAME));
				oos.writeObject(dependencies);
				oos.close();
				Log.debug(TAG, "Done.");
			}
			catch (FileNotFoundException e)
			{
				e.printStackTrace();
				return null;
			}
			catch (IOException e)
			{
				e.printStackTrace();
				return null;
			}
		}
		return dependencies;
	}

	/**
	 * Adds all dependencies of the given class to classDeps.
	 */
	private static void getAllDependencies(byte[] bytes, String relativePath, Dependencies.ClassDeps classDeps)
	{

		Log.debug(TAG, relativePath);
		DirectClassFile classFile= new DirectClassFile(bytes, relativePath, false);
		classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
		try
		{
			classFile.getMagic();
		}
		catch (ParseException ex)
		{
			Log.warn(TAG, "Put to red-list as it couldn't be parsed: " + relativePath);
			BAD_CLASSES.add(classDeps.getClassName());
			return;
		}

		String superClassName= "";
		// This can happen for java.lang.Object.
		if (classFile.getSuperclass() != null)
		{
			superClassName= Util.parseClassName(classFile.getSuperclass().getClassType().getClassName()).toString();
		}

		// Super Class
		if (!superClassName.isEmpty())
		{
			Set superClass= new HashSet();
			superClass.add(superClassName.replace('/', '.'));
			classDeps.getMethodDeps("SUPER").addDependency(superClassName.replace('/', '.'), "");
		}

		// Interfaces
		TypeList interfaces= classFile.getInterfaces();
		if (interfaces.size() > 0)
		{
			Set interfaceList= new HashSet();
			for (int i= 0; i < interfaces.size(); ++i)
			{
				interfaceList.add(Util.parseClassName(interfaces.getType(i).getClassName()).toString());
				classDeps.getMethodDeps("INTERFACES").addDependency(Util.parseClassName(interfaces.getType(i).getClassName()).toString(), "");
			}
		}

		// Methods
		MethodList methods= classFile.getMethods();
		for (int i= 0; i < methods.size(); i++)
		{
			Method method= methods.get(i);
			// CstMethodRef methodRef = new
			// CstMethodRef(method.getDefiningClass(), method.getNat());
			// We shouldn't need to go through the signature. If the class is
			// not used in the code block, we can ignore it.
			// processSignature(methodRef, dependencies);
			processCode(getCode(method, classFile), classDeps.getMethodDeps(method.getName().toHuman()));
		}
	}

	/**
	 * Extracts all types from the method signature.
	 */
	private static void processSignature(CstMethodRef methodRef, Set dependencies)
	{
		Prototype prototype= methodRef.getPrototype();

		// Parameter types.
		StdTypeList parameters= prototype.getParameterTypes();
		for (int i= 0; i < parameters.size(); ++i)
		{
			String parameterType= parameters.get(i).toHuman();
			dependencies.add(parameterType);
		}

		// Return type.
		dependencies.add(prototype.getReturnType().getType().toHuman());
	}

	private static void processCode(DalvCode code, Dependencies.MethodDeps methodDeps)
	{
		if (code == null)
		{
			return;
		}
		// TODO: We don't need this, as it should be added by code blocks, if
		// the exception is actually used. If it's not used, we shouldn't care.
		// processCatchTable(code.getCatches(), dependencies);

		DalvInsnList instructions= code.getInsns();
		for (int i= 0; i < instructions.size(); ++i)
		{
			processInstruction(instructions.get(i), methodDeps);
		}
	}

	private static void processInstruction(DalvInsn instruction, Dependencies.MethodDeps methodDeps)
	{
		String opname= instruction.getOpcode().getName();
		if (opname.equals("instance-of") || opname.equals("const-class"))
		{
			CstInsn isaInsn= (CstInsn) instruction;
			// TODO: Do we need this?
			// dependencies.add(isaInsn.getConstant().toHuman());
		}

		// TODO: Do we need to add these?
		// RegisterSpecList registers = instruction.getRegisters();
		// for (int i = 0; i < registers.size(); ++i) {
		// RegisterSpec register = registers.get(i);
		// String descriptor = register.getType().getDescriptor();
		// String registerType = register.getType().toHuman();
		// // Sometimes a register type name starts with some info about the
		// // register. We need to cut this out.
		// if (descriptor.startsWith("N")) {
		// registerType = registerType.substring(registerType.indexOf('L') + 1);
		// }
		// methodDeps.addDependency(registerType, "TODO");
		// }

		if (instruction instanceof CstInsn)
		{
			CstInsn cstInsn= (CstInsn) instruction;
			if (isInvokeInstruction(cstInsn))
			{
				// Adds the class that a method references
			    	CstBaseMethodRef methodRef= (CstBaseMethodRef) cstInsn.getConstant();
				methodDeps.addDependency(methodRef.getDefiningClass().toHuman(), methodRef.getNat().getName().toHuman() + ":" + methodRef.getPrototype().toString());
			}
			else
			{
				Constant constant= cstInsn.getConstant();
				if (constant instanceof CstMemberRef)
				{
					CstMemberRef memberRef= (CstMemberRef) constant;

					methodDeps.addDependency(memberRef.getDefiningClass().getClassType().toHuman(), memberRef.getNat().getName().toHuman());

					// dependencies.add(memberRef.getNat().getFieldType().getType().toHuman());
				}
			}
		}
		else if (instruction instanceof HighRegisterPrefix)
		{
			HighRegisterPrefix highRegisterPrefix= (HighRegisterPrefix) instruction;
			SimpleInsn[] moveInstructions= highRegisterPrefix.getMoveInstructions();
			for (SimpleInsn moveInstruction : moveInstructions)
			{
				// Recurse.
				processInstruction(moveInstruction, methodDeps);
			}
		}
	}

	/**
	 * Adds all exception types.
	 */
	private static void processCatchTable(CatchTable catchTable, Map> dependencies)
	{
		if (catchTable.size() == 0)
		{
			return;
		}
		for (int i= 0; i < catchTable.size(); ++i)
		{
			Entry entry= catchTable.get(i);
			CatchHandlerList catchHandlers= entry.getHandlers();
			for (int j= 0; j < catchHandlers.size(); ++j)
			{
				// TODO: Do we need these? Shouldn't they be handled by code, if
				// accessed?
				// dependencies.add(catchHandlers.get(j).getExceptionType().toHuman());
			}
		}
	}

	/**
	 * Returns whether the given instruction is an invoke instruction that can
	 * be handled by {@link #processInvokeInstruction(CstInsn)}.
	 */
	private static boolean isInvokeInstruction(CstInsn cstInsn)
	{
		final Dop[] invokeInstructions= { Dops.INVOKE_VIRTUAL, Dops.INVOKE_VIRTUAL_RANGE, Dops.INVOKE_STATIC, Dops.INVOKE_STATIC_RANGE, Dops.INVOKE_DIRECT, Dops.INVOKE_DIRECT_RANGE, Dops.INVOKE_INTERFACE, Dops.INVOKE_INTERFACE_RANGE, Dops.INVOKE_SUPER, Dops.INVOKE_SUPER_RANGE };
		for (Dop dop : invokeInstructions)
		{
			if (dop.equals(cstInsn.getOpcode()))
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * Extracts the code block from the given method of the given class, or
	 * null, if method is native or abstract.
	 */
	private static DalvCode getCode(Method method, DirectClassFile classFile)
	{
		boolean isNative= AccessFlags.isNative(method.getAccessFlags());
		boolean isStatic= AccessFlags.isStatic(method.getAccessFlags());
		boolean isAbstract= AccessFlags.isAbstract(method.getAccessFlags());

		if (isNative || isAbstract)
		{
			return null;
		}

		ConcreteMethod concrete= new ConcreteMethod(method, classFile, false, false);
		TranslationAdvice advice= DexTranslationAdvice.THE_ONE;
		RopMethod rmeth= Ropper.convert(concrete, advice);
		CstMethodRef meth= new CstMethodRef(method.getDefiningClass(), method.getNat());
		int paramSize= meth.getParameterWordCount(isStatic);
		DalvCode code= RopTranslator.translate(rmeth, PositionList.NONE, null, paramSize);
		DalvCode.AssignIndicesCallback callback= new DalvCode.AssignIndicesCallback()
		{
			public int getIndex(Constant cst)
			{
				// Everything is at index 0!
				return 0;
			}
		};
		code.assignIndices(callback);
		return code;
	}

	/**
	 * Writes a set to a file. Each item will be on a single line in the
	 * resulting file.
	 */
	private static void writeSetToFile(Set set, String fileName)
	{
		try
		{
			FileWriter writer= new FileWriter(fileName);
			for (String item : set)
			{
				writer.write(item);
				writer.write('\n');
			}
			writer.close();
			Log.debug(TAG, "File written: " + fileName);
		}
		catch (IOException e)
		{
			Log.error(TAG, "Error while writing set to file: " + e.getMessage());
		}
	}

	/**
	 * This method returns a list of methods (in green classes) that need to be
	 * added and mocked failing implementations.
	 * 

* Details: Imagine a green subclass of a red class which doesn't override * some protected or public methods/members of the red class. If such a * member is accessed on the subclass, we would fail to detect that this is * a bad call, that propagates up to the red class. With this list we detect * this situation and don't let the call bubble up. Instead an assertion * will fired in the mock implementation of the method in the sub-class. * This way we don't need to remove the whole green class. */ private static Set determineGreenMockMethods(Dependencies dependencies, TypeHierarchy hierarchy) { Set result= new HashSet(); for (String clazz : dependencies.keySet()) { // Once we find a bad class, we go through the sub-classes and try // to find good ones. if (!isGoodClass(clazz)) { Set subClasses= hierarchy.getDirectSubTypes(clazz); if (subClasses != null) { for (String subClass : subClasses) { if (isGoodClass(subClass)) { System.out.println(clazz + " -> " + subClass); // TODO: Need to get list of private and protected // members of good and bad classes. } } } } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy