Skip to content

Commit

Permalink
Update ShadowWrangler to iterate through methods using getDeclaredMet…
Browse files Browse the repository at this point in the history
…hods

Previously, in ShadowWrangler, shadow method lookup was performed using
ShadowClass.findDeclaredMethod. It was called once to look for an exact match
of a shadow method, and sometimes called again to check for a looseSignatures
match.

There are plans to add new features and capabilities to the way that shadow
methods are matched. For example:
* looseSignatures being replaced with a more minimal
@classname("internal.type") annotation.
* If the signature of a method changes across SDK levels, we could introduce
different method names that map to the same method name.

However, to search for methods that cannot be matched using
ShadowClass.findDeclaredMethod, it is required to iterate over all candidate
methods using ShadowClass.findDeclaredMethods.

There were some questions about the performance of using
ShadowClass.findDeclaredMethods + iteration. However, after some preliminary
benchmarks, this approach is surprisingly approximately 25% faster than using
ShadowClass.findDeclaredMethod. It is perhaps due to the internal caching of
ShadowClass.findDeclaredMethods.

With this change, it will be possible to perform more advanced filtering and
searching for methods.

For #8841

PiperOrigin-RevId: 619288002
  • Loading branch information
hoisie authored and Copybara-Service committed Mar 28, 2024
1 parent e1dffaa commit a944326
Showing 1 changed file with 39 additions and 35 deletions.
Expand Up @@ -18,6 +18,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Priority;
Expand Down Expand Up @@ -305,13 +306,8 @@ private Method findShadowMethod(
Class<?>[] types,
ShadowInfo shadowInfo,
Class<?> shadowClass) {
Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types);

if (method == null && shadowInfo.looseSignatures) {
Class<?>[] genericTypes = MethodType.genericMethodType(types.length).parameterArray();
method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes);
}

Method method =
findShadowMethodDeclaredOnClass(shadowClass, name, types, shadowInfo.looseSignatures);
if (method != null) {
return method;
} else {
Expand All @@ -334,41 +330,49 @@ private Method findShadowMethod(
}

private Method findShadowMethodDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
try {
Method method = shadowClass.getDeclaredMethod(methodName, paramClasses);

// todo: allow per-version overloading
// if (method == null) {
// String methodPrefix = name + "$$";
// for (Method candidateMethod : shadowClass.getDeclaredMethods()) {
// if (candidateMethod.getName().startsWith(methodPrefix)) {
//
// }
// }
// }

if (isValidShadowMethod(method)) {
method.setAccessible(true);
return method;
} else {
return null;
Class<?> shadowClass, String methodName, Class<?>[] paramClasses, boolean looseSignatures) {
Method foundMethod = null;
for (Method method : shadowClass.getDeclaredMethods()) {
if (!method.getName().equals(methodName)
|| method.getParameterCount() != paramClasses.length) {
continue;
}

} catch (NoSuchMethodException e) {
return null;
}
}
if (!Modifier.isPublic(method.getModifiers())
&& !Modifier.isProtected(method.getModifiers())) {
continue;
}

private boolean isValidShadowMethod(Method method) {
int modifiers = method.getModifiers();
if (!Modifier.isPublic(modifiers) && !Modifier.isProtected(modifiers)) {
return false;
if (Arrays.equals(method.getParameterTypes(), paramClasses)
&& shadowMatcher.matches(method)) {
// Found an exact match, we can exit early.
foundMethod = method;
break;
}
if (looseSignatures) {
boolean allParameterTypesAreObject = true;
for (Class<?> paramClass : method.getParameterTypes()) {
if (!paramClass.equals(Object.class)) {
allParameterTypesAreObject = false;
break;
}
}
if (allParameterTypesAreObject && shadowMatcher.matches(method)) {
// Found a looseSignatures match, but continue looking for an exact match.
foundMethod = method;
}
}
}

return shadowMatcher.matches(method);
if (foundMethod != null) {
foundMethod.setAccessible(true);
return foundMethod;
} else {
return null;
}
}


@Override
public Object intercept(String signature, Object instance, Object[] params, Class theClass)
throws Throwable {
Expand Down

0 comments on commit a944326

Please sign in to comment.