All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.internal.bytecode.ProxyMaker Maven / Gradle / Ivy

There is a newer version: 4.13
Show newest version
package org.robolectric.internal.bytecode;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.V1_7;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import org.robolectric.util.PerfStatsCollector;
import sun.misc.Unsafe;

/**
 * Defines proxy classes that can invoke methods names transformed with a {@link MethodMapper}. It
 * is primarily used to invoke the original $$robo$$-prefixed methods, but it can technically
 * support arbitrary naming schemes.
 *
 * @deprecated This is incompatible with JDK17+. Use a {@link
 *     org.robolectric.util.reflector.Reflector} interface with {@link
 *     org.robolectric.util.reflector.Direct}.
 */
@Deprecated
public class ProxyMaker {
  private static final String TARGET_FIELD = "__proxy__";
  private static final Class UNSAFE_CLASS = Unsafe.class;
  private static final Class LOOKUP_CLASS = MethodHandles.Lookup.class;
  private static final Unsafe UNSAFE;
  private static final java.lang.reflect.Method DEFINE_ANONYMOUS_CLASS;

  private static final MethodHandles.Lookup LOOKUP;
  private static final java.lang.reflect.Method HIDDEN_DEFINE_METHOD;
  private static final Object HIDDEN_CLASS_OPTIONS;

  private static final boolean DEBUG = false;

  static {
    try {
      Field unsafeField = UNSAFE_CLASS.getDeclaredField("theUnsafe");
      unsafeField.setAccessible(true);
      UNSAFE = (Unsafe) unsafeField.get(null);

      // Unsafe.defineAnonymousClass() has been deprecated in Java 15 and removed in Java 17. Its
      // usage should be replace by MethodHandles.Lookup.defineHiddenClass() which was introduced in
      // Java 15. For now, try and support both older and newer Java versions.
      DEFINE_ANONYMOUS_CLASS = getDefineAnonymousClass();
      if (DEFINE_ANONYMOUS_CLASS == null) {
        LOOKUP = getTrustedLookup();

        Class classOptionClass = Class.forName(LOOKUP_CLASS.getName() + "$ClassOption");
        HIDDEN_CLASS_OPTIONS = Array.newInstance(classOptionClass, 1);
        Array.set(HIDDEN_CLASS_OPTIONS, 0, Enum.valueOf(classOptionClass, "NESTMATE"));
        HIDDEN_DEFINE_METHOD =
            LOOKUP_CLASS.getMethod(
                "defineHiddenClass", byte[].class, boolean.class, HIDDEN_CLASS_OPTIONS.getClass());
      } else {
        LOOKUP = null;
        HIDDEN_DEFINE_METHOD = null;
        HIDDEN_CLASS_OPTIONS = null;
      }
    } catch (ReflectiveOperationException e) {
      throw new LinkageError(e.getMessage(), e);
    }
  }

  private static java.lang.reflect.Method getDefineAnonymousClass() {
    try {
      return UNSAFE_CLASS.getMethod(
          "defineAnonymousClass", Class.class, byte[].class, Object[].class);
    } catch (NoSuchMethodException e) {
      return null;
    }
  }

  private static MethodHandles.Lookup getTrustedLookup() throws ReflectiveOperationException {
    Field trustedLookupField = LOOKUP_CLASS.getDeclaredField("IMPL_LOOKUP");
    java.lang.reflect.Method baseMethod = UNSAFE_CLASS.getMethod("staticFieldBase", Field.class);
    Object lookupBase = baseMethod.invoke(UNSAFE, trustedLookupField);
    java.lang.reflect.Method offsetMethod =
        UNSAFE_CLASS.getMethod("staticFieldOffset", Field.class);
    Object lookupOffset = offsetMethod.invoke(UNSAFE, trustedLookupField);

    java.lang.reflect.Method getObjectMethod =
        UNSAFE_CLASS.getMethod("getObject", Object.class, long.class);
    return (MethodHandles.Lookup) getObjectMethod.invoke(UNSAFE, lookupBase, lookupOffset);
  }

  private final MethodMapper methodMapper;
  private final ClassValueMap factories;

  public ProxyMaker(MethodMapper methodMapper) {
    this.methodMapper = methodMapper;
    factories =
        new ClassValueMap() {
          @Override
          protected Factory computeValue(Class type) {
            return PerfStatsCollector.getInstance()
                .measure("createProxyFactory", () -> createProxyFactory(type));
          }
        };
  }

  public  T createProxy(Class targetClass, T target) {
    return PerfStatsCollector.getInstance()
        .measure(
            "createProxyInstance",
            () -> factories.get(targetClass).createProxy(targetClass, target));
  }

   Factory createProxyFactory(Class targetClass) {
    Type targetType = Type.getType(targetClass);
    String targetName = targetType.getInternalName();
    String proxyName = targetName + "$GeneratedProxy";
    Type proxyType = Type.getType("L" + proxyName.replace('.', '/') + ";");
    ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES| ClassWriter.COMPUTE_MAXS);
    writer.visit(
        V1_7,
        ACC_PUBLIC | ACC_SYNTHETIC | ACC_SUPER | ACC_FINAL,
        proxyName,
        null,
        targetName,
        null);

    writer.visitField(
        ACC_PUBLIC | ACC_SYNTHETIC, TARGET_FIELD, targetType.getDescriptor(), null, null);

    for (java.lang.reflect.Method method : targetClass.getMethods()) {
      if (!shouldProxy(method)) continue;

      Method proxyMethod = Method.getMethod(method);
      GeneratorAdapter m =
          new GeneratorAdapter(
              ACC_PUBLIC | ACC_SYNTHETIC, Method.getMethod(method), null, null, writer);
      m.loadThis();
      m.getField(proxyType, TARGET_FIELD, targetType);
      m.loadArgs();
      String targetMethod = methodMapper.getName(targetClass.getName(), method.getName());
      // In Java 8 we could use invokespecial here but not in 7, from jvm spec:
      // If an invokespecial instruction names a method which is not an instance
      // initialization method, then the type of the target reference on the operand
      // stack must be assignment compatible with the current class (JLS §5.2).
      m.visitMethodInsn(INVOKEVIRTUAL, targetName, targetMethod, proxyMethod.getDescriptor(), false);
      m.returnValue();
      m.endMethod();
    }

    writer.visitEnd();

    byte[] bytecode = writer.toByteArray();

    if (DEBUG) {
      File file = new File("/tmp", targetClass.getCanonicalName() + "-DirectProxy.class");
      System.out.println("Generated Direct Proxy: " + file.getAbsolutePath());
      try (OutputStream out = new FileOutputStream(file)) {
        out.write(bytecode);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

    try {
      final Class proxyClass = defineHiddenClass(targetClass, bytecode);
      final Field field = proxyClass.getDeclaredField(TARGET_FIELD);
      return new Factory() {
        @Override public  E createProxy(Class targetClass, E target) {
          try {
            Object proxy = UNSAFE.allocateInstance(proxyClass);

            field.set(proxy, target);

            return targetClass.cast(proxy);
          } catch (Throwable t) {
            throw new AssertionError(t);
          }
        }
      };
    } catch (ReflectiveOperationException e) {
      throw new AssertionError(e);
    }
  }

  private static Class defineHiddenClass(Class targetClass, byte[] bytes)
      throws ReflectiveOperationException {
    if (DEFINE_ANONYMOUS_CLASS != null) {
      return (Class) DEFINE_ANONYMOUS_CLASS.invoke(UNSAFE, targetClass, bytes, (Object[]) null);
    } else {
      MethodHandles.Lookup lookup = (MethodHandles.Lookup) LOOKUP.in(targetClass);
      MethodHandles.Lookup definedLookup =
          (MethodHandles.Lookup)
              HIDDEN_DEFINE_METHOD.invoke(lookup, bytes, false, HIDDEN_CLASS_OPTIONS);
      return definedLookup.lookupClass();
    }
  }

  private static boolean shouldProxy(java.lang.reflect.Method method) {
    int modifiers = method.getModifiers();
    return !Modifier.isAbstract(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isPrivate(
        modifiers) && !Modifier.isNative(modifiers);
  }

  interface MethodMapper {
    String getName(String className, String methodName);
  }

  interface Factory {
     T createProxy(Class targetClass, T target);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy