aboutsummaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorMichael Hoisie <hoisie@google.com>2023-05-04 11:03:57 -0700
committerMichael Hoisie <hoisie@google.com>2023-05-04 11:03:57 -0700
commit69158651549f64d28f1749c4e7cab1cc0fa5efa6 (patch)
tree64b56b5d642111c634a97834754ccd34eeaf93f4 /utils
parent5140d1c74b14bbc4ae3d8143543da9819b157550 (diff)
parentb0289bda3e7066750911fab96dbb38e29e39b11c (diff)
downloadrobolectric-69158651549f64d28f1749c4e7cab1cc0fa5efa6.tar.gz
Merge branch 'google' into 'master'
Diffstat (limited to 'utils')
-rw-r--r--utils/reflector/src/main/java/org/robolectric/util/reflector/Constructor.java11
-rw-r--r--utils/reflector/src/main/java/org/robolectric/util/reflector/Reflector.java14
-rw-r--r--utils/reflector/src/main/java/org/robolectric/util/reflector/ReflectorClassWriter.java199
-rw-r--r--utils/reflector/src/test/java/org/robolectric/util/reflector/ReflectorTest.java41
4 files changed, 234 insertions, 31 deletions
diff --git a/utils/reflector/src/main/java/org/robolectric/util/reflector/Constructor.java b/utils/reflector/src/main/java/org/robolectric/util/reflector/Constructor.java
new file mode 100644
index 000000000..d69c39145
--- /dev/null
+++ b/utils/reflector/src/main/java/org/robolectric/util/reflector/Constructor.java
@@ -0,0 +1,11 @@
+package org.robolectric.util.reflector;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that the annotated method is a constructor. */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Constructor {}
diff --git a/utils/reflector/src/main/java/org/robolectric/util/reflector/Reflector.java b/utils/reflector/src/main/java/org/robolectric/util/reflector/Reflector.java
index 2873e2864..12f855f2b 100644
--- a/utils/reflector/src/main/java/org/robolectric/util/reflector/Reflector.java
+++ b/utils/reflector/src/main/java/org/robolectric/util/reflector/Reflector.java
@@ -38,6 +38,8 @@ public class Reflector {
private static final boolean DEBUG = false;
private static final AtomicInteger COUNTER = new AtomicInteger();
private static final Map<Class<?>, Constructor<?>> cache = new ConcurrentHashMap<>();
+ private static final Map<Class<?>, Object> staticReflectorCache = new ConcurrentHashMap<>();
+
/**
* Returns an object which provides accessors for invoking otherwise inaccessible static methods
* and fields.
@@ -56,6 +58,10 @@ public class Reflector {
* @param target the target object
*/
public static <T> T reflector(Class<T> iClass, Object target) {
+ if (target == null && staticReflectorCache.containsKey(iClass)) {
+ return (T) staticReflectorCache.get(iClass);
+ }
+
Class<?> targetClass = determineTargetClass(iClass);
Constructor<? extends T> ctor = (Constructor<? extends T>) cache.get(iClass);
@@ -68,11 +74,15 @@ public class Reflector {
() -> Reflector.<T>createReflectorClass(iClass, targetClass));
ctor = reflectorClass.getConstructor(targetClass);
ctor.setAccessible(true);
+ cache.put(iClass, ctor);
}
- cache.put(iClass, ctor);
+ T instance = ctor.newInstance(target);
+ if (target == null) {
+ staticReflectorCache.put(iClass, instance);
+ }
+ return instance;
- return ctor.newInstance(target);
} catch (NoSuchMethodException
| InstantiationException
| IllegalAccessException
diff --git a/utils/reflector/src/main/java/org/robolectric/util/reflector/ReflectorClassWriter.java b/utils/reflector/src/main/java/org/robolectric/util/reflector/ReflectorClassWriter.java
index d3e366855..ea9b45c6d 100644
--- a/utils/reflector/src/main/java/org/robolectric/util/reflector/ReflectorClassWriter.java
+++ b/utils/reflector/src/main/java/org/robolectric/util/reflector/ReflectorClassWriter.java
@@ -15,6 +15,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
+import javax.annotation.Nullable;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -29,6 +30,8 @@ class ReflectorClassWriter extends ClassWriter {
private static final Type CLASS_TYPE = Type.getType(Class.class);
private static final Type FIELD_TYPE = Type.getType(Field.class);
private static final Type METHOD_TYPE = Type.getType(Method.class);
+ private static final Type CONSTRUCTOR_TYPE = Type.getType(java.lang.reflect.Constructor.class);
+
private static final Type STRING_TYPE = Type.getType(String.class);
private static final Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
@@ -45,6 +48,8 @@ class ReflectorClassWriter extends ClassWriter {
findMethod(Class.class, "getDeclaredField", new Class<?>[] {String.class});
private static final org.objectweb.asm.commons.Method CLASS$GET_DECLARED_METHOD =
findMethod(Class.class, "getDeclaredMethod", new Class<?>[] {String.class, Class[].class});
+ private static final org.objectweb.asm.commons.Method CLASS$GET_DECLARED_CONSTRUCTOR =
+ findMethod(Class.class, "getDeclaredConstructor", new Class<?>[] {Class[].class});
private static final org.objectweb.asm.commons.Method ACCESSIBLE_OBJECT$SET_ACCESSIBLE =
findMethod(AccessibleObject.class, "setAccessible", new Class<?>[] {boolean.class});
private static final org.objectweb.asm.commons.Method FIELD$GET =
@@ -53,6 +58,9 @@ class ReflectorClassWriter extends ClassWriter {
findMethod(Field.class, "set", new Class<?>[] {Object.class, Object.class});
private static final org.objectweb.asm.commons.Method METHOD$INVOKE =
findMethod(Method.class, "invoke", new Class<?>[] {Object.class, Object[].class});
+ private static final org.objectweb.asm.commons.Method CONSTRUCTOR$NEWINSTANCE =
+ findMethod(
+ java.lang.reflect.Constructor.class, "newInstance", new Class<?>[] {Object[].class});
private static final org.objectweb.asm.commons.Method THROWABLE$GET_CAUSE =
findMethod(Throwable.class, "getCause", new Class<?>[] {});
private static final org.objectweb.asm.commons.Method OBJECT_INIT =
@@ -118,8 +126,11 @@ class ReflectorClassWriter extends ClassWriter {
if (method.isDefault()) continue;
Accessor accessor = method.getAnnotation(Accessor.class);
+ Constructor constructor = method.getAnnotation(Constructor.class);
if (accessor != null) {
new AccessorMethodWriter(method, accessor).write();
+ } else if (constructor != null) {
+ new ConstructorMethodWriter(method).write();
} else {
new ReflectorMethodWriter(method).write();
}
@@ -251,6 +262,135 @@ class ReflectorClassWriter extends ClassWriter {
}
}
+ private class ConstructorMethodWriter extends BaseAdapter {
+
+ private final String constructorRefName;
+ private final Type[] targetParamTypes;
+
+ private ConstructorMethodWriter(Method method) {
+ super(method);
+ int myMethodNumber = nextMethodNumber++;
+ this.constructorRefName = "constructor" + myMethodNumber;
+ this.targetParamTypes = resolveParamTypes(iMethod);
+ }
+
+ void write() {
+ // write field to hold method reference...
+ visitField(
+ ACC_PRIVATE | ACC_STATIC,
+ constructorRefName,
+ CONSTRUCTOR_TYPE.getDescriptor(),
+ null,
+ null);
+
+ visitCode();
+
+ // pseudocode:
+ // try {
+ // return constructorN.newInstance(*args);
+ // } catch (InvocationTargetException e) {
+ // throw e.getCause();
+ // } catch (ReflectiveOperationException e) {
+ // throw new AssertionError("Error invoking reflector method in ClassLoader " +
+ // Instrumentation.class.getClassLoader(), e);
+ // }
+ Label tryStart = new Label();
+ Label tryEnd = new Label();
+ Label handleInvocationTargetException = new Label();
+ visitTryCatchBlock(
+ tryStart,
+ tryEnd,
+ handleInvocationTargetException,
+ INVOCATION_TARGET_EXCEPTION_TYPE.getInternalName());
+ Label handleReflectiveOperationException = new Label();
+ visitTryCatchBlock(
+ tryStart,
+ tryEnd,
+ handleReflectiveOperationException,
+ REFLECTIVE_OPERATION_EXCEPTION_TYPE.getInternalName());
+
+ mark(tryStart);
+ loadOriginalConstructorRef();
+ loadArgArray();
+ invokeVirtual(CONSTRUCTOR_TYPE, CONSTRUCTOR$NEWINSTANCE);
+ mark(tryEnd);
+
+ castForReturn(iMethod.getReturnType());
+ returnValue();
+
+ mark(handleInvocationTargetException);
+
+ int exceptionLocalVar = newLocal(THROWABLE_TYPE);
+ storeLocal(exceptionLocalVar);
+ loadLocal(exceptionLocalVar);
+ invokeVirtual(THROWABLE_TYPE, THROWABLE$GET_CAUSE);
+ throwException();
+ mark(handleReflectiveOperationException);
+ exceptionLocalVar = newLocal(REFLECTIVE_OPERATION_EXCEPTION_TYPE);
+ storeLocal(exceptionLocalVar);
+ newInstance(STRINGBUILDER_TYPE);
+ dup();
+ invokeConstructor(STRINGBUILDER_TYPE, OBJECT_INIT);
+ push("Error invoking reflector method in ClassLoader ");
+ invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER$APPEND);
+ push(targetType);
+ invokeVirtual(CLASS_TYPE, CLASS$GET_CLASS_LOADER);
+ invokeStatic(STRING_TYPE, STRING$VALUE_OF);
+ invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER$APPEND);
+ invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER$TO_STRING);
+ int messageLocalVar = newLocal(STRING_TYPE);
+ storeLocal(messageLocalVar);
+ newInstance(ASSERTION_ERROR_TYPE);
+ dup();
+ loadLocal(messageLocalVar);
+ loadLocal(exceptionLocalVar);
+ invokeConstructor(ASSERTION_ERROR_TYPE, ASSERTION_ERROR_INIT);
+ throwException();
+
+ endMethod();
+ }
+
+ private void loadOriginalConstructorRef() {
+ // pseudocode:
+ // if (constructorN == null) {
+ // constructorN = targetClass.getDeclaredConstructor(paramTypes);
+ // constructorN.setAccessible(true);
+ // }
+ // -> constructor reference on stack
+ getStatic(reflectorType, constructorRefName, CONSTRUCTOR_TYPE);
+ dup();
+ Label haveConstructorRef = newLabel();
+ ifNonNull(haveConstructorRef);
+ pop();
+
+ // pseudocode:
+ // targetClass.getDeclaredConstructor(paramTypes);
+ push(targetType);
+ Type[] paramTypes = targetParamTypes;
+ push(paramTypes.length);
+ newArray(CLASS_TYPE);
+ for (int i = 0; i < paramTypes.length; i++) {
+ dup();
+ push(i);
+ push(paramTypes[i]);
+ arrayStore(CLASS_TYPE);
+ }
+ invokeVirtual(CLASS_TYPE, CLASS$GET_DECLARED_CONSTRUCTOR);
+
+ // pseudocode:
+ // <constructor>.setAccessible(true);
+ dup();
+ push(true);
+ invokeVirtual(CONSTRUCTOR_TYPE, ACCESSIBLE_OBJECT$SET_ACCESSIBLE);
+
+ // pseudocode:
+ // constructorN = constructor;
+ dup();
+ putStatic(reflectorType, constructorRefName, CONSTRUCTOR_TYPE);
+ mark(haveConstructorRef);
+ }
+ }
+
private class ReflectorMethodWriter extends BaseAdapter {
private final String methodRefName;
@@ -375,35 +515,6 @@ class ReflectorClassWriter extends ClassWriter {
putStatic(reflectorType, methodRefName, METHOD_TYPE);
mark(haveMethodRef);
}
-
- private Type[] resolveParamTypes(Method iMethod) {
- Class<?>[] iParamTypes = iMethod.getParameterTypes();
- Annotation[][] paramAnnotations = iMethod.getParameterAnnotations();
-
- Type[] targetParamTypes = new Type[iParamTypes.length];
- for (int i = 0; i < iParamTypes.length; i++) {
- Class<?> paramType = findWithType(paramAnnotations[i]);
- if (paramType == null) {
- paramType = iParamTypes[i];
- }
- targetParamTypes[i] = Type.getType(paramType);
- }
- return targetParamTypes;
- }
-
- private Class<?> findWithType(Annotation[] paramAnnotation) {
- for (Annotation annotation : paramAnnotation) {
- if (annotation instanceof WithType) {
- String withTypeName = ((WithType) annotation).value();
- try {
- return Class.forName(withTypeName, true, iClass.getClassLoader());
- } catch (ClassNotFoundException e1) {
- // it's okay, ignore
- }
- }
- }
- return null;
- }
}
private static String[] getInternalNames(final Class<?>[] types) {
@@ -494,5 +605,35 @@ class ReflectorClassWriter extends ClassWriter {
void loadNull() {
visitInsn(Opcodes.ACONST_NULL);
}
+
+ protected Type[] resolveParamTypes(Method iMethod) {
+ Class<?>[] iParamTypes = iMethod.getParameterTypes();
+ Annotation[][] paramAnnotations = iMethod.getParameterAnnotations();
+
+ Type[] targetParamTypes = new Type[iParamTypes.length];
+ for (int i = 0; i < iParamTypes.length; i++) {
+ Class<?> paramType = findWithType(paramAnnotations[i]);
+ if (paramType == null) {
+ paramType = iParamTypes[i];
+ }
+ targetParamTypes[i] = Type.getType(paramType);
+ }
+ return targetParamTypes;
+ }
+
+ @Nullable
+ private Class<?> findWithType(Annotation[] paramAnnotation) {
+ for (Annotation annotation : paramAnnotation) {
+ if (annotation instanceof WithType) {
+ String withTypeName = ((WithType) annotation).value();
+ try {
+ return Class.forName(withTypeName, true, iClass.getClassLoader());
+ } catch (ClassNotFoundException e1) {
+ // it's okay, ignore
+ }
+ }
+ }
+ return null;
+ }
}
}
diff --git a/utils/reflector/src/test/java/org/robolectric/util/reflector/ReflectorTest.java b/utils/reflector/src/test/java/org/robolectric/util/reflector/ReflectorTest.java
index 8baf3d63e..74dc88487 100644
--- a/utils/reflector/src/test/java/org/robolectric/util/reflector/ReflectorTest.java
+++ b/utils/reflector/src/test/java/org/robolectric/util/reflector/ReflectorTest.java
@@ -133,6 +133,25 @@ public class ReflectorTest {
time("saved accessor", 10_000_000, () -> fieldBySavedReflector(accessor));
}
+ @Ignore
+ @Test
+ public void constructorPerf() {
+ SomeClass i = new SomeClass("c");
+
+ System.out.println("reflection = " + Collections.singletonList(methodByReflectionHelpers(i)));
+ System.out.println("accessor = " + Collections.singletonList(methodByReflector(i)));
+
+ _SomeClass_ accessor = reflector(_SomeClass_.class, i);
+
+ time("ReflectionHelpers", 10_000_000, this::constructorByReflectionHelpers);
+ time("accessor", 10_000_000, () -> constructorByReflector());
+ time("saved accessor", 10_000_000, () -> constructorBySavedReflector(accessor));
+
+ time("ReflectionHelpers", 10_000_000, () -> constructorByReflectionHelpers());
+ time("accessor", 10_000_000, () -> constructorByReflector());
+ time("saved accessor", 10_000_000, () -> constructorBySavedReflector(accessor));
+ }
+
@Test
public void nonExistentMethod_throwsAssertionError() {
SomeClass i = new SomeClass("c");
@@ -143,6 +162,11 @@ public class ReflectorTest {
assertThat(ex).hasCauseThat().isInstanceOf(NoSuchMethodException.class);
}
+ @Test
+ public void reflector_constructor() {
+ assertThat(staticReflector.newSomeClass("sdfsdf")).isNotNull();
+ }
+
//////////////////////
/** Accessor interface for {@link SomeClass}'s internals. */
@@ -170,6 +194,9 @@ public class ReflectorTest {
@Accessor("mD")
int getD();
+ @Constructor
+ SomeClass newSomeClass(String c);
+
String someMethod(String a, String b);
String nonExistentMethod(String a, String b, String c);
@@ -251,6 +278,20 @@ public class ReflectorTest {
return reflector.someMethod("a", "b");
}
+ private SomeClass constructorByReflectionHelpers() {
+ return ReflectionHelpers.callConstructor(
+ SomeClass.class, ClassParameter.from(String.class, "a"));
+ }
+
+ private SomeClass constructorByReflector() {
+ _SomeClass_ accessor = reflector(_SomeClass_.class);
+ return accessor.newSomeClass("a");
+ }
+
+ private SomeClass constructorBySavedReflector(_SomeClass_ reflector) {
+ return reflector.newSomeClass("a");
+ }
+
private String fieldByReflectionHelpers(SomeClass o) {
ReflectionHelpers.setField(o, "c", "abc");
return ReflectionHelpers.getField(o, "c");