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.PerfStatsCollector; 22 import org.robolectric.util.ReflectionHelpers; 23 24 @SuppressWarnings("RethrowReflectiveOperationExceptionAsLinkageError") 25 public class InvokeDynamicSupport { 26 @SuppressWarnings("unused") 27 private static Interceptors INTERCEPTORS; 28 29 private static final MethodHandle BIND_CALL_SITE; 30 private static final MethodHandle BIND_INIT_CALL_SITE; 31 private static final MethodHandle EXCEPTION_HANDLER; 32 private static final MethodHandle GET_SHADOW; 33 34 /** 35 * Represents the boolean 'true' as an integer. Due to a JVM bug, invokedynamic bootstrap methods 36 * currently do not support extra primitive boolean parameters. Integers are required to convey 37 * booleans. 38 * 39 * <p>See https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510 40 */ 41 private static final int BOOLEAN_TRUE = 1; 42 43 static { 44 try { 45 MethodHandles.Lookup lookup = MethodHandles.lookup(); 46 47 BIND_CALL_SITE = 48 lookup.findStatic( 49 InvokeDynamicSupport.class, 50 "bindCallSite", 51 methodType(MethodHandle.class, MethodCallSite.class)); 52 BIND_INIT_CALL_SITE = 53 lookup.findStatic( 54 InvokeDynamicSupport.class, 55 "bindInitCallSite", 56 methodType(MethodHandle.class, RoboCallSite.class)); 57 MethodHandle cleanStackTrace = 58 lookup.findStatic( 59 RobolectricInternals.class, 60 "cleanStackTrace", 61 methodType(Throwable.class, Throwable.class)); 62 EXCEPTION_HANDLER = 63 filterArguments(throwException(void.class, Throwable.class), 0, cleanStackTrace); 64 GET_SHADOW = 65 lookup.findVirtual(ShadowedObject.class, "$$robo$getData", methodType(Object.class)); 66 } catch (NoSuchMethodException | IllegalAccessException e) { 67 throw new AssertionError(e); 68 } 69 } 70 71 @SuppressWarnings("UnusedDeclaration") bootstrapInit(MethodHandles.Lookup caller, String name, MethodType type)72 public static CallSite bootstrapInit(MethodHandles.Lookup caller, String name, MethodType type) { 73 return PerfStatsCollector.getInstance() 74 .measure( 75 "invokedynamic bootstrap init", 76 () -> { 77 RoboCallSite site = new RoboCallSite(type, caller.lookupClass()); 78 79 bindInitCallSite(site); 80 81 return site; 82 }); 83 } 84 85 @SuppressWarnings("UnusedDeclaration") bootstrap( MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original, int isNative )86 public static CallSite bootstrap( 87 MethodHandles.Lookup caller, 88 String name, 89 MethodType type, 90 MethodHandle original, 91 int isNative /* 1 == originally native, 0 == not originally native */) 92 throws IllegalAccessException { 93 return PerfStatsCollector.getInstance() 94 .measure( 95 "invokedynamic bootstrap", 96 () -> { 97 MethodCallSite site = 98 new MethodCallSite( 99 caller.lookupClass(), 100 type, 101 name, 102 original, 103 REGULAR, 104 isNative == BOOLEAN_TRUE); 105 106 bindCallSite(site); 107 108 return site; 109 }); 110 } 111 112 @SuppressWarnings("UnusedDeclaration") 113 public static CallSite bootstrapStatic( 114 MethodHandles.Lookup caller, 115 String name, 116 MethodType type, 117 MethodHandle original, 118 int isNative /* 1 == originally native, 0 == not originally native */) 119 throws IllegalAccessException { 120 return PerfStatsCollector.getInstance() 121 .measure( 122 "invokedynamic bootstrap static", 123 () -> { 124 MethodCallSite site = 125 new MethodCallSite( 126 caller.lookupClass(), type, name, original, STATIC, isNative == BOOLEAN_TRUE); 127 128 bindCallSite(site); 129 130 return site; 131 }); 132 } 133 134 @SuppressWarnings("UnusedDeclaration") 135 public static CallSite bootstrapIntrinsic( 136 MethodHandles.Lookup caller, String name, MethodType type, String callee) 137 throws IllegalAccessException { 138 return PerfStatsCollector.getInstance() 139 .measure( 140 "invokedynamic bootstrap intrinsic", 141 () -> { 142 MethodHandle mh = getMethodHandle(callee, name, type); 143 if (mh == null) { 144 throw new IllegalArgumentException( 145 "Could not find intrinsic for " + callee + ":" + name); 146 } 147 return new ConstantCallSite(mh.asType(type)); 148 }); 149 } 150 151 private static final MethodHandle NOTHING = 152 constant(Void.class, null).asType(methodType(void.class)); 153 154 private static MethodHandle getMethodHandle( 155 String className, String methodName, MethodType type) { 156 Interceptor interceptor = INTERCEPTORS.findInterceptor(className, methodName); 157 if (interceptor != null) { 158 try { 159 // reload interceptor in sandbox... 160 Class<Interceptor> theClass = 161 (Class<Interceptor>) 162 ReflectionHelpers.loadClass( 163 RobolectricInternals.getClassLoader(), interceptor.getClass().getName()) 164 .asSubclass(Interceptor.class); 165 return ReflectionHelpers.newInstance(theClass).getMethodHandle(methodName, type); 166 } catch (NoSuchMethodException | IllegalAccessException e) { 167 throw new RuntimeException(e); 168 } 169 } 170 171 if (type.parameterCount() != 0) { 172 return dropArguments(NOTHING, 0, type.parameterArray()); 173 } else { 174 return NOTHING; 175 } 176 } 177 178 private static MethodHandle bindInitCallSite(RoboCallSite site) { 179 MethodHandle mh = RobolectricInternals.getShadowCreator(site.getTheClass()); 180 return bindWithFallback(site, mh, BIND_INIT_CALL_SITE); 181 } 182 183 private static MethodHandle bindCallSite(MethodCallSite site) throws IllegalAccessException { 184 MethodHandle mh = 185 RobolectricInternals.findShadowMethodHandle( 186 site.getTheClass(), site.getName(), site.type(), site.isStatic(), site.isNative()); 187 188 if (mh == null) { 189 // call original code 190 mh = site.getOriginal(); 191 } else if (mh == ShadowWrangler.DO_NOTHING) { 192 // no-op 193 mh = dropArguments(mh, 0, site.type().parameterList()); 194 } else if (!site.isStatic()) { 195 // drop arg 0 (this) for static methods 196 Class<?> mhType = mh.type().parameterType(0); 197 // At this point, thisType is either a shadow type, or in the case of native method 198 // invocations, it can be equivalent to the original type. 199 if (!mhType.equals(site.getTheClass())) { 200 // Only invoke getShadow if the method is on class that is decoupled from the original. 201 mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(mhType, site.thisType()))); 202 } 203 } 204 205 try { 206 return bindWithFallback(site, cleanStackTraces(mh), BIND_CALL_SITE); 207 } catch (Throwable t) { 208 // The error that bubbles up is currently not very helpful so we print any error messages 209 // here 210 t.printStackTrace(); 211 System.err.println(site.getTheClass()); 212 throw t; 213 } 214 } 215 216 private static MethodHandle bindWithFallback( 217 RoboCallSite site, MethodHandle mh, MethodHandle fallback) { 218 SwitchPoint switchPoint = getInvalidator(site.getTheClass()); 219 MethodType type = site.type(); 220 221 MethodHandle boundFallback = foldArguments(exactInvoker(type), fallback.bindTo(site)); 222 try { 223 mh = switchPoint.guardWithTest(mh.asType(type), boundFallback); 224 } catch (WrongMethodTypeException e) { 225 if (site instanceof MethodCallSite) { 226 MethodCallSite methodCallSite = (MethodCallSite) site; 227 throw new RuntimeException( 228 "failed to bind " + methodCallSite.thisType() + "." + methodCallSite.getName(), e); 229 } else { 230 throw e; 231 } 232 } 233 234 site.setTarget(mh); 235 return mh; 236 } 237 238 private static SwitchPoint getInvalidator(Class<?> cl) { 239 return RobolectricInternals.getShadowInvalidator().getSwitchPoint(cl); 240 } 241 242 private static MethodHandle cleanStackTraces(MethodHandle mh) { 243 MethodType type = EXCEPTION_HANDLER.type().changeReturnType(mh.type().returnType()); 244 return catchException(mh, Throwable.class, EXCEPTION_HANDLER.asType(type)); 245 } 246 } 247