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

soot.Hierarchy Maven / Gradle / Ivy

package soot;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 - 1999 Raja Vallee-Rai
 * %%
 * 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import soot.jimple.SpecialInvokeExpr;
import soot.util.ArraySet;
import soot.util.Chain;

/**
 * Represents the class hierarchy. It is closely linked to a Scene, and must be recreated if the Scene changes.
 *
 * The general convention is that if a method name contains "Including", then it returns the non-strict result; otherwise, it
 * does a strict query (e.g. strict superclass).
 */
public class Hierarchy {
  // These two maps are not filled in the constructor.
  protected Map> classToSubclasses;
  protected Map> interfaceToSubinterfaces;
  protected Map> interfaceToSuperinterfaces;

  protected Map> classToDirSubclasses;
  protected Map> interfaceToDirSubinterfaces;
  protected Map> interfaceToDirSuperinterfaces;

  // This holds the direct implementers.
  protected Map> interfaceToDirImplementers;

  int state;
  Scene sc;

  /** Constructs a hierarchy from the current scene. */
  public Hierarchy() {
    this.sc = Scene.v();
    state = sc.getState();

    // Well, this used to be describable by 'Duh'.
    // Construct the subclasses hierarchy and the subinterfaces hierarchy.
    {
      Chain allClasses = sc.getClasses();

      classToSubclasses = new HashMap>(allClasses.size() * 2 + 1, 0.7f);
      interfaceToSubinterfaces = new HashMap>(allClasses.size() * 2 + 1, 0.7f);
      interfaceToSuperinterfaces = new HashMap>(allClasses.size() * 2 + 1, 0.7f);

      classToDirSubclasses = new HashMap>(allClasses.size() * 2 + 1, 0.7f);
      interfaceToDirSubinterfaces = new HashMap>(allClasses.size() * 2 + 1, 0.7f);
      interfaceToDirSuperinterfaces = new HashMap>(allClasses.size() * 2 + 1, 0.7f);
      interfaceToDirImplementers = new HashMap>(allClasses.size() * 2 + 1, 0.7f);

      initializeHierarchy(allClasses);
    }
  }

  /**
   * Initializes the hierarchy given a chain of all classes that shall be included in the hierarchy
   *
   * @param allClasses
   *          The chain of all classes to be included in the hierarchy
   */
  protected void initializeHierarchy(Chain allClasses) {
    for (SootClass c : allClasses) {
      if (c.resolvingLevel() < SootClass.HIERARCHY) {
        continue;
      }

      if (c.isInterface()) {
        interfaceToDirSubinterfaces.put(c, new ArrayList());
        interfaceToDirSuperinterfaces.put(c, new ArrayList());
        interfaceToDirImplementers.put(c, new ArrayList());
      } else {
        classToDirSubclasses.put(c, new ArrayList());
      }
    }

    for (SootClass c : allClasses) {
      if (c.resolvingLevel() < SootClass.HIERARCHY) {
        continue;
      }

      if (c.hasSuperclass()) {
        if (c.isInterface()) {
          List l2 = interfaceToDirSuperinterfaces.get(c);
          for (SootClass i : c.getInterfaces()) {
            if (c.resolvingLevel() < SootClass.HIERARCHY) {
              continue;
            }
            List l = interfaceToDirSubinterfaces.get(i);
            if (l != null) {
              l.add(c);
            }
            if (l2 != null) {
              l2.add(i);
            }
          }
        } else {
          List l = classToDirSubclasses.get(c.getSuperclass());
          if (l != null) {
            l.add(c);
          }

          for (SootClass i : c.getInterfaces()) {
            if (c.resolvingLevel() < SootClass.HIERARCHY) {
              continue;
            }
            l = interfaceToDirImplementers.get(i);
            if (l != null) {
              l.add(c);
            }
          }
        }
      }
    }

    // Fill the directImplementers lists with subclasses.
    for (SootClass c : allClasses) {
      if (c.resolvingLevel() < SootClass.HIERARCHY) {
        continue;
      }
      if (c.isInterface()) {
        List imp = interfaceToDirImplementers.get(c);
        Set s = new ArraySet();
        for (SootClass c0 : imp) {
          if (c.resolvingLevel() < SootClass.HIERARCHY) {
            continue;
          }
          s.addAll(getSubclassesOfIncluding(c0));
        }
        imp.clear();
        imp.addAll(s);
      }
    }
  }

  protected void checkState() {
    if (state != sc.getState()) {
      throw new ConcurrentModificationException("Scene changed for Hierarchy!");
    }
  }

  // This includes c in the list of subclasses.
  /** Returns a list of subclasses of c, including itself. */
  public List getSubclassesOfIncluding(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (c.isInterface()) {
      throw new RuntimeException("class needed!");
    }

    List l = new ArrayList();
    l.addAll(getSubclassesOf(c));
    l.add(c);

    return Collections.unmodifiableList(l);
  }

  /** Returns a list of subclasses of c, excluding itself. */
  public List getSubclassesOf(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (c.isInterface()) {
      throw new RuntimeException("class needed!");
    }

    checkState();

    // If already cached, return the value.
    if (classToSubclasses.get(c) != null) {
      return classToSubclasses.get(c);
    }

    // Otherwise, build up the hashmap.
    List l = new ArrayList();

    for (SootClass cls : classToDirSubclasses.get(c)) {
      if (cls.resolvingLevel() < SootClass.HIERARCHY) {
        continue;
      }
      l.addAll(getSubclassesOfIncluding(cls));
    }

    l = Collections.unmodifiableList(l);
    classToSubclasses.put(c, l);

    return l;
  }

  /**
   * Returns a list of superclasses of {@code sootClass}, including itself.
   *
   * @param sootClass
   *          the class of which superclasses will be taken. Must not be {@code null} or interface
   * @return list of superclasses, including itself
   * @throws IllegalArgumentException
   *           when passed class is an interface
   * @throws NullPointerException
   *           when passed argument is {@code null}
   */
  public List getSuperclassesOfIncluding(SootClass sootClass) {
    final List superclasses = getSuperclassesOf(sootClass);

    final List result = new ArrayList<>(superclasses.size() + 1);
    result.add(sootClass);
    result.addAll(superclasses);

    return Collections.unmodifiableList(result);
  }

  /**
   * Returns a list of direct superclasses of passed class in reverse order, starting with its parent.
   *
   * @param sootClass
   *          the class of which superclasses will be taken. Must not be {@code null} or interface
   * @return list of superclasses
   * @throws IllegalArgumentException
   *           when passed class is an interface
   * @throws NullPointerException
   *           when passed argument is {@code null}
   */
  public List getSuperclassesOf(SootClass sootClass) {
    sootClass.checkLevel(SootClass.HIERARCHY);
    if (sootClass.isInterface()) {
      throw new IllegalArgumentException(sootClass.getName() + " is an interface, but class is expected");
    }

    checkState();

    final List superclasses = new ArrayList<>();

    SootClass current = sootClass;
    while (current.hasSuperclass()) {
      superclasses.add(current.getSuperclass());
      current = current.getSuperclass();
    }

    return Collections.unmodifiableList(superclasses);
  }

  /**
   * Returns a list of subinterfaces of sootClass, including itself.
   *
   * @param sootClass
   *          the interface of which subinterfaces will be taken. Must not be {@code null} or class
   * @return list of subinterfaces, including passed one
   * @throws IllegalArgumentException
   *           when passed class is a class
   * @throws NullPointerException
   *           when passed argument is {@code null}
   */
  public List getSubinterfacesOfIncluding(SootClass sootClass) {
    final List result = new ArrayList<>(getSubinterfacesOf(sootClass));
    result.add(sootClass);

    return Collections.unmodifiableList(result);
  }

  /**
   * Returns a list of subinterfaces of sootClass, excluding itself.
   *
   * @param sootClass
   *          the interface of which subinterfaces will be taken. Must not be {@code null} or class
   * @return list of subinterfaces, including passed one
   * @throws IllegalArgumentException
   *           when passed sootClass is a class
   * @throws NullPointerException
   *           when passed argument is {@code null}
   */
  public List getSubinterfacesOf(SootClass sootClass) {
    sootClass.checkLevel(SootClass.HIERARCHY);
    if (!sootClass.isInterface()) {
      throw new IllegalArgumentException(sootClass.getName() + " is a class, but interface is expected");
    }

    checkState();

    // If already cached, return the value.
    List subInterfaces = interfaceToSubinterfaces.get(sootClass);
    if (subInterfaces != null) {
      return subInterfaces;
    }

    // Otherwise, build up the hashmap.
    List directSubInterfaces = interfaceToDirSubinterfaces.get(sootClass);
    if (directSubInterfaces == null || directSubInterfaces.isEmpty()) {
      return Collections.emptyList();
    }
    
    final List result = new ArrayList<>();
    for (SootClass si : directSubInterfaces) {
      result.addAll(getSubinterfacesOfIncluding(si));
    }

    final List unmodifiableResult = Collections.unmodifiableList(result);

    interfaceToSubinterfaces.put(sootClass, unmodifiableResult);

    return unmodifiableResult;
  }

  /** Returns a list of superinterfaces of c, including itself. */
  public List getSuperinterfacesOfIncluding(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (!c.isInterface()) {
      throw new RuntimeException("interface needed!");
    }

    List l = new ArrayList();
    l.addAll(getSuperinterfacesOf(c));
    l.add(c);

    return Collections.unmodifiableList(l);
  }

  /** Returns a list of superinterfaces of c, excluding itself. */
  public List getSuperinterfacesOf(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (!c.isInterface()) {
      throw new RuntimeException("interface needed!");
    }

    checkState();

    // If already cached, return the value.
    List cached = interfaceToSuperinterfaces.get(c);
    if (cached != null) {
      return cached;
    }

    // Otherwise, build up the hashmap.
    List l = new ArrayList();

    for (SootClass si : interfaceToDirSuperinterfaces.get(c)) {
      l.addAll(getSuperinterfacesOfIncluding(si));
    }

    interfaceToSuperinterfaces.put(c, Collections.unmodifiableList(l));

    return Collections.unmodifiableList(l);
  }

  /** Returns a list of direct superclasses of c, excluding c. */
  public List getDirectSuperclassesOf(SootClass c) {
    throw new RuntimeException("Not implemented yet!");
  }

  /** Returns a list of direct subclasses of c, excluding c. */
  public List getDirectSubclassesOf(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (c.isInterface()) {
      throw new RuntimeException("class needed!");
    }

    checkState();

    return Collections.unmodifiableList(classToDirSubclasses.get(c));
  }

  // This includes c in the list of subclasses.
  /** Returns a list of direct subclasses of c, including c. */
  public List getDirectSubclassesOfIncluding(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (c.isInterface()) {
      throw new RuntimeException("class needed!");
    }

    checkState();

    List l = new ArrayList();
    l.addAll(classToDirSubclasses.get(c));
    l.add(c);

    return Collections.unmodifiableList(l);
  }

  /** Returns a list of direct superinterfaces of c. */
  public List getDirectSuperinterfacesOf(SootClass c) {
    throw new RuntimeException("Not implemented yet!");
  }

  /** Returns a list of direct subinterfaces of c. */
  public List getDirectSubinterfacesOf(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (!c.isInterface()) {
      throw new RuntimeException("interface needed!");
    }

    checkState();

    return interfaceToDirSubinterfaces.get(c);
  }

  /** Returns a list of direct subinterfaces of c, including itself. */
  public List getDirectSubinterfacesOfIncluding(SootClass c) {
    c.checkLevel(SootClass.HIERARCHY);
    if (!c.isInterface()) {
      throw new RuntimeException("interface needed!");
    }

    checkState();

    List l = new ArrayList();
    l.addAll(interfaceToDirSubinterfaces.get(c));
    l.add(c);

    return Collections.unmodifiableList(l);
  }

  /** Returns a list of direct implementers of c, excluding itself. */
  public List getDirectImplementersOf(SootClass i) {
    i.checkLevel(SootClass.HIERARCHY);
    if (!i.isInterface()) {
      throw new RuntimeException("interface needed; got " + i);
    }

    checkState();

    return Collections.unmodifiableList(interfaceToDirImplementers.get(i));
  }

  /** Returns a list of implementers of c, excluding itself. */
  public List getImplementersOf(SootClass i) {
    i.checkLevel(SootClass.HIERARCHY);
    if (!i.isInterface()) {
      throw new RuntimeException("interface needed; got " + i);
    }

    checkState();

    ArraySet set = new ArraySet();
    for (SootClass c : getSubinterfacesOfIncluding(i)) {
      set.addAll(getDirectImplementersOf(c));
    }

    ArrayList l = new ArrayList();
    l.addAll(set);

    return Collections.unmodifiableList(l);
  }

  /**
   * Returns true if child is a subclass of possibleParent. If one of the known parent classes is phantom, we conservatively
   * assume that the current class might be a child.
   */
  public boolean isClassSubclassOf(SootClass child, SootClass possibleParent) {
    child.checkLevel(SootClass.HIERARCHY);
    possibleParent.checkLevel(SootClass.HIERARCHY);
    List parentClasses = getSuperclassesOf(child);
    if (parentClasses.contains(possibleParent)) {
      return true;
    }

    for (SootClass sc : parentClasses) {
      if (sc.isPhantom()) {
        return true;
      }
    }

    return false;
  }

  /**
   * Returns true if child is, or is a subclass of, possibleParent. If one of the known parent classes is phantom, we
   * conservatively assume that the current class might be a child.
   */
  public boolean isClassSubclassOfIncluding(SootClass child, SootClass possibleParent) {
    child.checkLevel(SootClass.HIERARCHY);
    possibleParent.checkLevel(SootClass.HIERARCHY);
    List parentClasses = getSuperclassesOfIncluding(child);
    if (parentClasses.contains(possibleParent)) {
      return true;
    }

    for (SootClass sc : parentClasses) {
      if (sc.isPhantom()) {
        return true;
      }
    }

    return false;
  }

  /** Returns true if child is a direct subclass of possibleParent. */
  public boolean isClassDirectSubclassOf(SootClass c, SootClass c2) {
    throw new RuntimeException("Not implemented yet!");
  }

  /** Returns true if child is a superclass of possibleParent. */
  public boolean isClassSuperclassOf(SootClass parent, SootClass possibleChild) {
    parent.checkLevel(SootClass.HIERARCHY);
    possibleChild.checkLevel(SootClass.HIERARCHY);
    return getSubclassesOf(parent).contains(possibleChild);
  }

  /** Returns true if parent is, or is a superclass of, possibleChild. */
  public boolean isClassSuperclassOfIncluding(SootClass parent, SootClass possibleChild) {
    parent.checkLevel(SootClass.HIERARCHY);
    possibleChild.checkLevel(SootClass.HIERARCHY);
    return getSubclassesOfIncluding(parent).contains(possibleChild);
  }

  /** Returns true if child is a subinterface of possibleParent. */
  public boolean isInterfaceSubinterfaceOf(SootClass child, SootClass possibleParent) {
    child.checkLevel(SootClass.HIERARCHY);
    possibleParent.checkLevel(SootClass.HIERARCHY);
    return getSubinterfacesOf(possibleParent).contains(child);
  }

  /** Returns true if child is a direct subinterface of possibleParent. */
  public boolean isInterfaceDirectSubinterfaceOf(SootClass child, SootClass possibleParent) {
    child.checkLevel(SootClass.HIERARCHY);
    possibleParent.checkLevel(SootClass.HIERARCHY);
    return getDirectSubinterfacesOf(possibleParent).contains(child);
  }

  /** Returns true if parent is a superinterface of possibleChild. */
  public boolean isInterfaceSuperinterfaceOf(SootClass parent, SootClass possibleChild) {
    parent.checkLevel(SootClass.HIERARCHY);
    possibleChild.checkLevel(SootClass.HIERARCHY);
    return getSuperinterfacesOf(possibleChild).contains(parent);
  }

  /** Returns true if parent is a direct superinterface of possibleChild. */
  public boolean isInterfaceDirectSuperinterfaceOf(SootClass parent, SootClass possibleChild) {
    parent.checkLevel(SootClass.HIERARCHY);
    possibleChild.checkLevel(SootClass.HIERARCHY);
    return getDirectSuperinterfacesOf(possibleChild).contains(parent);
  }

  /**
   * Returns the most specific type which is an ancestor of both c1 and c2.
   */
  public SootClass getLeastCommonSuperclassOf(SootClass c1, SootClass c2) {
    c1.checkLevel(SootClass.HIERARCHY);
    c2.checkLevel(SootClass.HIERARCHY);
    throw new RuntimeException("Not implemented yet!");
  }

  // Questions about method invocation.

  /**
   * Checks whether check is a visible class in view of the from class. It assumes that protected and private classes do not
   * exit. If they exist and check is either protected or private, the check will return false.
   */
  public boolean isVisible(SootClass from, SootClass check) {
    if (check.isPublic()) {
      return true;
    }

    if (check.isProtected() || check.isPrivate()) {
      return false;
    }

    // package visibility
    return from.getJavaPackageName().equals(check.getJavaPackageName());
  }

  /**
   * Returns true if the classmember m is visible from code in the class from.
   */
  public boolean isVisible(SootClass from, ClassMember m) {
    from.checkLevel(SootClass.HIERARCHY);
    m.getDeclaringClass().checkLevel(SootClass.HIERARCHY);

    if (!isVisible(from, m.getDeclaringClass())) {
      return false;
    }

    if (m.isPublic()) {
      return true;
    }
    if (m.isPrivate()) {
      return from.equals(m.getDeclaringClass());
    }
    if (m.isProtected()) {
      return isClassSubclassOfIncluding(from, m.getDeclaringClass())
          || from.getJavaPackageName().equals(m.getDeclaringClass().getJavaPackageName());
    }
    // m is package
    return from.getJavaPackageName().equals(m.getDeclaringClass().getJavaPackageName());
  }

  /**
   * Given an object of actual type C (o = new C()), returns the method which will be called on an o.f() invocation.
   */
  public SootMethod resolveConcreteDispatch(SootClass concreteType, SootMethod m) {
    concreteType.checkLevel(SootClass.HIERARCHY);
    m.getDeclaringClass().checkLevel(SootClass.HIERARCHY);
    checkState();

    if (concreteType.isInterface()) {
      throw new RuntimeException("class needed!");
    }

    String methodSig = m.getSubSignature();

    for (SootClass c : getSuperclassesOfIncluding(concreteType)) {
      SootMethod sm = c.getMethodUnsafe(methodSig);
      if (sm != null && isVisible(c, m)) {
        return sm;
      }
    }
    throw new RuntimeException("could not resolve concrete dispatch!\nType: " + concreteType + "\nMethod: " + m);
  }

  /**
   * Given a set of definite receiver types, returns a list of possible targets.
   */
  public List resolveConcreteDispatch(List classes, SootMethod m) {
    m.getDeclaringClass().checkLevel(SootClass.HIERARCHY);
    checkState();

    Set s = new ArraySet();
    for (Type cls : classes) {
      if (cls instanceof RefType) {
        s.add(resolveConcreteDispatch(((RefType) cls).getSootClass(), m));
      } else if (cls instanceof ArrayType) {
        s.add(resolveConcreteDispatch((RefType.v("java.lang.Object")).getSootClass(), m));
      } else {
        throw new RuntimeException("Unable to resolve concrete dispatch of type " + cls);
      }
    }

    return Collections.unmodifiableList(new ArrayList(s));
  }

  // what can get called for c & all its subclasses
  /**
   * Given an abstract dispatch to an object of type c and a method m, gives a list of possible receiver methods.
   */
  public List resolveAbstractDispatch(SootClass c, SootMethod m) {
    c.checkLevel(SootClass.HIERARCHY);
    m.getDeclaringClass().checkLevel(SootClass.HIERARCHY);
    checkState();

    Set s = new ArraySet();
    Collection classesIt;

    if (c.isInterface()) {
      Set classes = new HashSet();
      for (SootClass sootClass : getImplementersOf(c)) {
        classes.addAll(getSubclassesOfIncluding(sootClass));
      }
      classesIt = classes;
    } else {
      classesIt = getSubclassesOfIncluding(c);
    }

    for (SootClass cl : classesIt) {
      if (!Modifier.isAbstract(cl.getModifiers())) {
        s.add(resolveConcreteDispatch(cl, m));
      }
    }

    return Collections.unmodifiableList(new ArrayList(s));
  }

  // what can get called if you have a set of possible receiver types
  /**
   * Returns a list of possible targets for the given method and set of receiver types.
   */
  public List resolveAbstractDispatch(List classes, SootMethod m) {
    m.getDeclaringClass().checkLevel(SootClass.HIERARCHY);
    Set s = new ArraySet();
    for (SootClass sootClass : classes) {
      s.addAll(resolveAbstractDispatch(sootClass, m));
    }

    return Collections.unmodifiableList(new ArrayList(s));
  }

  /** Returns the target for the given SpecialInvokeExpr. */
  public SootMethod resolveSpecialDispatch(SpecialInvokeExpr ie, SootMethod container) {
    container.getDeclaringClass().checkLevel(SootClass.HIERARCHY);
    SootMethod target = ie.getMethod();
    target.getDeclaringClass().checkLevel(SootClass.HIERARCHY);

    /*
     * This is a bizarre condition! Hopefully the implementation is correct. See VM Spec, 2nd Edition, Chapter 6, in the
     * definition of invokespecial.
     */
    if ("".equals(target.getName()) || target.isPrivate()) {
      return target;
    } else if (isClassSubclassOf(target.getDeclaringClass(), container.getDeclaringClass())) {
      return resolveConcreteDispatch(container.getDeclaringClass(), target);
    } else {
      return target;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy