Package org.xmlvm.util.analytics

Source Code of org.xmlvm.util.analytics.JDKAnalyzer

/* 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<String, Integer> badClassCount= new HashMap<String, Integer>();
  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<String> GOOD_CLASSES= new HashSet<String>();

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

  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<String> mockMethods= determineGreenMockMethods(dependencies, hierarchy);

    Pair<Set<String>, Set<String>> orangeLists= constructOrangeList(dependencies);
    Set<String> redList= constructRedList(dependencies, orangeLists.first);
    Set<String> 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<String> 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<String> constructRedList(Dependencies dependencies, Set<String> orangeList)
  {
    Set<String> result= new HashSet<String>();
    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<String> constructGreenList(Dependencies dependencies)
  {
    Set<String> result= new HashSet<String>();
    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 <class/member> pairs that need to be mocked with
   * assertions, as these members will be accessed by the remaining good
   * classes.
   */
  private static Pair<Set<String>, Set<String>> constructOrangeList(Dependencies dependencies)
  {
    // These are just the orange class names. This can be used to remove
    // these classes from he red list.
    Set<String> orangeClasses= new HashSet<String>();

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

    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<String>, Set<String>>(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<String> allNodes= new HashSet<String>();
    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<String> clazzes= new ArrayList<String>(badClassCount.keySet());

    Collections.sort(clazzes, new Comparator<String>()
    {

      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<String> getGoodClassesWithBadReferences(Dependencies data)
  {
    Set<String> result= new HashSet<String>();

    // 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<String>) 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<String> superClass= new HashSet<String>();
      superClass.add(superClassName.replace('/', '.'));
      classDeps.getMethodDeps("SUPER").addDependency(superClassName.replace('/', '.'), "");
    }

    // Interfaces
    TypeList interfaces= classFile.getInterfaces();
    if (interfaces.size() > 0)
    {
      Set<String> interfaceList= new HashSet<String>();
      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<String> 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<String, Set<String>> 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
   * <code>null</code>, 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<String> 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.
   * <p>
   * 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<String> determineGreenMockMethods(Dependencies dependencies, TypeHierarchy hierarchy)
  {
    Set<String> result= new HashSet<String>();
    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<String> 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;
  }
}
TOP

Related Classes of org.xmlvm.util.analytics.JDKAnalyzer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.