diff options
author | Michael Hoisie <hoisie@google.com> | 2023-05-04 11:03:57 -0700 |
---|---|---|
committer | Michael Hoisie <hoisie@google.com> | 2023-05-04 11:03:57 -0700 |
commit | 69158651549f64d28f1749c4e7cab1cc0fa5efa6 (patch) | |
tree | 64b56b5d642111c634a97834754ccd34eeaf93f4 /utils | |
parent | 5140d1c74b14bbc4ae3d8143543da9819b157550 (diff) | |
parent | b0289bda3e7066750911fab96dbb38e29e39b11c (diff) | |
download | robolectric-69158651549f64d28f1749c4e7cab1cc0fa5efa6.tar.gz |
Merge branch 'google' into 'master'
Diffstat (limited to 'utils')
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"); |