1 package org.robolectric.internal.bytecode; 2 3 import static java.lang.invoke.MethodHandles.catchException; 4 import static java.lang.invoke.MethodHandles.constant; 5 import static java.lang.invoke.MethodHandles.dropArguments; 6 import static java.lang.invoke.MethodHandles.exactInvoker; 7 import static java.lang.invoke.MethodHandles.filterArguments; 8 import static java.lang.invoke.MethodHandles.foldArguments; 9 import static java.lang.invoke.MethodHandles.throwException; 10 import static java.lang.invoke.MethodType.methodType; 11 import static org.robolectric.internal.bytecode.MethodCallSite.Kind.REGULAR; 12 import static org.robolectric.internal.bytecode.MethodCallSite.Kind.STATIC; 13 14 import java.lang.invoke.CallSite; 15 import java.lang.invoke.ConstantCallSite; 16 import java.lang.invoke.MethodHandle; 17 import java.lang.invoke.MethodHandles; 18 import java.lang.invoke.MethodType; 19 import java.lang.invoke.SwitchPoint; 20 import java.lang.invoke.WrongMethodTypeException; 21 import org.robolectric.util.ReflectionHelpers; 22 23 public class InvokeDynamicSupport { 24 @SuppressWarnings("unused") 25 private static Interceptors INTERCEPTORS; 26 27 private static final MethodHandle BIND_CALL_SITE; 28 private static final MethodHandle BIND_INIT_CALL_SITE; 29 private static final MethodHandle EXCEPTION_HANDLER; 30 private static final MethodHandle GET_SHADOW; 31 32 static { 33 try { 34 MethodHandles.Lookup lookup = MethodHandles.lookup(); 35 36 BIND_CALL_SITE = lookup.findStatic(InvokeDynamicSupport.class, "bindCallSite", 37 methodType(MethodHandle.class, MethodCallSite.class)); 38 BIND_INIT_CALL_SITE = lookup.findStatic(InvokeDynamicSupport.class, "bindInitCallSite", 39 methodType(MethodHandle.class, RoboCallSite.class)); 40 MethodHandle cleanStackTrace = lookup.findStatic(RobolectricInternals.class, "cleanStackTrace", 41 methodType(Throwable.class, Throwable.class)); 42 EXCEPTION_HANDLER = filterArguments(throwException(void.class, Throwable.class), 0, cleanStackTrace); 43 GET_SHADOW = lookup.findVirtual(ShadowedObject.class, "$$robo$getData", methodType(Object.class)); 44 } catch (NoSuchMethodException | IllegalAccessException e) { 45 throw new AssertionError(e); 46 } 47 } 48 49 @SuppressWarnings("UnusedDeclaration") bootstrapInit(MethodHandles.Lookup caller, String name, MethodType type)50 public static CallSite bootstrapInit(MethodHandles.Lookup caller, String name, MethodType type) { 51 RoboCallSite site = new RoboCallSite(type, caller.lookupClass()); 52 53 bindInitCallSite(site); 54 55 return site; 56 } 57 58 @SuppressWarnings("UnusedDeclaration") bootstrap(MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original)59 public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type, 60 MethodHandle original) throws IllegalAccessException { 61 MethodCallSite site = new MethodCallSite(caller.lookupClass(), type, name, original, REGULAR); 62 63 bindCallSite(site); 64 65 return site; 66 } 67 68 @SuppressWarnings("UnusedDeclaration") bootstrapStatic(MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original)69 public static CallSite bootstrapStatic(MethodHandles.Lookup caller, String name, MethodType type, 70 MethodHandle original) throws IllegalAccessException { 71 MethodCallSite site = new MethodCallSite(caller.lookupClass(), type, name, original, STATIC); 72 73 bindCallSite(site); 74 75 return site; 76 } 77 78 @SuppressWarnings("UnusedDeclaration") bootstrapIntrinsic(MethodHandles.Lookup caller, String name, MethodType type, String callee)79 public static CallSite bootstrapIntrinsic(MethodHandles.Lookup caller, String name, 80 MethodType type, String callee) throws IllegalAccessException { 81 82 MethodHandle mh = getMethodHandle(callee, name, type); 83 if (mh == null) { 84 throw new IllegalArgumentException("Could not find intrinsic for " + callee + ":" + name); 85 } 86 87 return new ConstantCallSite(mh.asType(type)); 88 } 89 90 private static final MethodHandle NOTHING = constant(Void.class, null).asType(methodType(void.class)); 91 getMethodHandle(String className, String methodName, MethodType type)92 private static MethodHandle getMethodHandle(String className, String methodName, MethodType type) { 93 Interceptor interceptor = INTERCEPTORS.findInterceptor(className, methodName); 94 if (interceptor != null) { 95 try { 96 // reload interceptor in sandbox... 97 Class<Interceptor> theClass = 98 (Class<Interceptor>) ReflectionHelpers.loadClass( 99 RobolectricInternals.getClassLoader(), 100 interceptor.getClass().getName()).asSubclass(Interceptor.class); 101 return ReflectionHelpers.newInstance(theClass).getMethodHandle(methodName, type); 102 } catch (NoSuchMethodException | IllegalAccessException e) { 103 throw new RuntimeException(e); 104 } 105 } 106 107 if (type.parameterCount() != 0) { 108 return dropArguments(NOTHING, 0, type.parameterArray()); 109 } else { 110 return NOTHING; 111 } 112 } 113 bindInitCallSite(RoboCallSite site)114 private static MethodHandle bindInitCallSite(RoboCallSite site) { 115 MethodHandle mh = RobolectricInternals.getShadowCreator(site.getTheClass()); 116 return bindWithFallback(site, mh, BIND_INIT_CALL_SITE); 117 } 118 bindCallSite(MethodCallSite site)119 private static MethodHandle bindCallSite(MethodCallSite site) throws IllegalAccessException { 120 MethodHandle mh = 121 RobolectricInternals.findShadowMethodHandle(site.getTheClass(), site.getName(), site.type(), 122 site.isStatic()); 123 124 if (mh == null) { 125 // call original code 126 mh = site.getOriginal(); 127 } else if (mh == ShadowWrangler.DO_NOTHING) { 128 // no-op 129 mh = dropArguments(mh, 0, site.type().parameterList()); 130 } else if (!site.isStatic()) { 131 // drop arg 0 (this) for static methods 132 Class<?> shadowType = mh.type().parameterType(0); 133 mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(shadowType, site.thisType()))); 134 } 135 136 try { 137 return bindWithFallback(site, cleanStackTraces(mh), BIND_CALL_SITE); 138 } catch (Throwable t) { 139 // The error that bubbles up is currently not very helpful so we print any error messages 140 // here 141 t.printStackTrace(); 142 System.err.println(site.getTheClass()); 143 throw t; 144 } 145 } 146 bindWithFallback(RoboCallSite site, MethodHandle mh, MethodHandle fallback)147 private static MethodHandle bindWithFallback(RoboCallSite site, MethodHandle mh, 148 MethodHandle fallback) { 149 SwitchPoint switchPoint = getInvalidator(site.getTheClass()); 150 MethodType type = site.type(); 151 152 MethodHandle boundFallback = foldArguments(exactInvoker(type), fallback.bindTo(site)); 153 try { 154 mh = switchPoint.guardWithTest(mh.asType(type), boundFallback); 155 } catch (WrongMethodTypeException e) { 156 if (site instanceof MethodCallSite) { 157 MethodCallSite methodCallSite = (MethodCallSite) site; 158 throw new RuntimeException("failed to bind " + methodCallSite.thisType() + "." 159 + methodCallSite.getName(), e); 160 } else { 161 throw e; 162 } 163 } 164 165 site.setTarget(mh); 166 return mh; 167 } 168 getInvalidator(Class<?> cl)169 private static SwitchPoint getInvalidator(Class<?> cl) { 170 return RobolectricInternals.getShadowInvalidator().getSwitchPoint(cl); 171 } 172 cleanStackTraces(MethodHandle mh)173 private static MethodHandle cleanStackTraces(MethodHandle mh) { 174 MethodType type = EXCEPTION_HANDLER.type().changeReturnType(mh.type().returnType()); 175 return catchException(mh, Throwable.class, EXCEPTION_HANDLER.asType(type)); 176 } 177 } 178