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

proguard.analysis.CallUtil Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
package proguard.analysis;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import proguard.classfile.AccessConstants;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.MethodSignature;
import proguard.classfile.constant.AnyMethodrefConstant;

/** Utility methods for call resolution. */
public class CallUtil {
  private CallUtil() {}

  /**
   * The invokevirtual and invokeinterface resolution algorithm, annotated
   * with JVM
   * spec §6.5.invokevirtual citations where appropriate, so that the specified lookup process
   * can easily be compared to this implementation.
   *
   * @param callingClass JVM spec: "current class".
   * @param thisPointerType The type of the this pointer of the call (JVM spec:
   *     "objectref").
   * @param ref The {@link AnyMethodrefConstant} specifying name and descriptor of the method to be
   *     invoked.
   * @return The fully qualified names of potential call target clases (usually just one, but see
   *     {@link #resolveFromSuperinterfaces(Clazz, String, String)} for details on when there might
   *     be multiple).
   */
  public static Set resolveVirtual(
      Clazz callingClass, Clazz thisPointerType, AnyMethodrefConstant ref) {
    return resolveVirtual(thisPointerType, ref.getName(callingClass), ref.getType(callingClass));
  }

  /**
   * The invokevirtual and invokeinterface resolution algorithm, annotated
   * with JVM
   * spec §6.5.invokevirtual citations where appropriate, so that the specified lookup process
   * can easily be compared to this implementation.
   *
   * @param thisPointerType The type of the this pointer of the call (JVM spec:
   *     "objectref").
   * @param methodName The name of the invoked method.
   * @param descriptor The descriptor of the invoked method.
   * @return The fully qualified names of potential call target clases (usually just one, but see
   *     {@link #resolveFromSuperinterfaces(Clazz, String, String)} for details on when there might
   *     be multiple).
   */
  public static Set resolveVirtual(
      Clazz thisPointerType, String methodName, String descriptor) {
    if (thisPointerType == null) {
      return Collections.emptySet();
    }

    // 1. + 2. (Search the class belonging to the this pointer type and all its transitive
    // superclasses)
    return resolveFromSuperclasses(thisPointerType, methodName, descriptor)
        .map(Collections::singleton)
        // 3. (Otherwise find maximally specific method from superinterfaces)
        .orElseGet(() -> resolveFromSuperinterfaces(thisPointerType, methodName, descriptor));
  }

  /**
   * Adapter of {@link CallUtil#resolveVirtual(Clazz, String, String)} returning {@link
   * MethodSignature}.
   *
   * @param thisPointerType The type of the this pointer of the call (JVM spec:
   *     "objectref").
   * @param methodName The name of the invoked method.
   * @param descriptor The descriptor of the invoked method.
   * @return The {@link MethodSignature}s of potential call target (usually just one, but see {@link
   *     #resolveFromSuperinterfaces(Clazz, String, String)} for details on when there might be
   *     multiple).
   */
  public static Set resolveVirtualSignatures(
      Clazz thisPointerType, String methodName, String descriptor) {
    return resolveVirtual(thisPointerType, methodName, descriptor).stream()
        .map(className -> new MethodSignature(className, methodName, descriptor))
        .collect(Collectors.toSet());
  }

  /**
   * Search for the invocation target in a specific class and recursively in all superclasses.
   *
   * @param start The {@link Clazz} where the lookup is to be started.
   * @param name The name of the method.
   * @param descriptor The method descriptor.
   * @return An {@link Optional} with the fully qualified name of the class containing the target
   *     method, empty if it couldn't be found.
   */
  public static Optional resolveFromSuperclasses(
      Clazz start, String name, String descriptor) {
    Clazz curr = start;
    while (curr != null) {
      Method targetMethod = curr.findMethod(name, descriptor);
      if (targetMethod != null && (targetMethod.getAccessFlags() & AccessConstants.ABSTRACT) == 0) {
        return Optional.of(curr.getName());
      }

      curr = curr.getSuperClass();
    }
    return Optional.empty();
  }

  /**
   * Search for a maximally specific default implementation in all superinterfaces of a class. This
   * step is potentially unintuitive and difficult to grasp, see JVM spec
   * §5.4.3.3 for more information, as well as this great blog post concerning the
   * resolution pitfalls. The following is based on the information on those websites.
   *
   * @param start The {@link Clazz} whose superinterfaces are to be searched.
   * @param name The target method name.
   * @param descriptor The target method descriptor.
   * @return The fully qualified name of the class(es) that contain the method to be invoked. Be
   *     aware that purely from a JVM point of view, this choice can be ambiguous, in which case it
   *     just chooses the candidate randomly. Here, we don't want to gamble, but rather want to add
   *     call graph edges for every possibility, if this ever happens. Javac ensures that such a
   *     case never occurs, but who knows how the bytecode has been generated, so this possibility
   *     is implemented just in case.
   */
  public static Set resolveFromSuperinterfaces(
      Clazz start, String name, String descriptor) {
    Set superInterfaces = new HashSet<>();
    getSuperinterfaces(start, superInterfaces);
    // Get all transitive superinterfaces that have a matching method.
    Set applicableInterfaces =
        superInterfaces.stream()
            .filter(
                i -> {
                  Method m = i.findMethod(name, descriptor);
                  return m != null
                      && (m.getAccessFlags()
                              & (AccessConstants.PRIVATE
                                  | AccessConstants.STATIC
                                  | AccessConstants.ABSTRACT))
                          == 0;
                })
            .collect(Collectors.toSet());

    // Tricky part: Find the "maximally specific" implementation,
    // i.e. the lowest applicable interface in the type hierarchy.
    for (Clazz iface : new HashSet<>(applicableInterfaces)) {
      superInterfaces.clear();
      getSuperinterfaces(iface, superInterfaces);
      // If an applicable interface overrides another applicable interface, it is more specific than
      // the
      // one being overridden -> the overridden interface is no longer applicable.
      superInterfaces.forEach(applicableInterfaces::remove);
    }

    return applicableInterfaces.stream().map(Clazz::getName).collect(Collectors.toSet());
  }

  /**
   * Get the transitive superinterfaces of a class/interface recursively.
   *
   * @param start The {@link Clazz} where the collection process is to be started.
   * @param accumulator The current set of superinterfaces, so that only one set is constructed at
   *     runtime.
   */
  public static void getSuperinterfaces(Clazz start, Set accumulator) {
    for (int i = 0; i < start.getInterfaceCount(); i++) {
      Clazz iface = start.getInterface(i);
      if (iface == null) {
        Metrics.increaseCount(Metrics.MetricType.MISSING_CLASS);
        continue;
      }
      accumulator.add(iface);
      getSuperinterfaces(iface, accumulator);
    }
    if (start.getSuperClass() != null) {
      getSuperinterfaces(start.getSuperClass(), accumulator);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy