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

soot.Scene Maven / Gradle / Ivy

package soot;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 - 1999 Raja Vallee-Rai
 * Copyright (C) 2004 Ondrej Lhotak
 * %%
 * 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 program 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.MagicNumberFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.NodeVisitor;

import soot.dexpler.DalvikThrowAnalysis;
import soot.jimple.spark.internal.ClientAccessibilityOracle;
import soot.jimple.spark.internal.PublicAndProtectedAccessibility;
import soot.jimple.spark.pag.SparkField;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.ContextSensitiveCallGraph;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import soot.jimple.toolkits.pointer.DumbPointerAnalysis;
import soot.jimple.toolkits.pointer.SideEffectAnalysis;
import soot.options.CGOptions;
import soot.options.Options;
import soot.toolkits.exceptions.PedanticThrowAnalysis;
import soot.toolkits.exceptions.ThrowAnalysis;
import soot.toolkits.exceptions.UnitThrowAnalysis;
import soot.util.ArrayNumberer;
import soot.util.Chain;
import soot.util.HashChain;
import soot.util.MapNumberer;
import soot.util.Numberer;
import soot.util.StringNumberer;

/** Manages the SootClasses of the application being analyzed. */
public class Scene // extends AbstractHost
{
  private static final Logger logger = LoggerFactory.getLogger(Scene.class);

  private final int defaultSdkVersion = 15;
  private final Map maxAPIs = new HashMap();

  public Scene(Singletons.Global g) {
    setReservedNames();

    // load soot.class.path system property, if defined
    String scp = System.getProperty("soot.class.path");

    if (scp != null) {
      setSootClassPath(scp);
    }

    kindNumberer = new ArrayNumberer(
        new Kind[] { Kind.INVALID, Kind.STATIC, Kind.VIRTUAL, Kind.INTERFACE, Kind.SPECIAL, Kind.CLINIT, Kind.THREAD,
            Kind.EXECUTOR, Kind.ASYNCTASK, Kind.FINALIZE, Kind.INVOKE_FINALIZE, Kind.PRIVILEGED, Kind.NEWINSTANCE });

    addSootBasicClasses();

    determineExcludedPackages();
  }

  private void determineExcludedPackages() {
    excludedPackages = new LinkedList();
    if (Options.v().exclude() != null) {
      excludedPackages.addAll(Options.v().exclude());
    }

    // do not kill contents of the APK if we want a working new APK
    // afterwards
    if (!Options.v().include_all() && Options.v().output_format() != Options.output_format_dex
        && Options.v().output_format() != Options.output_format_force_dex) {
      excludedPackages.add("java.*");
      excludedPackages.add("sun.*");
      excludedPackages.add("javax.*");
      excludedPackages.add("com.sun.*");
      excludedPackages.add("com.ibm.*");
      excludedPackages.add("org.xml.*");
      excludedPackages.add("org.w3c.*");
      excludedPackages.add("apple.awt.*");
      excludedPackages.add("com.apple.*");
    }
  }

  public static Scene v() {
    return G.v().soot_Scene();
  }

  Chain classes = new HashChain();
  Chain applicationClasses = new HashChain();
  Chain libraryClasses = new HashChain();
  Chain phantomClasses = new HashChain();

  private final Map nameToClass = new HashMap();

  protected final ArrayNumberer kindNumberer;
  protected ArrayNumberer typeNumberer = new ArrayNumberer();
  protected ArrayNumberer methodNumberer = new ArrayNumberer();
  protected Numberer unitNumberer = new MapNumberer();
  protected Numberer contextNumberer = null;
  protected Numberer fieldNumberer = new ArrayNumberer();
  protected ArrayNumberer classNumberer = new ArrayNumberer();
  protected StringNumberer subSigNumberer = new StringNumberer();
  protected ArrayNumberer localNumberer = new ArrayNumberer();

  protected Hierarchy activeHierarchy;
  protected FastHierarchy activeFastHierarchy;
  protected CallGraph activeCallGraph;
  protected ReachableMethods reachableMethods;
  protected PointsToAnalysis activePointsToAnalysis;
  protected SideEffectAnalysis activeSideEffectAnalysis;
  protected List entryPoints;
  protected ClientAccessibilityOracle accessibilityOracle;

  protected boolean allowsPhantomRefs = false;

  protected SootClass mainClass;
  protected String sootClassPath = null;

  // Two default values for constructing ExceptionalUnitGraphs:
  private ThrowAnalysis defaultThrowAnalysis = null;

  private int androidAPIVersion = -1;

  public void setMainClass(SootClass m) {
    mainClass = m;
    if (!m.declaresMethod(getSubSigNumberer().findOrAdd("void main(java.lang.String[])"))) {
      throw new RuntimeException("Main-class has no main method!");
    }
  }

  Set reservedNames = new HashSet();

  /**
   * Returns a set of tokens which are reserved. Any field, class, method, or local variable with such a name will be quoted.
   */

  public Set getReservedNames() {
    return reservedNames;
  }

  /**
   * If this name is in the set of reserved names, then return a quoted version of it. Else pass it through. If the name
   * consists of multiple parts separated by dots, the individual names are checked as well.
   */
  public String quotedNameOf(String s) {
    // Pre-check: Is there a chance that we need to escape something?
    // If not, skip the transformation altogether.
    boolean found = s.contains("-");
    for (String token : reservedNames) {
      if (s.contains(token)) {
        found = true;
        break;
      }
    }
    if (!found) {
      return s;
    }

    StringBuilder res = new StringBuilder(s.length());
    for (String part : s.split("\\.")) {
      if (res.length() > 0) {
        res.append('.');
      }
      if (part.startsWith("-") || reservedNames.contains(part)) {
        res.append('\'');
        res.append(part);
        res.append('\'');
      } else {
        res.append(part);
      }
    }
    return res.toString();
  }

  /**
   * This method is the inverse of quotedNameOf(). It takes a possible escaped class and reconstructs the original version of
   * it.
   *
   * @param s
   *          The possibly escaped name
   * @return The original, non-escaped name
   */
  public String unescapeName(String s) {
    // If the name is not escaped, there is nothing to do here
    if (!s.contains("'")) {
      return s;
    }

    StringBuilder res = new StringBuilder(s.length());
    for (String part : s.split("\\.")) {
      if (res.length() > 0) {
        res.append('.');
      }
      if (part.startsWith("'") && part.endsWith("'")) {
        res.append(part.substring(1, part.length() - 1));
      } else {
        res.append(part);
      }
    }
    return res.toString();
  }

  public boolean hasMainClass() {
    if (mainClass == null) {
      setMainClassFromOptions();
    }
    return mainClass != null;
  }

  public SootClass getMainClass() {
    if (!hasMainClass()) {
      throw new RuntimeException("There is no main class set!");
    }

    return mainClass;
  }

  public SootMethod getMainMethod() {
    if (!hasMainClass()) {
      throw new RuntimeException("There is no main class set!");
    }

    SootMethod mainMethod = mainClass.getMethodUnsafe("main",
        Collections.singletonList(ArrayType.v(RefType.v("java.lang.String"), 1)), VoidType.v());
    if (mainMethod == null) {
      throw new RuntimeException("Main class declares no main method!");
    }
    return mainMethod;
  }

  public void setSootClassPath(String p) {
    sootClassPath = p;
    SourceLocator.v().invalidateClassPath();
  }

  public void extendSootClassPath(String newPathElement) {
    sootClassPath += File.pathSeparator + newPathElement;
    SourceLocator.v().extendClassPath(newPathElement);
  }

  public String getSootClassPath() {
    if (sootClassPath == null) {
      String optionscp = Options.v().soot_classpath();
      if (optionscp != null && optionscp.length() > 0) {
        sootClassPath = optionscp;
      }

      // if no classpath is given on the command line, take the default
      if (sootClassPath == null || sootClassPath.isEmpty()) {
        sootClassPath = defaultClassPath();
      } else {
        // if one is given...
        if (Options.v().prepend_classpath()) {
          // if the prepend flag is set, append the default classpath
          sootClassPath += File.pathSeparator + defaultClassPath();
        }
        // else, leave it as it is
      }

      // add process-dirs
      List process_dir = Options.v().process_dir();
      StringBuffer pds = new StringBuffer();
      for (String path : process_dir) {
        if (!sootClassPath.contains(path)) {
          pds.append(path);
          pds.append(File.pathSeparator);
        }
      }
      sootClassPath = pds + sootClassPath;
    }

    return sootClassPath;
  }

  /**
   * Returns the max Android API version number available in directory 'dir'
   *
   * @param dir
   * @return
   */
  private int getMaxAPIAvailable(String dir) {
    Integer mapi = this.maxAPIs.get(dir);
    if (mapi != null) {
      return mapi;
    }

    File d = new File(dir);
    if (!d.exists()) {
      throw new AndroidPlatformException(
          String.format("The Android platform directory you have specified (%s) does not exist. Please check.", dir));
    }

    File[] files = d.listFiles();
    if (files == null) {
      return -1;
    }

    int maxApi = -1;
    for (File f : files) {
      String name = f.getName();
      if (f.isDirectory() && name.startsWith("android-")) {
        try {
          int v = Integer.decode(name.split("android-")[1]);
          if (v > maxApi) {
            maxApi = v;
          }
        } catch (NumberFormatException ex) {
          // We simply ignore directories that do not follow the
          // Android naming structure
        }
      }
    }
    this.maxAPIs.put(dir, maxApi);
    return maxApi;
  }

  public String getAndroidJarPath(String jars, String apk) {
    int APIVersion = getAndroidAPIVersion(jars, apk);

    String jarPath = jars + File.separator + "android-" + APIVersion + File.separator + "android.jar";

    // check that jar exists
    File f = new File(jarPath);
    if (!f.isFile()) {
      throw new AndroidPlatformException(String.format("error: target android.jar %s does not exist.", jarPath));
    }

    return jarPath;
  }

  public int getAndroidAPIVersion() {
    return androidAPIVersion > 0 ? androidAPIVersion
        : (Options.v().android_api_version() > 0 ? Options.v().android_api_version() : defaultSdkVersion);
  }

  private int getAndroidAPIVersion(String jars, String apk) {
    // Do we already have an API version?
    if (androidAPIVersion > 0) {
      return androidAPIVersion;
    }

    // get path to appropriate android.jar
    File jarsF = new File(jars);
    File apkF = apk == null ? null : new File(apk);

    if (!jarsF.exists()) {
      throw new AndroidPlatformException(
          String.format("Android platform directory '%s' does not exist!", jarsF.getAbsolutePath()));
    }

    if (apkF != null && !apkF.exists()) {
      throw new RuntimeException("file '" + apk + "' does not exist!");
    }

    // Use the default if we don't have any other information
    androidAPIVersion = defaultSdkVersion;

    // Do we have an explicit API version?
    if (Options.v().android_api_version() > 0) {
      androidAPIVersion = Options.v().android_api_version();
    } else if (apk != null) {
      if (apk.toLowerCase().endsWith(".apk")) {
        androidAPIVersion = getTargetSDKVersion(apk, jars);
      }
    }

    // If we don't have that API version installed, we take the most recent
    // one we have
    final int maxAPI = getMaxAPIAvailable(jars);
    if (maxAPI > 0 && androidAPIVersion > maxAPI) {
      androidAPIVersion = maxAPI;
    }

    // If the platform version is missing in the middle, we take the next one
    while (androidAPIVersion < maxAPI) {
      String jarPath = jars + File.separator + "android-" + androidAPIVersion + File.separator + "android.jar";
      if (new File(jarPath).exists()) {
        break;
      }
      androidAPIVersion++;
    }

    return androidAPIVersion;
  }

  private static class AndroidVersionInfo {

    public int sdkTargetVersion = -1;
    public int minSdkVersion = -1;
    public int platformBuildVersionCode = -1;

  }

  private int getTargetSDKVersion(String apkFile, String platformJARs) {
    // get AndroidManifest
    InputStream manifestIS = null;
    ZipFile archive = null;
    try {
      try {
        archive = new ZipFile(apkFile);
        for (Enumeration entries = archive.entries(); entries.hasMoreElements();) {
          ZipEntry entry = entries.nextElement();
          String entryName = entry.getName();
          // We are dealing with the Android manifest
          if (entryName.equals("AndroidManifest.xml")) {
            manifestIS = archive.getInputStream(entry);
            break;
          }
        }
      } catch (Exception e) {
        throw new RuntimeException("Error when looking for manifest in apk: " + e);
      }

      if (manifestIS == null) {
        logger.debug("Could not find sdk version in Android manifest! Using default: " + defaultSdkVersion);
        return defaultSdkVersion;
      }

      // process AndroidManifest.xml
      int maxAPI = getMaxAPIAvailable(platformJARs);
      final AndroidVersionInfo versionInfo = new AndroidVersionInfo();
      try {
        AxmlReader xmlReader = new AxmlReader(IOUtils.toByteArray(manifestIS));
        xmlReader.accept(new AxmlVisitor() {

          private String nodeName = null;

          @Override
          public void attr(String ns, String name, int resourceId, int type, Object obj) {
            super.attr(ns, name, resourceId, type, obj);

            if (nodeName != null && name != null) {
              if (nodeName.equals("manifest")) {
                if (name.equals("platformBuildVersionCode")) {
                  versionInfo.platformBuildVersionCode = Integer.valueOf("" + obj);
                }
              } else if (nodeName.equals("uses-sdk")) {
                // Obfuscated APKs often remove the attribute names and use the resourceId instead
                // Therefore it is better to check for both variants
                if (name.equals("targetSdkVersion") || (name.equals("") && resourceId == 16843376)) {
                  versionInfo.sdkTargetVersion = Integer.valueOf("" + obj);
                } else if (name.equals("minSdkVersion") || (name.equals("") && resourceId == 16843276)) {
                  versionInfo.minSdkVersion = Integer.valueOf("" + obj);
                }
              }
            }
          }

          @Override
          public NodeVisitor child(String ns, String name) {
            nodeName = name;

            return this;
          }

        });
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
      }

      int APIVersion = -1;
      if (versionInfo.sdkTargetVersion != -1) {
        if (versionInfo.sdkTargetVersion > maxAPI && versionInfo.minSdkVersion != -1
            && versionInfo.minSdkVersion <= maxAPI) {
          logger.warn("Android API version '" + versionInfo.sdkTargetVersion + "' not available, using minApkVersion '"
              + versionInfo.minSdkVersion + "' instead");
          APIVersion = versionInfo.minSdkVersion;
        } else {
          APIVersion = versionInfo.sdkTargetVersion;
        }
      } else if (versionInfo.platformBuildVersionCode != -1) {
        if (versionInfo.platformBuildVersionCode > maxAPI && versionInfo.minSdkVersion != -1
            && versionInfo.minSdkVersion <= maxAPI) {
          logger.warn("Android API version '" + versionInfo.platformBuildVersionCode
              + "' not available, using minApkVersion '" + versionInfo.minSdkVersion + "' instead");
          APIVersion = versionInfo.minSdkVersion;
        } else {
          APIVersion = versionInfo.platformBuildVersionCode;
        }
      } else if (versionInfo.minSdkVersion != -1) {
        APIVersion = versionInfo.minSdkVersion;
      } else {
        logger.debug("Could not find sdk version in Android manifest! Using default: " + defaultSdkVersion);
        APIVersion = defaultSdkVersion;
      }

      if (APIVersion <= 2) {
        APIVersion = 3;
      }
      return APIVersion;
    } finally {
      if (archive != null) {
        try {
          archive.close();
        } catch (IOException e) {
          throw new RuntimeException("Error when looking for manifest in apk: " + e);
        }
      }
    }
  }

  public String defaultClassPath() {
    // If we have an apk file on the process dir and do not have a src-prec
    // option
    // that loads APK files, we give a warning
    if (Options.v().src_prec() != Options.src_prec_apk) {
      for (String entry : Options.v().process_dir()) {
        if (entry.toLowerCase().endsWith(".apk")) {
          System.err.println("APK file on process dir, but chosen src-prec does not support loading APKs");
          break;
        }
      }
    }

    if (Options.v().src_prec() == Options.src_prec_apk) {
      return defaultAndroidClassPath();
    } else {
      return defaultJavaClassPath();
    }
  }

  private String defaultAndroidClassPath() {
    // check that android.jar is not in classpath
    String androidJars = Options.v().android_jars();
    String forceAndroidJar = Options.v().force_android_jar();
    if ((androidJars == null || androidJars.equals("")) && (forceAndroidJar == null || forceAndroidJar.equals(""))) {
      throw new RuntimeException("You are analyzing an Android application but did "
          + "not define android.jar. Options -android-jars or -force-android-jar should be used.");
    }

    // Get the platform JAR file. It either directly specified, or
    // we detect it from the target version of the APK we are
    // analyzing
    String jarPath = "";
    if (forceAndroidJar != null && !forceAndroidJar.isEmpty()) {
      jarPath = forceAndroidJar;

      if (Options.v().android_api_version() > 0) {
        androidAPIVersion = Options.v().android_api_version();
      } else if (forceAndroidJar.contains("android-")) {
        Pattern pt = Pattern.compile("\\" + File.separatorChar + "android-(\\d+)" + "\\" + File.separatorChar);
        Matcher m = pt.matcher(forceAndroidJar);
        if (m.find()) {
          androidAPIVersion = Integer.valueOf(m.group(1));
        }
      } else {
        androidAPIVersion = defaultSdkVersion;
      }
    } else if (androidJars != null && !androidJars.isEmpty()) {
      List classPathEntries
          = new LinkedList(Arrays.asList(Options.v().soot_classpath().split(File.pathSeparator)));
      classPathEntries.addAll(Options.v().process_dir());

      String targetApk = "";
      Set targetDexs = new HashSet();
      for (String entry : classPathEntries) {
        if (isApk(entry)) {
          if (targetApk != null && !targetApk.isEmpty()) {
            throw new RuntimeException("only one Android application can be analyzed when using option -android-jars.");
          }
          targetApk = entry;
        }
        if (entry.toLowerCase().endsWith(".dex")) {
          // names are
          // case-insensitive
          targetDexs.add(entry);
        }
      }

      // We need at least one file to process
      if (targetApk == null || targetApk.isEmpty()) {
        if (targetDexs.isEmpty()) {
          throw new RuntimeException("no apk file given");
        }
        jarPath = getAndroidJarPath(androidJars, null);
      } else {
        jarPath = getAndroidJarPath(androidJars, targetApk);
      }
    }

    // We must have a platform JAR file when analyzing Android apps
    if (jarPath.equals("")) {
      throw new RuntimeException("android.jar not found.");
    }

    // Check the platform JAR file
    File f = new File(jarPath);
    if (!f.exists()) {
      throw new RuntimeException("file '" + jarPath + "' does not exist!");
    } else {
      logger.debug("Using '" + jarPath + "' as android.jar");
    }

    return jarPath;
  }

  public static boolean isApk(String file) {
    // decide if a file is an APK by its magic number and whether it contains dex file.
    boolean r = false;
    // first check magic number
    File apk = new File(file);
    MagicNumberFileFilter apkFilter
        = new MagicNumberFileFilter(new byte[] { (byte) 0x50, (byte) 0x4B, (byte) 0x03, (byte) 0x04 });
    if (!apkFilter.accept(apk)) {
      return r;
    }
    // second check if contains dex file.
    ZipFile zf = null;
    try {
      zf = new ZipFile(file);
      Enumeration en = zf.entries();
      while (en.hasMoreElements()) {
        ZipEntry z = (ZipEntry) en.nextElement();
        String name = z.getName();
        if (name.equals("classes.dex")) {
          r = true;
          break;
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (zf != null) {
        try {
          zf.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    return r;
  }

  private String defaultJavaClassPath() {
    StringBuilder sb = new StringBuilder();
    if (System.getProperty("os.name").equals("Mac OS X")) {
      // in older Mac OS X versions, rt.jar was split into classes.jar and
      // ui.jar
      String prefix = System.getProperty("java.home") + File.separator + ".." + File.separator + "Classes" + File.separator;
      File classesJar = new File(prefix + "classes.jar");
      if (classesJar.exists()) {
        sb.append(classesJar.getAbsolutePath() + File.pathSeparator);
      }

      File uiJar = new File(prefix + "ui.jar");
      if (uiJar.exists()) {
        sb.append(uiJar.getAbsolutePath() + File.pathSeparator);
      }
    }

    File rtJar = new File(System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar");
    if (rtJar.exists() && rtJar.isFile()) {
      // logger.debug("Using JRE runtime: " +
      // rtJar.getAbsolutePath());
      sb.append(rtJar.getAbsolutePath());
    } else {
      // in case we're not in JRE environment, try JDK
      rtJar = new File(
          System.getProperty("java.home") + File.separator + "jre" + File.separator + "lib" + File.separator + "rt.jar");
      if (rtJar.exists() && rtJar.isFile()) {
        // logger.debug("Using JDK runtime: " +
        // rtJar.getAbsolutePath());
        sb.append(rtJar.getAbsolutePath());
      } else {
        // not in JDK either
        throw new RuntimeException("Error: cannot find rt.jar.");
      }
    }

    if (Options.v().whole_program() || Options.v().output_format() == Options.output_format_dava) {
      // add jce.jar, which is necessary for whole program mode
      // (java.security.Signature from rt.jar import javax.crypto.Cipher
      // from jce.jar
      sb.append(File.pathSeparator + System.getProperty("java.home") + File.separator + "lib" + File.separator + "jce.jar");
    }

    return sb.toString();
  }

  private int stateCount;

  public int getState() {
    return this.stateCount;
  }

  protected void modifyHierarchy() {
    stateCount++;
    activeHierarchy = null;
    activeFastHierarchy = null;
    activeSideEffectAnalysis = null;
    activePointsToAnalysis = null;
  }

  /**
   * Adds the given class to the Scene. This method marks the given class as a library class and invalidates the class
   * hierarchy.
   *
   * @param c
   *          The class to add
   */
  public void addClass(SootClass c) {
    addClassSilent(c);
    c.setLibraryClass();
    modifyHierarchy();
  }

  /**
   * Adds the given class to the Scene. This method does not handle any dependencies such as invalidating the hierarchy. The
   * class is neither marked as application class, nor library class.
   *
   * @param c
   *          The class to add
   */
  protected void addClassSilent(SootClass c) {
    if (c.isInScene()) {
      throw new RuntimeException("already managed: " + c.getName());
    }

    if (containsClass(c.getName())) {
      throw new RuntimeException("duplicate class: " + c.getName());
    }

    classes.add(c);
    nameToClass.put(c.getName(), c.getType());
    c.getType().setSootClass(c);
    c.setInScene(true);

    // Phantom classes are not really part of the hierarchy anyway, so
    // we can keep the old one
    if (!c.isPhantom) {
      modifyHierarchy();
    }
  }

  public void removeClass(SootClass c) {
    if (!c.isInScene()) {
      throw new RuntimeException();
    }

    classes.remove(c);

    if (c.isLibraryClass()) {
      libraryClasses.remove(c);
    } else if (c.isPhantomClass()) {
      phantomClasses.remove(c);
    } else if (c.isApplicationClass()) {
      applicationClasses.remove(c);
    }

    c.getType().setSootClass(null);
    c.setInScene(false);
    modifyHierarchy();
  }

  public boolean containsClass(String className) {
    RefType type = nameToClass.get(className);
    if (type == null) {
      return false;
    }
    if (!type.hasSootClass()) {
      return false;
    }
    SootClass c = type.getSootClass();
    return c.isInScene();
  }

  public boolean containsType(String className) {
    return nameToClass.containsKey(className);
  }

  public String signatureToClass(String sig) {
    if (sig.charAt(0) != '<') {
      throw new RuntimeException("oops " + sig);
    }
    if (sig.charAt(sig.length() - 1) != '>') {
      throw new RuntimeException("oops " + sig);
    }
    int index = sig.indexOf(":");
    if (index < 0) {
      throw new RuntimeException("oops " + sig);
    }
    return unescapeName(sig.substring(1, index));
  }

  public String signatureToSubsignature(String sig) {
    if (sig.charAt(0) != '<') {
      throw new RuntimeException("oops " + sig);
    }
    if (sig.charAt(sig.length() - 1) != '>') {
      throw new RuntimeException("oops " + sig);
    }
    int index = sig.indexOf(":");
    if (index < 0) {
      throw new RuntimeException("oops " + sig);
    }
    return sig.substring(index + 2, sig.length() - 1);
  }

  public SootField grabField(String fieldSignature) {
    String cname = signatureToClass(fieldSignature);
    String fname = signatureToSubsignature(fieldSignature);
    if (!containsClass(cname)) {
      return null;
    }
    SootClass c = getSootClass(cname);
    return c.getFieldUnsafe(fname);
  }

  public boolean containsField(String fieldSignature) {
    return grabField(fieldSignature) != null;
  }

  public SootMethod grabMethod(String methodSignature) {
    String cname = signatureToClass(methodSignature);
    String mname = signatureToSubsignature(methodSignature);
    if (!containsClass(cname)) {
      return null;
    }
    SootClass c = getSootClass(cname);
    return c.getMethodUnsafe(mname);
  }

  public boolean containsMethod(String methodSignature) {
    return grabMethod(methodSignature) != null;
  }

  public SootField getField(String fieldSignature) {
    SootField f = grabField(fieldSignature);
    if (f != null) {
      return f;
    }

    throw new RuntimeException("tried to get nonexistent field " + fieldSignature);
  }

  public SootMethod getMethod(String methodSignature) {
    SootMethod m = grabMethod(methodSignature);
    if (m != null) {
      return m;
    }
    throw new RuntimeException("tried to get nonexistent method " + methodSignature);
  }

  /**
   * Attempts to load the given class and all of the required support classes. Returns the original class if it was loaded,
   * or null otherwise.
   */

  public SootClass tryLoadClass(String className, int desiredLevel) {
    /*
     * if(Options.v().time()) Main.v().resolveTimer.start();
     */

    setPhantomRefs(true);
    ClassSource source = SourceLocator.v().getClassSource(className);
    try {
      if (!getPhantomRefs() && source == null) {
        setPhantomRefs(false);
        return null;
      }
    } finally {
      if (source != null) {
        source.close();
      }
    }
    SootResolver resolver = SootResolver.v();
    SootClass toReturn = resolver.resolveClass(className, desiredLevel);
    setPhantomRefs(false);

    return toReturn;

    /*
     * if(Options.v().time()) Main.v().resolveTimer.end();
     */
  }

  /**
   * Loads the given class and all of the required support classes. Returns the first class.
   */

  public SootClass loadClassAndSupport(String className) {
    SootClass ret = loadClass(className, SootClass.SIGNATURES);
    if (!ret.isPhantom()) {
      ret = loadClass(className, SootClass.BODIES);
    }
    return ret;
  }

  public SootClass loadClass(String className, int desiredLevel) {
    /*
     * if(Options.v().time()) Main.v().resolveTimer.start();
     */

    setPhantomRefs(true);
    // SootResolver resolver = new SootResolver();
    SootResolver resolver = SootResolver.v();
    SootClass toReturn = resolver.resolveClass(className, desiredLevel);
    setPhantomRefs(false);

    return toReturn;

    /*
     * if(Options.v().time()) Main.v().resolveTimer.end();
     */
  }

  /**
   * Returns the RefType with the given class name or primitive type.
   *
   * @throws RuntimeException
   *           if the Type for this name cannot be found. Use {@link #getRefTypeUnsafe(String)} to check if type is an
   *           registered RefType.
   */
  public Type getType(String arg) {
    Type t = getTypeUnsafe(arg, false);// Set to false to preserve the original functionality just in case
    if (t == null) {
      throw new RuntimeException("Unknown Type: '" + t + "'");
    }
    return t;
  }

  /**
   * Returns a Type object representing the given type string. It will first attempt to resolve the type as a reference type
   * that currently exists in the Scene. If this fails it will attempt to resolve the type as a java primitive type. Lastly,
   * if phantom refs are allowed it will construct a phantom class for the given type and return a reference type based on
   * the phantom class. Otherwise, this method will return null. Note, if the resolved base type is not null and the string
   * representation of the type is an array, the returned type will be an ArrayType with the base type resolved as described
   * above.
   * 
   * @param arg
   *          A string description of the type
   * @return The Type if it can be resolved and null otherwise
   */
  public Type getTypeUnsafe(String arg) {
    return getTypeUnsafe(arg, true);
  }

  /**
   * Returns a Type object representing the given type string. It will first attempt to resolve the type as a reference type
   * that currently exists in the Scene. If this fails it will attempt to resolve the type as a java primitive type. Lastly,
   * if phantom refs are allowed and phantomNonExist=true it will construct a phantom class for the given type and return a
   * reference type based on the phantom class. Otherwise, this method will return null. Note, if the resolved base type is
   * not null and the string representation of the type is an array, the returned type will be an ArrayType with the base
   * type resolved as described above.
   * 
   * @param arg
   *          A string description of the type
   * @param phantomNonExist
   *          Indicates that a phantom class should be created for the given type string and a Type object should be created
   *          based on the phantom class if a class matching the type name does not exists in the scene and phantom refs are
   *          allowed
   * @return The Type if it can be resolved and null otherwise
   */
  public Type getTypeUnsafe(String arg, boolean phantomNonExist) {
    String type = arg.replaceAll("([^\\[\\]]*)(.*)", "$1");
    int arrayCount = arg.contains("[") ? arg.replaceAll("([^\\[\\]]*)(.*)", "$2").length() / 2 : 0;

    Type result = getRefTypeUnsafe(type);

    if (result == null) {
      if (type.equals("long")) {
        result = LongType.v();
      } else if (type.equals("short")) {
        result = ShortType.v();
      } else if (type.equals("double")) {
        result = DoubleType.v();
      } else if (type.equals("int")) {
        result = IntType.v();
      } else if (type.equals("float")) {
        result = FloatType.v();
      } else if (type.equals("byte")) {
        result = ByteType.v();
      } else if (type.equals("char")) {
        result = CharType.v();
      } else if (type.equals("void")) {
        result = VoidType.v();
      } else if (type.equals("boolean")) {
        result = BooleanType.v();
      } else if (allowsPhantomRefs() && phantomNonExist) {
        getSootClassUnsafe(type, phantomNonExist);
        result = getRefTypeUnsafe(type);
      }
    }

    if (result != null && arrayCount != 0) {
      result = ArrayType.v(result, arrayCount);
    }
    return result;
  }

  /**
   * Returns the RefType with the given className.
   *
   * @throws IllegalStateException
   *           if the RefType for this class cannot be found. Use {@link #containsType(String)} to check if type is
   *           registered
   */
  public RefType getRefType(String className) {
    RefType refType = getRefTypeUnsafe(className);
    if (refType == null) {
      throw new IllegalStateException("RefType " + className + " not loaded. "
          + "If you tried to get the RefType of a library class, did you call loadNecessaryClasses()? "
          + "Otherwise please check Soot's classpath.");
    }
    return refType;
  }

  /**
   * Returns the RefType with the given className. Returns null if no type with the given name can be found.
   */
  public RefType getRefTypeUnsafe(String className) {
    RefType refType = nameToClass.get(className);
    return refType;
  }

  /**
   * Returns the {@link RefType} for {@link Object}.
   */
  public RefType getObjectType() {
    return getRefType("java.lang.Object");
  }

  /**
   * Returns the RefType with the given className.
   */
  public void addRefType(RefType type) {
    nameToClass.put(type.getClassName(), type);
  }

  /**
   * Returns the SootClass with the given className. If no class with the given name exists, null is returned unless phantom
   * refs are allowed. In this case, a new phantom class is created.
   *
   * @param className
   *          The name of the class to get
   * @return The class if it exists, otherwise null
   */
  public SootClass getSootClassUnsafe(String className) {
    return getSootClassUnsafe(className, true);
  }

  /**
   * Returns the SootClass with the given className. If no class with the given name exists, null is returned unless
   * phantomNonExist=true and phantom refs are allowed. In this case, a new phantom class is created and returned.
   * 
   * @param className
   *          The name of the class to get
   * @param phantomNonExist
   *          Indicates that a phantom class should be created if a class with the given name does not exist and phantom refs
   *          are allowed
   * @return The class if it exists, otherwise null
   */
  public SootClass getSootClassUnsafe(String className, boolean phantomNonExist) {
    RefType type = nameToClass.get(className);
    if (type != null) {
      SootClass tsc = type.getSootClass();
      if (tsc != null) {
        return tsc;
      }
    }

    if ((allowsPhantomRefs() && phantomNonExist) || className.equals(SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME)) {
      synchronized (this) {
        // Double check the class has not been created already between last check an synchronize
        type = nameToClass.get(className);
        if (type != null) {
          SootClass tsc = type.getSootClass();
          if (tsc != null) {
            return tsc;
          }
        }
        SootClass c = new SootClass(className);
        c.isPhantom = true;
        addClassSilent(c);
        c.setPhantomClass();
        return c;
      }
    }

    return null;
  }

  /**
   * Returns the SootClass with the given className.
   */
  public SootClass getSootClass(String className) {
    SootClass sc = getSootClassUnsafe(className);
    if (sc != null) {
      return sc;
    }

    throw new RuntimeException(System.getProperty("line.separator") + "Aborting: can't find classfile " + className);
  }

  /**
   * Returns an backed chain of the classes in this manager.
   */

  public Chain getClasses() {
    return classes;
  }

  /* The four following chains are mutually disjoint. */

  /**
   * Returns a chain of the application classes in this scene. These classes are the ones which can be freely analysed &
   * modified.
   */
  public Chain getApplicationClasses() {
    return applicationClasses;
  }

  /**
   * Returns a chain of the library classes in this scene. These classes can be analysed but not modified.
   */
  public Chain getLibraryClasses() {
    return libraryClasses;
  }

  /**
   * Returns a chain of the phantom classes in this scene. These classes are referred to by other classes, but cannot be
   * loaded.
   */
  public Chain getPhantomClasses() {
    return phantomClasses;
  }

  Chain getContainingChain(SootClass c) {
    if (c.isApplicationClass()) {
      return getApplicationClasses();
    } else if (c.isLibraryClass()) {
      return getLibraryClasses();
    } else if (c.isPhantomClass()) {
      return getPhantomClasses();
    }

    return null;
  }

  /****************************************************************************/
  /**
   * Retrieves the active side-effect analysis
   */

  public SideEffectAnalysis getSideEffectAnalysis() {
    if (!hasSideEffectAnalysis()) {
      setSideEffectAnalysis(new SideEffectAnalysis(getPointsToAnalysis(), getCallGraph()));
    }

    return activeSideEffectAnalysis;
  }

  /**
   * Sets the active side-effect analysis
   */

  public void setSideEffectAnalysis(SideEffectAnalysis sea) {
    activeSideEffectAnalysis = sea;
  }

  public boolean hasSideEffectAnalysis() {
    return activeSideEffectAnalysis != null;
  }

  public void releaseSideEffectAnalysis() {
    activeSideEffectAnalysis = null;
  }

  /****************************************************************************/
  /**
   * Retrieves the active pointer analysis
   */

  public PointsToAnalysis getPointsToAnalysis() {
    if (!hasPointsToAnalysis()) {
      return DumbPointerAnalysis.v();
    }

    return activePointsToAnalysis;
  }

  /**
   * Sets the active pointer analysis
   */

  public void setPointsToAnalysis(PointsToAnalysis pa) {
    activePointsToAnalysis = pa;
  }

  public boolean hasPointsToAnalysis() {
    return activePointsToAnalysis != null;
  }

  public void releasePointsToAnalysis() {
    activePointsToAnalysis = null;
  }

  /****************************************************************************/
  /**
   * Retrieves the active client accessibility oracle
   */
  public ClientAccessibilityOracle getClientAccessibilityOracle() {
    if (!hasClientAccessibilityOracle()) {
      return PublicAndProtectedAccessibility.v();
    }

    return accessibilityOracle;
  }

  public boolean hasClientAccessibilityOracle() {
    return accessibilityOracle != null;
  }

  public void setClientAccessibilityOracle(ClientAccessibilityOracle oracle) {
    accessibilityOracle = oracle;
  }

  public void releaseClientAccessibilityOracle() {
    accessibilityOracle = null;
  }

  /****************************************************************************/
  /**
   * Makes a new fast hierarchy is none is active, and returns the active fast hierarchy.
   */
  public FastHierarchy getOrMakeFastHierarchy() {
    if (!hasFastHierarchy()) {
      setFastHierarchy(new FastHierarchy());
    }
    return getFastHierarchy();
  }

  /**
   * Retrieves the active fast hierarchy
   */

  public FastHierarchy getFastHierarchy() {
    if (!hasFastHierarchy()) {
      throw new RuntimeException("no active FastHierarchy present for scene");
    }

    return activeFastHierarchy;
  }

  /**
   * Sets the active hierarchy
   */

  public void setFastHierarchy(FastHierarchy hierarchy) {
    activeFastHierarchy = hierarchy;
  }

  public boolean hasFastHierarchy() {
    return activeFastHierarchy != null;
  }

  public void releaseFastHierarchy() {
    activeFastHierarchy = null;
  }

  /****************************************************************************/
  /**
   * Retrieves the active hierarchy
   */

  public Hierarchy getActiveHierarchy() {
    if (!hasActiveHierarchy()) {
      // throw new RuntimeException("no active Hierarchy present for
      // scene");
      setActiveHierarchy(new Hierarchy());
    }

    return activeHierarchy;
  }

  /**
   * Sets the active hierarchy
   */

  public void setActiveHierarchy(Hierarchy hierarchy) {
    activeHierarchy = hierarchy;
  }

  public boolean hasActiveHierarchy() {
    return activeHierarchy != null;
  }

  public void releaseActiveHierarchy() {
    activeHierarchy = null;
  }

  public boolean hasCustomEntryPoints() {
    return entryPoints != null;
  }

  /** Get the set of entry points that are used to build the call graph. */
  public List getEntryPoints() {
    if (entryPoints == null) {
      entryPoints = EntryPoints.v().all();
    }
    return entryPoints;
  }

  /** Change the set of entry point methods used to build the call graph. */
  public void setEntryPoints(List entryPoints) {
    this.entryPoints = entryPoints;
  }

  private ContextSensitiveCallGraph cscg = null;

  public ContextSensitiveCallGraph getContextSensitiveCallGraph() {
    if (cscg == null) {
      throw new RuntimeException("No context-sensitive call graph present in Scene. You can bulid one with Paddle.");
    }
    return cscg;
  }

  public void setContextSensitiveCallGraph(ContextSensitiveCallGraph cscg) {
    this.cscg = cscg;
  }

  public CallGraph getCallGraph() {
    if (!hasCallGraph()) {
      throw new RuntimeException("No call graph present in Scene. Maybe you want Whole Program mode (-w).");
    }

    return activeCallGraph;
  }

  public void setCallGraph(CallGraph cg) {
    reachableMethods = null;
    activeCallGraph = cg;
  }

  public boolean hasCallGraph() {
    return activeCallGraph != null;
  }

  public void releaseCallGraph() {
    activeCallGraph = null;
    reachableMethods = null;
  }

  public ReachableMethods getReachableMethods() {
    if (reachableMethods == null) {
      reachableMethods = new ReachableMethods(getCallGraph(), new ArrayList(getEntryPoints()));
    }
    reachableMethods.update();
    return reachableMethods;
  }

  public void setReachableMethods(ReachableMethods rm) {
    reachableMethods = rm;
  }

  public boolean hasReachableMethods() {
    return reachableMethods != null;
  }

  public void releaseReachableMethods() {
    reachableMethods = null;
  }

  public boolean getPhantomRefs() {
    // if( !Options.v().allow_phantom_refs() ) return false;
    // return allowsPhantomRefs;
    return Options.v().allow_phantom_refs();
  }

  public void setPhantomRefs(boolean value) {
    allowsPhantomRefs = value;
  }

  public boolean allowsPhantomRefs() {
    return getPhantomRefs();
  }

  public Numberer kindNumberer() {
    return kindNumberer;
  }

  public ArrayNumberer getTypeNumberer() {
    return typeNumberer;
  }

  public ArrayNumberer getMethodNumberer() {
    return methodNumberer;
  }

  public Numberer getContextNumberer() {
    return contextNumberer;
  }

  public Numberer getUnitNumberer() {
    return unitNumberer;
  }

  public Numberer getFieldNumberer() {
    return fieldNumberer;
  }

  public ArrayNumberer getClassNumberer() {
    return classNumberer;
  }

  public StringNumberer getSubSigNumberer() {
    return subSigNumberer;
  }

  public ArrayNumberer getLocalNumberer() {
    return localNumberer;
  }

  public void setContextNumberer(Numberer n) {
    if (contextNumberer != null) {
      throw new RuntimeException("Attempt to set context numberer when it is already set.");
    }
    contextNumberer = n;
  }

  /**
   * Returns the {@link ThrowAnalysis} to be used by default when constructing CFGs which include exceptional control flow.
   *
   * @return the default {@link ThrowAnalysis}
   */
  public ThrowAnalysis getDefaultThrowAnalysis() {
    if (defaultThrowAnalysis == null) {
      int optionsThrowAnalysis = Options.v().throw_analysis();
      switch (optionsThrowAnalysis) {
        case Options.throw_analysis_pedantic:
          defaultThrowAnalysis = PedanticThrowAnalysis.v();
          break;
        case Options.throw_analysis_unit:
          defaultThrowAnalysis = UnitThrowAnalysis.v();
          break;
        case Options.throw_analysis_dalvik:
          defaultThrowAnalysis = DalvikThrowAnalysis.v();
          break;
        default:
          throw new IllegalStateException("Options.v().throw_analysi() == " + Options.v().throw_analysis());
      }
    }
    return defaultThrowAnalysis;
  }

  /**
   * Sets the {@link ThrowAnalysis} to be used by default when constructing CFGs which include exceptional control flow.
   *
   * @param ta
   *          the default {@link ThrowAnalysis}.
   */
  public void setDefaultThrowAnalysis(ThrowAnalysis ta) {
    defaultThrowAnalysis = ta;
  }

  private void setReservedNames() {
    Set rn = getReservedNames();
    rn.add("newarray");
    rn.add("newmultiarray");
    rn.add("nop");
    rn.add("ret");
    rn.add("specialinvoke");
    rn.add("staticinvoke");
    rn.add("tableswitch");
    rn.add("virtualinvoke");
    rn.add("null_type");
    rn.add("unknown");
    rn.add("cmp");
    rn.add("cmpg");
    rn.add("cmpl");
    rn.add("entermonitor");
    rn.add("exitmonitor");
    rn.add("interfaceinvoke");
    rn.add("lengthof");
    rn.add("lookupswitch");
    rn.add("neg");
    rn.add("if");
    rn.add("abstract");
    rn.add("annotation");
    rn.add("boolean");
    rn.add("break");
    rn.add("byte");
    rn.add("case");
    rn.add("catch");
    rn.add("char");
    rn.add("class");
    rn.add("enum");
    rn.add("final");
    rn.add("native");
    rn.add("public");
    rn.add("protected");
    rn.add("private");
    rn.add("static");
    rn.add("synchronized");
    rn.add("transient");
    rn.add("volatile");
    rn.add("interface");
    rn.add("void");
    rn.add("short");
    rn.add("int");
    rn.add("long");
    rn.add("float");
    rn.add("double");
    rn.add("extends");
    rn.add("implements");
    rn.add("breakpoint");
    rn.add("default");
    rn.add("goto");
    rn.add("instanceof");
    rn.add("new");
    rn.add("return");
    rn.add("throw");
    rn.add("throws");
    rn.add("null");
    rn.add("from");
    rn.add("to");
    rn.add("with");
    rn.add("cls");
    rn.add("dynamicinvoke");
    rn.add("strictfp");
  }

  private final Set[] basicclasses = new Set[4];

  private void addSootBasicClasses() {
    basicclasses[SootClass.HIERARCHY] = new HashSet();
    basicclasses[SootClass.SIGNATURES] = new HashSet();
    basicclasses[SootClass.BODIES] = new HashSet();

    addBasicClass("java.lang.Object");
    addBasicClass("java.lang.Class", SootClass.SIGNATURES);

    addBasicClass("java.lang.Void", SootClass.SIGNATURES);
    addBasicClass("java.lang.Boolean", SootClass.SIGNATURES);
    addBasicClass("java.lang.Byte", SootClass.SIGNATURES);
    addBasicClass("java.lang.Character", SootClass.SIGNATURES);
    addBasicClass("java.lang.Short", SootClass.SIGNATURES);
    addBasicClass("java.lang.Integer", SootClass.SIGNATURES);
    addBasicClass("java.lang.Long", SootClass.SIGNATURES);
    addBasicClass("java.lang.Float", SootClass.SIGNATURES);
    addBasicClass("java.lang.Double", SootClass.SIGNATURES);

    addBasicClass("java.lang.String");
    addBasicClass("java.lang.StringBuffer", SootClass.SIGNATURES);

    addBasicClass("java.lang.Error");
    addBasicClass("java.lang.AssertionError", SootClass.SIGNATURES);
    addBasicClass("java.lang.Throwable", SootClass.SIGNATURES);
    addBasicClass("java.lang.Exception", SootClass.SIGNATURES);
    addBasicClass("java.lang.NoClassDefFoundError", SootClass.SIGNATURES);
    addBasicClass("java.lang.ExceptionInInitializerError");
    addBasicClass("java.lang.RuntimeException");
    addBasicClass("java.lang.ClassNotFoundException");
    addBasicClass("java.lang.ArithmeticException");
    addBasicClass("java.lang.ArrayStoreException");
    addBasicClass("java.lang.ClassCastException");
    addBasicClass("java.lang.IllegalMonitorStateException");
    addBasicClass("java.lang.IndexOutOfBoundsException");
    addBasicClass("java.lang.ArrayIndexOutOfBoundsException");
    addBasicClass("java.lang.NegativeArraySizeException");
    addBasicClass("java.lang.NullPointerException", SootClass.SIGNATURES);
    addBasicClass("java.lang.InstantiationError");
    addBasicClass("java.lang.InternalError");
    addBasicClass("java.lang.OutOfMemoryError");
    addBasicClass("java.lang.StackOverflowError");
    addBasicClass("java.lang.UnknownError");
    addBasicClass("java.lang.ThreadDeath");
    addBasicClass("java.lang.ClassCircularityError");
    addBasicClass("java.lang.ClassFormatError");
    addBasicClass("java.lang.IllegalAccessError");
    addBasicClass("java.lang.IncompatibleClassChangeError");
    addBasicClass("java.lang.LinkageError");
    addBasicClass("java.lang.VerifyError");
    addBasicClass("java.lang.NoSuchFieldError");
    addBasicClass("java.lang.AbstractMethodError");
    addBasicClass("java.lang.NoSuchMethodError");
    addBasicClass("java.lang.UnsatisfiedLinkError");

    addBasicClass("java.lang.Thread");
    addBasicClass("java.lang.Runnable");
    addBasicClass("java.lang.Cloneable");

    addBasicClass("java.io.Serializable");

    addBasicClass("java.lang.ref.Finalizer");

    addBasicClass("java.lang.invoke.LambdaMetafactory");
  }

  public void addBasicClass(String name) {
    addBasicClass(name, SootClass.HIERARCHY);
  }

  public void addBasicClass(String name, int level) {
    basicclasses[level].add(name);
  }

  /**
   * Load just the set of basic classes soot needs, ignoring those specified on the command-line. You don't need to use both
   * this and loadNecessaryClasses, though it will only waste time.
   */
  public void loadBasicClasses() {
    addReflectionTraceClasses();

    for (int i = SootClass.BODIES; i >= SootClass.HIERARCHY; i--) {
      for (String name : basicclasses[i]) {
        tryLoadClass(name, i);
      }
    }
  }

  public Set getBasicClasses() {
    Set all = new HashSet();
    for (int i = 0; i < basicclasses.length; i++) {
      Set classes = basicclasses[i];
      if (classes != null) {
        all.addAll(classes);
      }
    }
    return all;
  }

  private void addReflectionTraceClasses() {
    CGOptions options = new CGOptions(PhaseOptions.v().getPhaseOptions("cg"));
    String log = options.reflection_log();

    Set classNames = new HashSet();
    if (log != null && log.length() > 0) {
      BufferedReader reader = null;
      String line = "";
      try {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(log)));
        while ((line = reader.readLine()) != null) {
          if (line.length() == 0) {
            continue;
          }
          String[] portions = line.split(";", -1);
          String kind = portions[0];
          String target = portions[1];
          String source = portions[2];
          String sourceClassName = source.substring(0, source.lastIndexOf("."));
          classNames.add(sourceClassName);
          if (kind.equals("Class.forName")) {
            classNames.add(target);
          } else if (kind.equals("Class.newInstance")) {
            classNames.add(target);
          } else if (kind.equals("Method.invoke") || kind.equals("Constructor.newInstance")) {
            classNames.add(signatureToClass(target));
          } else if (kind.equals("Field.set*") || kind.equals("Field.get*")) {
            classNames.add(signatureToClass(target));
          } else {
            throw new RuntimeException("Unknown entry kind: " + kind);
          }
        }
      } catch (Exception e) {
        throw new RuntimeException("Line: '" + line + "'", e);
      } finally {
        if (reader != null) {
          try {
            reader.close();
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }
    }

    for (String c : classNames) {
      addBasicClass(c, SootClass.BODIES);
    }
  }

  private List dynamicClasses = null;

  public Collection dynamicClasses() {
    if (dynamicClasses == null) {
      throw new IllegalStateException("Have to call loadDynamicClasses() first!");
    }
    return dynamicClasses;
  }

  private void loadNecessaryClass(String name) {
    SootClass c;
    c = loadClassAndSupport(name);
    c.setApplicationClass();
  }

  /**
   * Load the set of classes that soot needs, including those specified on the command-line. This is the standard way of
   * initialising the list of classes soot should use.
   */
  public void loadNecessaryClasses() {
    loadBasicClasses();

    for (String name : Options.v().classes()) {
      loadNecessaryClass(name);
    }

    loadDynamicClasses();

    if (Options.v().oaat()) {
      if (Options.v().process_dir().isEmpty()) {
        throw new IllegalArgumentException("If switch -oaat is used, then also -process-dir must be given.");
      }
    } else {
      for (final String path : Options.v().process_dir()) {
        for (String cl : SourceLocator.v().getClassesUnder(path)) {
          SootClass theClass = loadClassAndSupport(cl);
          if (!theClass.isPhantom) {
            theClass.setApplicationClass();
          }
        }
      }
    }

    prepareClasses();
    setDoneResolving();
  }

  public void loadDynamicClasses() {
    dynamicClasses = new ArrayList();
    HashSet dynClasses = new HashSet();
    dynClasses.addAll(Options.v().dynamic_class());

    for (Iterator pathIt = Options.v().dynamic_dir().iterator(); pathIt.hasNext();) {

      final String path = pathIt.next();
      dynClasses.addAll(SourceLocator.v().getClassesUnder(path));
    }

    for (Iterator pkgIt = Options.v().dynamic_package().iterator(); pkgIt.hasNext();) {

      final String pkg = pkgIt.next();
      dynClasses.addAll(SourceLocator.v().classesInDynamicPackage(pkg));
    }

    for (String className : dynClasses) {

      dynamicClasses.add(loadClassAndSupport(className));
    }

    // remove non-concrete classes that may accidentally have been loaded
    for (Iterator iterator = dynamicClasses.iterator(); iterator.hasNext();) {
      SootClass c = iterator.next();
      if (!c.isConcrete()) {
        if (Options.v().verbose()) {
          logger.warn("dynamic class " + c.getName() + " is abstract or an interface, and it will not be considered.");
        }
        iterator.remove();
      }
    }
  }

  /*
   * Generate classes to process, adding or removing package marked by command line options.
   */
  private void prepareClasses() {
    // Remove/add all classes from packageInclusionMask as per -i option
    Chain processedClasses = new HashChain();
    while (true) {
      Chain unprocessedClasses = new HashChain(getClasses());
      unprocessedClasses.removeAll(processedClasses);
      if (unprocessedClasses.isEmpty()) {
        break;
      }
      processedClasses.addAll(unprocessedClasses);
      for (SootClass s : unprocessedClasses) {
        if (s.isPhantom()) {
          continue;
        }
        if (Options.v().app()) {
          s.setApplicationClass();
        }
        if (Options.v().classes().contains(s.getName())) {
          s.setApplicationClass();
          continue;
        }
        if (s.isApplicationClass() && isExcluded(s)) {
          s.setLibraryClass();
        }
        if (isIncluded(s)) {
          s.setApplicationClass();
        }
        if (s.isApplicationClass()) {
          // make sure we have the support
          loadClassAndSupport(s.getName());
        }
      }
    }
  }

  public boolean isExcluded(SootClass sc) {
    String name = sc.getName();
    for (String pkg : excludedPackages) {
      if (name.equals(pkg)
          || ((pkg.endsWith(".*") || pkg.endsWith("$*")) && name.startsWith(pkg.substring(0, pkg.length() - 1)))) {
        return !isIncluded(sc);
      }
    }
    return false;
  }

  public boolean isIncluded(SootClass sc) {
    String name = sc.getName();
    for (String inc : Options.v().include()) {
      if (name.equals(inc)
          || ((inc.endsWith(".*") || inc.endsWith("$*")) && name.startsWith(inc.substring(0, inc.length() - 1)))) {
        return true;
      }
    }
    return false;
  }

  List pkgList;

  public void setPkgList(List list) {
    pkgList = list;
  }

  public List getPkgList() {
    return pkgList;
  }

  /** Create an unresolved reference to a method. */
  public SootMethodRef makeMethodRef(SootClass declaringClass, String name, List parameterTypes, Type returnType,
      boolean isStatic) {
    if (PolymorphicMethodRef.handlesClass(declaringClass)) {
      return new PolymorphicMethodRef(declaringClass, name, parameterTypes, returnType, isStatic);
    }
    return new SootMethodRefImpl(declaringClass, name, parameterTypes, returnType, isStatic);
  }

  /** Create an unresolved reference to a constructor. */
  public SootMethodRef makeConstructorRef(SootClass declaringClass, List parameterTypes) {
    return makeMethodRef(declaringClass, SootMethod.constructorName, parameterTypes, VoidType.v(), false);
  }

  /** Create an unresolved reference to a field. */
  public SootFieldRef makeFieldRef(SootClass declaringClass, String name, Type type, boolean isStatic) {
    return new AbstractSootFieldRef(declaringClass, name, type, isStatic);
  }

  /**
   * Returns the list of SootClasses that have been resolved at least to the level specified.
   */
  public List getClasses(int desiredLevel) {
    List ret = new ArrayList();
    for (Iterator clIt = getClasses().iterator(); clIt.hasNext();) {
      final SootClass cl = clIt.next();
      if (cl.resolvingLevel() >= desiredLevel) {
        ret.add(cl);
      }
    }
    return ret;
  }

  private boolean doneResolving = false;
  private boolean incrementalBuild;
  protected LinkedList excludedPackages;

  public boolean doneResolving() {
    return doneResolving;
  }

  public void setDoneResolving() {
    doneResolving = true;
  }

  public void setMainClassFromOptions() {
    if (mainClass != null) {
      return;
    }
    if (Options.v().main_class() != null && Options.v().main_class().length() > 0) {
      setMainClass(getSootClass(Options.v().main_class()));
    } else {
      // try to infer a main class from the command line if none is given
      for (Iterator classIter = Options.v().classes().iterator(); classIter.hasNext();) {
        SootClass c = getSootClass(classIter.next());
        if (c.declaresMethod("main", Collections.singletonList(ArrayType.v(RefType.v("java.lang.String"), 1)),
            VoidType.v())) {
          logger.debug("No main class given. Inferred '" + c.getName() + "' as main class.");
          setMainClass(c);
          return;
        }
      }

      // try to infer a main class from the usual classpath if none is
      // given
      for (Iterator classIter = getApplicationClasses().iterator(); classIter.hasNext();) {
        SootClass c = classIter.next();
        if (c.declaresMethod("main", Collections.singletonList(ArrayType.v(RefType.v("java.lang.String"), 1)),
            VoidType.v())) {
          logger.debug("No main class given. Inferred '" + c.getName() + "' as main class.");
          setMainClass(c);
          return;
        }
      }
    }
  }

  /**
   * This method returns true when in incremental build mode. Other classes can query this flag and change the way in which
   * they use the Scene, depending on the flag's value.
   */
  public boolean isIncrementalBuild() {
    return incrementalBuild;
  }

  public void initiateIncrementalBuild() {
    this.incrementalBuild = true;
  }

  public void incrementalBuildFinished() {
    this.incrementalBuild = false;
  }

  /*
   * Forces Soot to resolve the class with the given name to the given level, even if resolving has actually already
   * finished.
   */
  public SootClass forceResolve(String className, int level) {
    boolean tmp = doneResolving;
    doneResolving = false;
    SootClass c;
    try {
      c = SootResolver.v().resolveClass(className, level);
    } finally {
      doneResolving = tmp;
    }
    return c;
  }

  public SootClass makeSootClass(String name) {
    return new SootClass(name);
  }

  public SootClass makeSootClass(String name, int modifiers) {
    return new SootClass(name, modifiers);
  }

  public SootMethod makeSootMethod(String name, List parameterTypes, Type returnType) {
    return new SootMethod(name, parameterTypes, returnType);
  }

  public SootMethod makeSootMethod(String name, List parameterTypes, Type returnType, int modifiers) {
    return new SootMethod(name, parameterTypes, returnType, modifiers);
  }

  public SootMethod makeSootMethod(String name, List parameterTypes, Type returnType, int modifiers,
      List thrownExceptions) {
    return new SootMethod(name, parameterTypes, returnType, modifiers, thrownExceptions);
  }

  public SootField makeSootField(String name, Type type, int modifiers) {
    return new SootField(name, type, modifiers);
  }

  public SootField makeSootField(String name, Type type) {
    return new SootField(name, type);
  }

  public RefType getOrAddRefType(RefType tp) {
    RefType existing = nameToClass.get(tp.getClassName());
    if (existing != null) {
      return existing;
    }
    nameToClass.put(tp.getClassName(), tp);
    return tp;
  }

  /**
   * 

* SOOT USERS: DO NOT CALL THIS METHOD! *

* *

* This method is a Soot-internal factory method for generating callgraph objects. It creates non-initialized object that * must then be initialized by a callgraph algorithm *

* * @return A new callgraph empty object */ public CallGraph internalMakeCallGraph() { return new CallGraph(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy