package org.robolectric.internal.bytecode;
import org.robolectric.util.ReflectionHelpers;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SwitchPoint;
import static java.lang.invoke.MethodHandles.catchException;
import static java.lang.invoke.MethodHandles.constant;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.exactInvoker;
import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.foldArguments;
import static java.lang.invoke.MethodHandles.throwException;
import static java.lang.invoke.MethodType.methodType;
import static org.robolectric.internal.bytecode.MethodCallSite.Kind.REGULAR;
import static org.robolectric.internal.bytecode.MethodCallSite.Kind.STATIC;
public class InvokeDynamicSupport {
@SuppressWarnings("unused")
private static Interceptors INTERCEPTORS;
private static final MethodHandle BIND_CALL_SITE;
private static final MethodHandle BIND_INIT_CALL_SITE;
private static final MethodHandle EXCEPTION_HANDLER;
private static final MethodHandle GET_SHADOW;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
BIND_CALL_SITE = lookup.findStatic(InvokeDynamicSupport.class, "bindCallSite",
methodType(MethodHandle.class, MethodCallSite.class));
BIND_INIT_CALL_SITE = lookup.findStatic(InvokeDynamicSupport.class, "bindInitCallSite",
methodType(MethodHandle.class, RoboCallSite.class));
MethodHandle cleanStackTrace = lookup.findStatic(RobolectricInternals.class, "cleanStackTrace",
methodType(Throwable.class, Throwable.class));
EXCEPTION_HANDLER = filterArguments(throwException(void.class, Throwable.class), 0, cleanStackTrace);
GET_SHADOW = lookup.findVirtual(ShadowedObject.class, "$$robo$getData", methodType(Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrapInit(MethodHandles.Lookup caller, String name, MethodType type) {
RoboCallSite site = new RoboCallSite(type, caller.lookupClass());
bindInitCallSite(site);
return site;
}
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type,
MethodHandle original) throws IllegalAccessException {
MethodCallSite site = new MethodCallSite(type, caller.lookupClass(), name, original, REGULAR);
bindCallSite(site);
return site;
}
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrapStatic(MethodHandles.Lookup caller, String name, MethodType type,
MethodHandle original) throws IllegalAccessException {
MethodCallSite site = new MethodCallSite(type, caller.lookupClass(), name, original, STATIC);
bindCallSite(site);
return site;
}
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrapIntrinsic(MethodHandles.Lookup caller, String name,
MethodType type, String callee) throws IllegalAccessException {
MethodHandle mh = getMethodHandle(callee, name, type);
if (mh == null) {
throw new IllegalArgumentException("Could not find intrinsic for " + callee + ":" + name);
}
return new ConstantCallSite(mh.asType(type));
}
private static final MethodHandle NOTHING = constant(Void.class, null).asType(methodType(void.class));
private static MethodHandle getMethodHandle(String className, String methodName, MethodType type) {
Interceptor interceptor = INTERCEPTORS.findInterceptor(className, methodName);
if (interceptor != null) {
try {
// reload interceptor in sandbox...
Class<Interceptor> theClass =
(Class<Interceptor>) ReflectionHelpers.loadClass(
RobolectricInternals.getClassLoader(),
interceptor.getClass().getName()).asSubclass(Interceptor.class);
return ReflectionHelpers.newInstance(theClass).getMethodHandle(methodName, type);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
if (type.parameterCount() != 0) {
return dropArguments(NOTHING, 0, type.parameterArray());
} else {
return NOTHING;
}
}
private static MethodHandle bindInitCallSite(RoboCallSite site) {
MethodHandle mh = RobolectricInternals.getShadowCreator(site.getCaller());
return bindWithFallback(mh, site, BIND_INIT_CALL_SITE);
}
private static MethodHandle bindCallSite(MethodCallSite site) throws IllegalAccessException {
MethodHandle mh =
RobolectricInternals.findShadowMethod(site.getCaller(), site.getName(), site.type(),
site.isStatic());
if (mh == null) {
// Call original code and make sure to clean stack traces
mh = cleanStackTraces(site.getOriginal());
} else if (mh == ShadowWrangler.DO_NOTHING) {
mh = dropArguments(mh, 0, site.type().parameterList());
} else if (!site.isStatic()) {
Class<?> shadowType = mh.type().parameterType(0);
mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(shadowType, site.thisType())));
}
try {
return bindWithFallback(mh, site, BIND_CALL_SITE);
} catch (Throwable t) {
// The error that bubbles up is currently not very helpful so we print any error messages
// here
t.printStackTrace();
System.err.println(site.getCaller());
throw t;
}
}
private static MethodHandle bindWithFallback(MethodHandle mh, RoboCallSite site, MethodHandle fallback) {
SwitchPoint switchPoint = getInvalidator(site.getCaller());
MethodType type = site.type();
MethodHandle boundFallback = foldArguments(exactInvoker(type), fallback.bindTo(site));
mh = switchPoint.guardWithTest(mh.asType(type), boundFallback);
site.setTarget(mh);
return mh;
}
private static SwitchPoint getInvalidator(Class<?> cl) {
return RobolectricInternals.getShadowInvalidator().getSwitchPoint(cl);
}
private static MethodHandle cleanStackTraces(MethodHandle mh) {
MethodType type = EXCEPTION_HANDLER.type().changeReturnType(mh.type().returnType());
return catchException(mh, Throwable.class, EXCEPTION_HANDLER.asType(type));
}
}