• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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