Skip to content

Commit

Permalink
Replace looseSignature with ClassName
Browse files Browse the repository at this point in the history
Signed-off-by: utzcoz <utzcoz@outlook.com>
  • Loading branch information
utzcoz committed Feb 15, 2024
1 parent c65b13e commit 14b93cd
Show file tree
Hide file tree
Showing 33 changed files with 313 additions and 161 deletions.
@@ -0,0 +1,20 @@
package org.robolectric.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Parameters with types that can't be resolved at compile time may be annotated @ClassName. */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassName {

/**
* The class name intended for this parameter.
*
* <p>Use the value as returned from {@link Class#getName()}, not {@link
* Class#getCanonicalName()}; e.g. {@code Foo$Bar} instead of {@code Foo.Bar}.
*/
String value();
}
Expand Up @@ -38,6 +38,7 @@
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.Implementation;
import org.robolectric.versioning.AndroidVersionInitTools;

Expand Down Expand Up @@ -162,7 +163,7 @@ public String verifyMethod(

MethodExtraInfo sdkMethod = classInfo.findMethod(methodElement, looseSignatures);
if (sdkMethod == null) {
return "No such method in " + className;
return "No such method " + methodElement + " in " + className;
}

MethodExtraInfo implMethod = new MethodExtraInfo(methodElement);
Expand Down Expand Up @@ -335,7 +336,7 @@ public ClassInfo(ClassNode classNode) {
}

MethodExtraInfo findMethod(ExecutableElement methodElement, boolean looseSignatures) {
MethodInfo methodInfo = new MethodInfo(methodElement);
MethodInfo methodInfo = new MethodInfo(methodElement, looseSignatures);

MethodExtraInfo methodExtraInfo = methods.get(methodInfo);
if (looseSignatures && methodExtraInfo == null) {
Expand Down Expand Up @@ -366,14 +367,25 @@ public MethodInfo(String name, int size) {
}

/** Create a MethodInfo from AST (an @Implementation method in a shadow class). */
public MethodInfo(ExecutableElement methodElement) {
public MethodInfo(ExecutableElement methodElement, boolean looseSignatures) {
this.name = cleanMethodName(methodElement);

for (VariableElement variableElement : methodElement.getParameters()) {
TypeMirror varTypeMirror = variableElement.asType();
String paramType = canonicalize(varTypeMirror);
String paramTypeWithoutGenerics = typeWithoutGenerics(paramType);
paramTypes.add(paramTypeWithoutGenerics);
// If one shadow class is marked by looseSignature, we should ignore
// ClassName in its method's signature as it will affect special method
// signatures for looseSignature.
if (!looseSignatures) {
ClassName className = variableElement.getAnnotation(ClassName.class);
// If this parameter has ClassName annotation, we need to save its type
// based on ClassName value.
if (className != null) {
paramTypes.add(typeWithoutGenerics(className.value()));
continue;
}
}
paramTypes.add(typeWithoutGenerics(paramType));
}
}

Expand Down Expand Up @@ -433,7 +445,14 @@ public MethodExtraInfo(MethodNode method) {

public MethodExtraInfo(ExecutableElement methodElement) {
this.isStatic = methodElement.getModifiers().contains(Modifier.STATIC);
this.returnType = typeWithoutGenerics(canonicalize(methodElement.getReturnType()));
ClassName className = methodElement.getAnnotation(ClassName.class);
if (className != null) {
// If this return type has ClassName annotation, we need to save its type
// based on ClassName value.
this.returnType = typeWithoutGenerics(className.value());
} else {
this.returnType = typeWithoutGenerics(canonicalize(methodElement.getReturnType()));
}
}

@Override
Expand Down
Expand Up @@ -8,6 +8,7 @@
import static org.robolectric.util.reflector.Reflector.reflector;

import com.google.auto.service.AutoService;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
Expand All @@ -21,6 +22,7 @@
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Priority;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.ReflectorObject;
import org.robolectric.sandbox.NativeMethodNotFoundException;
Expand Down Expand Up @@ -320,13 +322,23 @@ private Method findShadowMethod(
Class<?>[] types,
ShadowInfo shadowInfo,
Class<?> shadowClass) {
// Try to find the shadow method with the exact method signature first.
Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types);

// Try to find shadow method with fallback looseSignature mechanism.
if (method == null && shadowInfo.looseSignatures) {
// If user sets looseSignatures for shadow class, we will try to find method with generic
// types by following origin full looseSignatures definition.
Class<?>[] genericTypes = MethodType.genericMethodType(types.length).parameterArray();
method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes);
}

// Try to find shadow method with another fallback WithType mechanism with a lower priority.
if (method == null && !shadowInfo.looseSignatures) {
// Otherwise, we will try to find method with WithType annotation that can match signature.
method = findShadowMethodHasWithTypeDeclaredOnClass(shadowClass, name, types);
}

if (method != null) {
return method;
} else {
Expand All @@ -348,6 +360,76 @@ private Method findShadowMethod(
return method;
}

private ClassName findClassNameAnnotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (ClassName.class.isAssignableFrom(annotation.annotationType())) {
return (ClassName) annotation;
}
}
return null;
}

private boolean hasClassNameAnnotation(Annotation[][] annotations) {
for (Annotation[] parameterAnnotations : annotations) {
for (Annotation annotation : parameterAnnotations) {
if (ClassName.class.isAssignableFrom(annotation.annotationType())) {
return true;
}
}
}
return false;
}

private Method findShadowMethodHasWithTypeDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
// We don't process the method without input parameters now.
if (paramClasses == null || paramClasses.length == 0) {
return null;
}
Method[] methods = shadowClass.getDeclaredMethods();
// TODO try to find methods with the same name first
for (Method method : methods) {
if (method == null
|| !method.getName().equals(methodName)
|| method.getParameterCount() != paramClasses.length
|| !isValidShadowMethod(method)) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] allAnnotations = method.getParameterAnnotations();
if (!hasClassNameAnnotation(allAnnotations)) {
continue;
}
boolean matched = true;
for (int i = 0; i < parameterTypes.length; i++) {
// If method's parameter type is superclass of input parameter, we can pass checking for
// this parameter.
if (parameterTypes[i].isAssignableFrom(paramClasses[i])) {
continue;
}
if (allAnnotations.length <= i) {
matched = false;
break;
}
ClassName className = findClassNameAnnotation(allAnnotations[i]);
// If developer uses WithType for an input parameter, we need ensure it is the same
// type of the real method to avoid unexpected method override/overwrite result.
if (className != null
&& paramClasses[i] != null
&& className.value().equals(paramClasses[i].getCanonicalName())) {
continue;
}
matched = false;
break;
}
// TODO identify why above logic will affect __constructor__ without WithType
if (matched) {
return method;
}
}
return null;
}

private Method findShadowMethodDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
try {
Expand Down
Expand Up @@ -29,6 +29,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.ClassName;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowContextThemeWrapper;
Expand All @@ -39,7 +40,6 @@
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;

/**
* ActivityController provides low-level APIs to control activity's lifecycle.
Expand Down Expand Up @@ -99,7 +99,7 @@ private ActivityController<T> attach(@Nullable Bundle activityOptions) {

private ActivityController<T> attach(
@Nullable Bundle activityOptions,
@Nullable @WithType("android.app.Activity$NonConfigurationInstances")
@Nullable @ClassName("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances,
@Nullable Configuration overrideConfig) {
if (attached) {
Expand Down
Expand Up @@ -62,6 +62,7 @@
import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
Expand All @@ -76,10 +77,9 @@
import org.robolectric.shadows.ShadowLoadedApk._LoadedApk_;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;

@SuppressWarnings("NewApi")
@Implements(value = Activity.class, looseSignatures = true)
@Implements(value = Activity.class)
public class ShadowActivity extends ShadowContextThemeWrapper {

@RealObject protected Activity realActivity;
Expand Down Expand Up @@ -130,7 +130,7 @@ public void callAttach(Intent intent, @Nullable Bundle activityOptions) {
public void callAttach(
Intent intent,
@Nullable Bundle activityOptions,
@Nullable @WithType("android.app.Activity$NonConfigurationInstances")
@Nullable @ClassName("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances) {
callAttach(
intent,
Expand All @@ -142,7 +142,7 @@ public void callAttach(
public void callAttach(
Intent intent,
@Nullable Bundle activityOptions,
@Nullable @WithType("android.app.Activity$NonConfigurationInstances")
@Nullable @ClassName("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances,
@Nullable Configuration overrideConfig) {
Application application = RuntimeEnvironment.getApplication();
Expand Down
Expand Up @@ -42,7 +42,7 @@
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Reflector;

@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
@Implements(value = ActivityThread.class, isInAndroidSdk = false)
public class ShadowActivityThread {
private static ApplicationInfo applicationInfo;
@RealObject protected ActivityThread realActivityThread;
Expand Down
Expand Up @@ -52,6 +52,7 @@
import java.util.Set;
import java.util.stream.IntStream;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
Expand All @@ -64,7 +65,7 @@
import org.robolectric.util.reflector.ForType;

/** Shadow for {@link AppOpsManager}. */
@Implements(value = AppOpsManager.class, looseSignatures = true)
@Implements(value = AppOpsManager.class)
public class ShadowAppOpsManager {

// OpEntry fields that the shadow doesn't currently allow the test to configure.
Expand Down Expand Up @@ -415,18 +416,18 @@ protected int noteProxyOpNoThrow(
@RequiresApi(api = S)
@Implementation(minSdk = S)
protected int noteProxyOpNoThrow(
Object op, Object attributionSource, Object message, Object ignoredSkipProxyOperation) {
Preconditions.checkArgument(op instanceof Integer);
int op,
@ClassName(value = "android.content.AttributionSource") Object attributionSource,
String message,
boolean ignoredSkipProxyOperation) {
Preconditions.checkArgument(attributionSource instanceof AttributionSource);
Preconditions.checkArgument(message == null || message instanceof String);
Preconditions.checkArgument(ignoredSkipProxyOperation instanceof Boolean);
AttributionSource castedAttributionSource = (AttributionSource) attributionSource;
return noteProxyOpNoThrow(
(int) op,
op,
castedAttributionSource.getNextPackageName(),
castedAttributionSource.getNextUid(),
castedAttributionSource.getNextAttributionTag(),
(String) message);
message);
}

@Implementation
Expand Down
Expand Up @@ -14,6 +14,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
Expand All @@ -37,8 +38,7 @@
value = ApkAssets.class,
minSdk = P,
shadowPicker = Picker.class,
isInAndroidSdk = false,
looseSignatures = true)
isInAndroidSdk = false)
public class ShadowArscApkAssets9 extends ShadowApkAssets {
// #define ATRACE_TAG ATRACE_TAG_RESOURCES
//
Expand Down Expand Up @@ -149,12 +149,16 @@ protected static long nativeLoad(
}

@Implementation(minSdk = R)
protected static Object nativeLoad(
Object format, Object javaPath, Object flags, Object assetsProvider) throws IOException {
boolean system = ((int) flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
boolean overlay = ((int) flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
boolean forceSharedLib = ((int) flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
return nativeLoad((String) javaPath, system, forceSharedLib, overlay);
protected static long nativeLoad(
int format,
String javaPath,
int flags,
@ClassName(value = "android.content.res.loader.AssetsProvider") Object assetsProvider)
throws IOException {
boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
boolean overlay = (flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
boolean forceSharedLib = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
return nativeLoad(javaPath, system, forceSharedLib, overlay);
}

// static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
Expand Down

0 comments on commit 14b93cd

Please sign in to comment.