Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
# Conflicts:
#	base/src/main/java/proguard/analysis/CallResolver.java
#	base/src/main/java/proguard/analysis/cpa/bam/BamTransferRelation.java
#	base/src/main/java/proguard/analysis/cpa/jvm/cfa/visitors/JvmIntraproceduralCfaFillerAllInstructionVisitor.java
#	base/src/main/java/proguard/analysis/cpa/jvm/domain/memory/JvmMemoryLocationTransferRelation.java
#	base/src/main/java/proguard/analysis/cpa/jvm/domain/value/ValueAbstractState.java
#	base/src/main/java/proguard/analysis/cpa/jvm/state/heap/tree/JvmShallowHeapAbstractState.java
  • Loading branch information
zlataovce committed Sep 1, 2024
2 parents 15a148f + 88378b4 commit 413fcd1
Show file tree
Hide file tree
Showing 31 changed files with 589 additions and 249 deletions.
146 changes: 12 additions & 134 deletions base/src/main/java/proguard/analysis/CallResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@

package proguard.analysis;

import static proguard.exception.ErrorId.ANALYSIS_CALL_RESOLVER_SELECTIVE_PARAMETER_RECONSTRUCTION_MISSING_PARAMS;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -74,6 +75,7 @@
import proguard.evaluation.value.TypedReferenceValue;
import proguard.evaluation.value.Value;
import proguard.evaluation.value.ValueFactory;
import proguard.exception.ProguardCoreException;
import proguard.util.Logger;
import proguard.util.PartialEvaluatorUtils;

Expand Down Expand Up @@ -253,8 +255,10 @@ public CallResolver(
this.selectiveParameterReconstruction = selectiveParameterReconstruction;
if (selectiveParameterReconstruction
&& (interestingMethods == null || interestingCallPredicates == null)) {
throw new IllegalArgumentException(
"Using selectiveParameterReconstruction requires interestingMethods and interestingCallPredicates.");
throw new ProguardCoreException.Builder(
"Using selectiveParameterReconstruction requires interestingMethods and interestingCallPredicates.",
ANALYSIS_CALL_RESOLVER_SELECTIVE_PARAMETER_RECONSTRUCTION_MISSING_PARAMS)
.build();
}
this.interestingMethods = interestingMethods;
this.interestingCallPredicates = interestingCallPredicates;
Expand Down Expand Up @@ -656,8 +660,8 @@ private void handleInvokeSpecial(
* @param ref The {@link AnyMethodrefConstant} specifying name and descriptor of the method to be
* invoked.
* @return The fully qualified names of potential call target classes (usually just one, but see
* {@link #resolveFromSuperinterfaces(Clazz, String, String)} for details on when there might
* be multiple).
* {@link CallUtil#resolveFromSuperinterfaces(Clazz, String, String)} for details on when
* there might be multiple).
*/
private Set<String> resolveInvokeSpecial(Clazz callingClass, AnyMethodrefConstant ref) {
String name = ref.getName(callingClass);
Expand Down Expand Up @@ -699,7 +703,7 @@ private Set<String> resolveInvokeSpecial(Clazz callingClass, AnyMethodrefConstan
Optional<String> target = Optional.empty();
if ((c.getAccessFlags() & AccessConstants.INTERFACE) == 0) {
// 2. (C is a class -> check superclasses transitively)
target = resolveFromSuperclasses(c, name, descriptor);
target = CallUtil.resolveFromSuperclasses(c, name, descriptor);
} else {
// 3. (C is an interface -> Check if java.lang.Object has a suitable method)
for (java.lang.reflect.Method m : Object.class.getMethods()) {
Expand All @@ -714,7 +718,7 @@ && getDescriptor(m).equals(descriptor)) {
// 4. (Otherwise find maximally specific method from superinterfaces)
return target
.map(Collections::singleton)
.orElseGet(() -> resolveFromSuperinterfaces(c, name, descriptor));
.orElseGet(() -> CallUtil.resolveFromSuperinterfaces(c, name, descriptor));
}

/** Get the Descriptor of a {@link java.lang.reflect.Method}. */
Expand Down Expand Up @@ -797,7 +801,7 @@ private void handleVirtualMethods(
Metrics.increaseCount(MetricType.MISSING_CLASS);
}

Set<String> targetClasses = resolveVirtual(location.clazz, referencedClass, ref);
Set<String> targetClasses = CallUtil.resolveVirtual(location.clazz, referencedClass, ref);
if (targetClasses.isEmpty()) {
if (referencedClass != null) {
Metrics.increaseCount(MetricType.MISSING_METHODS);
Expand All @@ -821,132 +825,6 @@ private void handleVirtualMethods(
}
}

/**
* The <code>invokevirtual</code> and <code>invokeinterface</code> resolution algorithm, annotated
* with <a
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual">JVM
* spec §6.5.invokevirtual</a> citations where appropriate, so that the specified lookup process
* can easily be compared to this implementation.
*
* @param callingClass JVM spec: "current class".
* @param thisPtrType The type of the <code>this</code> 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).
*/
private Set<String> resolveVirtual(
Clazz callingClass, Clazz thisPtrType, AnyMethodrefConstant ref) {
if (thisPtrType == null) {
return Collections.emptySet();
}
String name = ref.getName(callingClass);
String descriptor = ref.getType(callingClass);

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

/**
* 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.
*/
private Optional<String> 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 <a
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">JVM spec
* §5.4.3.3</a> for more information, as well as this great <a
* href="https://jvilk.com/blog/java-8-wtf-ambiguous-method-lookup/">blog post</a> 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.
*/
private Set<String> resolveFromSuperinterfaces(Clazz start, String name, String descriptor) {
Set<Clazz> superInterfaces = new HashSet<>();
getSuperinterfaces(start, superInterfaces);
// Get all transitive superinterfaces that have a matching method.
Set<Clazz> 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.
*/
private void getSuperinterfaces(Clazz start, Set<Clazz> accumulator) {
for (int i = 0; i < start.getInterfaceCount(); i++) {
Clazz iface = start.getInterface(i);
if (iface == null) {
Metrics.increaseCount(MetricType.MISSING_CLASS);
continue;
}
accumulator.add(iface);
getSuperinterfaces(iface, accumulator);
}
if (start.getSuperClass() != null) {
getSuperinterfaces(start.getSuperClass(), accumulator);
}
}

private static class CurrentClazzMethodAttribute {
public final Clazz clazz;
public final Method method;
Expand Down
182 changes: 182 additions & 0 deletions base/src/main/java/proguard/analysis/CallUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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 <code>invokevirtual</code> and <code>invokeinterface</code> resolution algorithm, annotated
* with <a
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual">JVM
* spec §6.5.invokevirtual</a> 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 <code>this</code> 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<String> resolveVirtual(
Clazz callingClass, Clazz thisPointerType, AnyMethodrefConstant ref) {
return resolveVirtual(thisPointerType, ref.getName(callingClass), ref.getType(callingClass));
}

/**
* The <code>invokevirtual</code> and <code>invokeinterface</code> resolution algorithm, annotated
* with <a
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual">JVM
* spec §6.5.invokevirtual</a> citations where appropriate, so that the specified lookup process
* can easily be compared to this implementation.
*
* @param thisPointerType The type of the <code>this</code> 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<String> 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 <code>this</code> 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<MethodSignature> 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<String> 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 <a
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">JVM spec
* §5.4.3.3</a> for more information, as well as this great <a
* href="https://jvilk.com/blog/java-8-wtf-ambiguous-method-lookup/">blog post</a> 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<String> resolveFromSuperinterfaces(
Clazz start, String name, String descriptor) {
Set<Clazz> superInterfaces = new HashSet<>();
getSuperinterfaces(start, superInterfaces);
// Get all transitive superinterfaces that have a matching method.
Set<Clazz> 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<Clazz> 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);
}
}
}
Loading

0 comments on commit 413fcd1

Please sign in to comment.