|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package java.lang.reflect; |
|
|
|
import jdk.internal.org.objectweb.asm.ClassWriter; |
|
import jdk.internal.org.objectweb.asm.Label; |
|
import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
import jdk.internal.org.objectweb.asm.Opcodes; |
|
import jdk.internal.org.objectweb.asm.Type; |
|
import sun.security.action.GetBooleanAction; |
|
|
|
import java.io.IOException; |
|
import java.lang.invoke.MethodType; |
|
import java.nio.file.Files; |
|
import java.nio.file.Path; |
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
import java.util.HashMap; |
|
import java.util.LinkedHashMap; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.ListIterator; |
|
import java.util.Map; |
|
|
|
import static jdk.internal.org.objectweb.asm.Opcodes.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class ProxyGenerator extends ClassWriter { |
|
|
|
private static final String JL_CLASS = "java/lang/Class"; |
|
private static final String JL_OBJECT = "java/lang/Object"; |
|
private static final String JL_THROWABLE = "java/lang/Throwable"; |
|
private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException"; |
|
private static final String JL_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException"; |
|
|
|
private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError"; |
|
private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException"; |
|
private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError"; |
|
private static final String JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup"; |
|
private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles"; |
|
|
|
private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler"; |
|
private static final String JLR_PROXY = "java/lang/reflect/Proxy"; |
|
private static final String JLR_UNDECLARED_THROWABLE_EX = "java/lang/reflect/UndeclaredThrowableException"; |
|
|
|
private static final String LJL_CLASS = "Ljava/lang/Class;"; |
|
private static final String LJLR_METHOD = "Ljava/lang/reflect/Method;"; |
|
private static final String LJLR_INVOCATION_HANDLER = "Ljava/lang/reflect/InvocationHandler;"; |
|
|
|
private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V"; |
|
|
|
private static final String NAME_CTOR = "<init>"; |
|
private static final String NAME_CLINIT = "<clinit>"; |
|
private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; |
|
|
|
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; |
|
|
|
|
|
|
|
*/ |
|
private static final String handlerFieldName = "h"; |
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
private static final boolean saveGeneratedFiles = |
|
java.security.AccessController.doPrivileged( |
|
new GetBooleanAction( |
|
"jdk.proxy.ProxyGenerator.saveGeneratedFiles")); |
|
|
|
|
|
private static final ProxyMethod hashCodeMethod; |
|
private static final ProxyMethod equalsMethod; |
|
private static final ProxyMethod toStringMethod; |
|
|
|
static { |
|
try { |
|
hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), "m0"); |
|
equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), "m1"); |
|
toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), "m2"); |
|
} catch (NoSuchMethodException e) { |
|
throw new NoSuchMethodError(e.getMessage()); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private final ClassLoader loader; |
|
|
|
|
|
|
|
*/ |
|
private final String className; |
|
|
|
|
|
|
|
*/ |
|
private final List<Class<?>> interfaces; |
|
|
|
|
|
|
|
*/ |
|
private final int accessFlags; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private final Map<String, List<ProxyMethod>> proxyMethods = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
*/ |
|
private int proxyMethodCount = 3; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private ProxyGenerator(ClassLoader loader, String className, List<Class<?>> interfaces, |
|
int accessFlags) { |
|
super(ClassWriter.COMPUTE_FRAMES); |
|
this.loader = loader; |
|
this.className = className; |
|
this.interfaces = interfaces; |
|
this.accessFlags = accessFlags; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
static byte[] generateProxyClass(ClassLoader loader, |
|
final String name, |
|
List<Class<?>> interfaces, |
|
int accessFlags) { |
|
ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); |
|
final byte[] classFile = gen.generateClassFile(); |
|
|
|
if (saveGeneratedFiles) { |
|
java.security.AccessController.doPrivileged( |
|
new java.security.PrivilegedAction<Void>() { |
|
public Void run() { |
|
try { |
|
int i = name.lastIndexOf('.'); |
|
Path path; |
|
if (i > 0) { |
|
Path dir = Path.of(dotToSlash(name.substring(0, i))); |
|
Files.createDirectories(dir); |
|
path = dir.resolve(name.substring(i + 1) + ".class"); |
|
} else { |
|
path = Path.of(name + ".class"); |
|
} |
|
Files.write(path, classFile); |
|
return null; |
|
} catch (IOException e) { |
|
throw new InternalError( |
|
"I/O exception saving generated file: " + e); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
return classFile; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static String[] typeNames(List<Class<?>> classes) { |
|
if (classes == null || classes.size() == 0) |
|
return null; |
|
int size = classes.size(); |
|
String[] ifaces = new String[size]; |
|
for (int i = 0; i < size; i++) |
|
ifaces[i] = dotToSlash(classes.get(i).getName()); |
|
return ifaces; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static void checkReturnTypes(List<ProxyMethod> methods) { |
|
|
|
|
|
|
|
|
|
*/ |
|
if (methods.size() < 2) { |
|
return; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
LinkedList<Class<?>> uncoveredReturnTypes = new LinkedList<>(); |
|
|
|
nextNewReturnType: |
|
for (ProxyMethod pm : methods) { |
|
Class<?> newReturnType = pm.returnType; |
|
if (newReturnType.isPrimitive()) { |
|
throw new IllegalArgumentException( |
|
"methods with same signature " + |
|
pm.shortSignature + |
|
" but incompatible return types: " + |
|
newReturnType.getName() + " and others"); |
|
} |
|
boolean added = false; |
|
|
|
|
|
|
|
|
|
*/ |
|
ListIterator<Class<?>> liter = uncoveredReturnTypes.listIterator(); |
|
while (liter.hasNext()) { |
|
Class<?> uncoveredReturnType = liter.next(); |
|
|
|
|
|
|
|
|
|
*/ |
|
if (newReturnType.isAssignableFrom(uncoveredReturnType)) { |
|
assert !added; |
|
continue nextNewReturnType; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (uncoveredReturnType.isAssignableFrom(newReturnType)) { |
|
|
|
if (!added) { |
|
liter.set(newReturnType); |
|
added = true; |
|
} else { |
|
liter.remove(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (!added) { |
|
uncoveredReturnTypes.add(newReturnType); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
if (uncoveredReturnTypes.size() > 1) { |
|
ProxyMethod pm = methods.get(0); |
|
throw new IllegalArgumentException( |
|
"methods with same signature " + |
|
pm.shortSignature + |
|
" but incompatible return types: " + uncoveredReturnTypes); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static List<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) { |
|
List<Class<?>> uniqueList = new ArrayList<>(); |
|
// unique exceptions to catch |
|
|
|
uniqueList.add(Error.class); |
|
uniqueList.add(RuntimeException.class); |
|
|
|
nextException: |
|
for (Class<?> ex : exceptions) { |
|
if (ex.isAssignableFrom(Throwable.class)) { |
|
|
|
|
|
|
|
|
|
*/ |
|
uniqueList.clear(); |
|
break; |
|
} else if (!Throwable.class.isAssignableFrom(ex)) { |
|
|
|
|
|
*/ |
|
continue; |
|
} |
|
|
|
|
|
|
|
*/ |
|
for (int j = 0; j < uniqueList.size(); ) { |
|
Class<?> ex2 = uniqueList.get(j); |
|
if (ex2.isAssignableFrom(ex)) { |
|
|
|
|
|
|
|
*/ |
|
continue nextException; |
|
} else if (ex.isAssignableFrom(ex2)) { |
|
|
|
|
|
|
|
*/ |
|
uniqueList.remove(j); |
|
} else { |
|
j++; |
|
} |
|
} |
|
|
|
uniqueList.add(ex); |
|
} |
|
return uniqueList; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static String dotToSlash(String name) { |
|
return name.replace('.', '/'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int getWordsPerType(Class<?> type) { |
|
if (type == long.class || type == double.class) { |
|
return 2; |
|
} else { |
|
return 1; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static void collectCompatibleTypes(Class<?>[] from, |
|
Class<?>[] with, |
|
List<Class<?>> list) { |
|
for (Class<?> fc : from) { |
|
if (!list.contains(fc)) { |
|
for (Class<?> wc : with) { |
|
if (wc.isAssignableFrom(fc)) { |
|
list.add(fc); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected ClassLoader getClassLoader() { |
|
return loader; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] generateClassFile() { |
|
visit(V14, accessFlags, dotToSlash(className), null, |
|
JLR_PROXY, typeNames(interfaces)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
addProxyMethod(hashCodeMethod); |
|
addProxyMethod(equalsMethod); |
|
addProxyMethod(toStringMethod); |
|
|
|
|
|
|
|
*/ |
|
for (Class<?> intf : interfaces) { |
|
for (Method m : intf.getMethods()) { |
|
if (!Modifier.isStatic(m.getModifiers())) { |
|
addProxyMethod(m, intf); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
for (List<ProxyMethod> sigmethods : proxyMethods.values()) { |
|
checkReturnTypes(sigmethods); |
|
} |
|
|
|
generateConstructor(); |
|
|
|
for (List<ProxyMethod> sigmethods : proxyMethods.values()) { |
|
for (ProxyMethod pm : sigmethods) { |
|
|
|
visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName, |
|
LJLR_METHOD, null, null); |
|
|
|
|
|
pm.generateMethod(this, className); |
|
} |
|
} |
|
|
|
generateStaticInitializer(); |
|
generateLookupAccessor(); |
|
return toByteArray(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void addProxyMethod(Method m, Class<?> fromClass) { |
|
Class<?> returnType = m.getReturnType(); |
|
Class<?>[] exceptionTypes = m.getExceptionTypes(); |
|
|
|
String sig = m.toShortSignature(); |
|
List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, |
|
(f) -> new ArrayList<>(3)); |
|
for (ProxyMethod pm : sigmethods) { |
|
if (returnType == pm.returnType) { |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
List<Class<?>> legalExceptions = new ArrayList<>(); |
|
collectCompatibleTypes( |
|
exceptionTypes, pm.exceptionTypes, legalExceptions); |
|
collectCompatibleTypes( |
|
pm.exceptionTypes, exceptionTypes, legalExceptions); |
|
pm.exceptionTypes = legalExceptions.toArray(EMPTY_CLASS_ARRAY); |
|
return; |
|
} |
|
} |
|
sigmethods.add(new ProxyMethod(m, sig, m.getParameterTypes(), returnType, |
|
exceptionTypes, fromClass, |
|
"m" + proxyMethodCount++)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void addProxyMethod(ProxyMethod pm) { |
|
String sig = pm.shortSignature; |
|
List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, |
|
(f) -> new ArrayList<>(3)); |
|
sigmethods.add(pm); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void generateConstructor() { |
|
MethodVisitor ctor = visitMethod(Modifier.PUBLIC, NAME_CTOR, |
|
MJLR_INVOCATIONHANDLER, null, null); |
|
ctor.visitParameter(null, 0); |
|
ctor.visitCode(); |
|
ctor.visitVarInsn(ALOAD, 0); |
|
ctor.visitVarInsn(ALOAD, 1); |
|
ctor.visitMethodInsn(INVOKESPECIAL, JLR_PROXY, NAME_CTOR, |
|
MJLR_INVOCATIONHANDLER, false); |
|
ctor.visitInsn(RETURN); |
|
|
|
|
|
ctor.visitMaxs(-1, -1); |
|
ctor.visitEnd(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void generateStaticInitializer() { |
|
|
|
MethodVisitor mv = visitMethod(Modifier.STATIC, NAME_CLINIT, |
|
"()V", null, null); |
|
mv.visitCode(); |
|
Label L_startBlock = new Label(); |
|
Label L_endBlock = new Label(); |
|
Label L_NoMethodHandler = new Label(); |
|
Label L_NoClassHandler = new Label(); |
|
|
|
mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoMethodHandler, |
|
JL_NO_SUCH_METHOD_EX); |
|
mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoClassHandler, |
|
JL_CLASS_NOT_FOUND_EX); |
|
|
|
mv.visitLabel(L_startBlock); |
|
for (List<ProxyMethod> sigmethods : proxyMethods.values()) { |
|
for (ProxyMethod pm : sigmethods) { |
|
pm.codeFieldInitialization(mv, className); |
|
} |
|
} |
|
mv.visitInsn(RETURN); |
|
mv.visitLabel(L_endBlock); |
|
// Generate exception handler |
|
|
|
mv.visitLabel(L_NoMethodHandler); |
|
mv.visitVarInsn(ASTORE, 1); |
|
mv.visitTypeInsn(Opcodes.NEW, JL_NO_SUCH_METHOD_ERROR); |
|
mv.visitInsn(DUP); |
|
mv.visitVarInsn(ALOAD, 1); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, |
|
"getMessage", "()Ljava/lang/String;", false); |
|
mv.visitMethodInsn(INVOKESPECIAL, JL_NO_SUCH_METHOD_ERROR, |
|
"<init>", "(Ljava/lang/String;)V", false); |
|
mv.visitInsn(ATHROW); |
|
|
|
mv.visitLabel(L_NoClassHandler); |
|
mv.visitVarInsn(ASTORE, 1); |
|
mv.visitTypeInsn(Opcodes.NEW, JL_NO_CLASS_DEF_FOUND_ERROR); |
|
mv.visitInsn(DUP); |
|
mv.visitVarInsn(ALOAD, 1); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, |
|
"getMessage", "()Ljava/lang/String;", false); |
|
mv.visitMethodInsn(INVOKESPECIAL, JL_NO_CLASS_DEF_FOUND_ERROR, |
|
"<init>", "(Ljava/lang/String;)V", false); |
|
mv.visitInsn(ATHROW); |
|
|
|
|
|
mv.visitMaxs(-1, -1); |
|
mv.visitEnd(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void generateLookupAccessor() { |
|
MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR, |
|
"(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null, |
|
new String[] { JL_ILLEGAL_ACCESS_EX }); |
|
mv.visitCode(); |
|
Label L_illegalAccess = new Label(); |
|
|
|
mv.visitVarInsn(ALOAD, 0); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass", |
|
"()Ljava/lang/Class;", false); |
|
mv.visitLdcInsn(Type.getType(Proxy.class)); |
|
mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess); |
|
mv.visitVarInsn(ALOAD, 0); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess", |
|
"()Z", false); |
|
mv.visitJumpInsn(IFEQ, L_illegalAccess); |
|
mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup", |
|
"()Ljava/lang/invoke/MethodHandles$Lookup;", false); |
|
mv.visitInsn(ARETURN); |
|
|
|
mv.visitLabel(L_illegalAccess); |
|
mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX); |
|
mv.visitInsn(DUP); |
|
mv.visitVarInsn(ALOAD, 0); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString", |
|
"()Ljava/lang/String;", false); |
|
mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX, |
|
"<init>", "(Ljava/lang/String;)V", false); |
|
mv.visitInsn(ATHROW); |
|
|
|
|
|
mv.visitMaxs(-1, -1); |
|
mv.visitEnd(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class ProxyMethod { |
|
|
|
private final Method method; |
|
private final String shortSignature; |
|
private final Class<?> fromClass; |
|
private final Class<?>[] parameterTypes; |
|
private final Class<?> returnType; |
|
private final String methodFieldName; |
|
private Class<?>[] exceptionTypes; |
|
|
|
private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes, |
|
Class<?> returnType, Class<?>[] exceptionTypes, |
|
Class<?> fromClass, String methodFieldName) { |
|
this.method = method; |
|
this.shortSignature = sig; |
|
this.parameterTypes = parameterTypes; |
|
this.returnType = returnType; |
|
this.exceptionTypes = exceptionTypes; |
|
this.fromClass = fromClass; |
|
this.methodFieldName = methodFieldName; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private ProxyMethod(Method method, String methodFieldName) { |
|
this(method, method.toShortSignature(), |
|
method.getParameterTypes(), method.getReturnType(), |
|
method.getExceptionTypes(), method.getDeclaringClass(), methodFieldName); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void generateMethod(ClassWriter cw, String className) { |
|
MethodType mt = MethodType.methodType(returnType, parameterTypes); |
|
String desc = mt.toMethodDescriptorString(); |
|
int accessFlags = ACC_PUBLIC | ACC_FINAL; |
|
if (method.isVarArgs()) accessFlags |= ACC_VARARGS; |
|
|
|
MethodVisitor mv = cw.visitMethod(accessFlags, |
|
method.getName(), desc, null, |
|
typeNames(Arrays.asList(exceptionTypes))); |
|
|
|
int[] parameterSlot = new int[parameterTypes.length]; |
|
int nextSlot = 1; |
|
for (int i = 0; i < parameterSlot.length; i++) { |
|
parameterSlot[i] = nextSlot; |
|
nextSlot += getWordsPerType(parameterTypes[i]); |
|
} |
|
|
|
mv.visitCode(); |
|
Label L_startBlock = new Label(); |
|
Label L_endBlock = new Label(); |
|
Label L_RuntimeHandler = new Label(); |
|
Label L_ThrowableHandler = new Label(); |
|
|
|
List<Class<?>> catchList = computeUniqueCatchList(exceptionTypes); |
|
if (catchList.size() > 0) { |
|
for (Class<?> ex : catchList) { |
|
mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_RuntimeHandler, |
|
dotToSlash(ex.getName())); |
|
} |
|
|
|
mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_ThrowableHandler, |
|
JL_THROWABLE); |
|
} |
|
mv.visitLabel(L_startBlock); |
|
|
|
mv.visitVarInsn(ALOAD, 0); |
|
mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName, |
|
LJLR_INVOCATION_HANDLER); |
|
mv.visitVarInsn(ALOAD, 0); |
|
mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName, |
|
LJLR_METHOD); |
|
|
|
if (parameterTypes.length > 0) { |
|
|
|
emitIconstInsn(mv, parameterTypes.length); |
|
mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_OBJECT); |
|
for (int i = 0; i < parameterTypes.length; i++) { |
|
mv.visitInsn(DUP); |
|
emitIconstInsn(mv, i); |
|
codeWrapArgument(mv, parameterTypes[i], parameterSlot[i]); |
|
mv.visitInsn(Opcodes.AASTORE); |
|
} |
|
} else { |
|
mv.visitInsn(Opcodes.ACONST_NULL); |
|
} |
|
|
|
mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER, |
|
"invoke", |
|
"(Ljava/lang/Object;Ljava/lang/reflect/Method;" + |
|
"[Ljava/lang/Object;)Ljava/lang/Object;", true); |
|
|
|
if (returnType == void.class) { |
|
mv.visitInsn(POP); |
|
mv.visitInsn(RETURN); |
|
} else { |
|
codeUnwrapReturnValue(mv, returnType); |
|
} |
|
|
|
mv.visitLabel(L_endBlock); |
|
|
|
|
|
mv.visitLabel(L_RuntimeHandler); |
|
mv.visitInsn(ATHROW); |
|
|
|
mv.visitLabel(L_ThrowableHandler); |
|
mv.visitVarInsn(ASTORE, 1); |
|
mv.visitTypeInsn(Opcodes.NEW, JLR_UNDECLARED_THROWABLE_EX); |
|
mv.visitInsn(DUP); |
|
mv.visitVarInsn(ALOAD, 1); |
|
mv.visitMethodInsn(INVOKESPECIAL, JLR_UNDECLARED_THROWABLE_EX, |
|
"<init>", "(Ljava/lang/Throwable;)V", false); |
|
mv.visitInsn(ATHROW); |
|
|
|
mv.visitMaxs(-1, -1); |
|
mv.visitEnd(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void codeWrapArgument(MethodVisitor mv, Class<?> type, int slot) { |
|
if (type.isPrimitive()) { |
|
PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); |
|
|
|
if (type == int.class || |
|
type == boolean.class || |
|
type == byte.class || |
|
type == char.class || |
|
type == short.class) { |
|
mv.visitVarInsn(ILOAD, slot); |
|
} else if (type == long.class) { |
|
mv.visitVarInsn(LLOAD, slot); |
|
} else if (type == float.class) { |
|
mv.visitVarInsn(FLOAD, slot); |
|
} else if (type == double.class) { |
|
mv.visitVarInsn(DLOAD, slot); |
|
} else { |
|
throw new AssertionError(); |
|
} |
|
mv.visitMethodInsn(INVOKESTATIC, prim.wrapperClassName, "valueOf", |
|
prim.wrapperValueOfDesc, false); |
|
} else { |
|
mv.visitVarInsn(ALOAD, slot); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void codeUnwrapReturnValue(MethodVisitor mv, Class<?> type) { |
|
if (type.isPrimitive()) { |
|
PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); |
|
|
|
mv.visitTypeInsn(CHECKCAST, prim.wrapperClassName); |
|
mv.visitMethodInsn(INVOKEVIRTUAL, |
|
prim.wrapperClassName, |
|
prim.unwrapMethodName, prim.unwrapMethodDesc, false); |
|
|
|
if (type == int.class || |
|
type == boolean.class || |
|
type == byte.class || |
|
type == char.class || |
|
type == short.class) { |
|
mv.visitInsn(IRETURN); |
|
} else if (type == long.class) { |
|
mv.visitInsn(LRETURN); |
|
} else if (type == float.class) { |
|
mv.visitInsn(FRETURN); |
|
} else if (type == double.class) { |
|
mv.visitInsn(DRETURN); |
|
} else { |
|
throw new AssertionError(); |
|
} |
|
} else { |
|
mv.visitTypeInsn(CHECKCAST, dotToSlash(type.getName())); |
|
mv.visitInsn(ARETURN); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private void codeFieldInitialization(MethodVisitor mv, String className) { |
|
codeClassForName(mv, fromClass); |
|
|
|
mv.visitLdcInsn(method.getName()); |
|
|
|
emitIconstInsn(mv, parameterTypes.length); |
|
|
|
mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_CLASS); |
|
|
|
|
|
for (int i = 0; i < parameterTypes.length; i++) { |
|
mv.visitInsn(DUP); |
|
emitIconstInsn(mv, i); |
|
|
|
if (parameterTypes[i].isPrimitive()) { |
|
PrimitiveTypeInfo prim = |
|
PrimitiveTypeInfo.get(parameterTypes[i]); |
|
mv.visitFieldInsn(GETSTATIC, |
|
prim.wrapperClassName, "TYPE", LJL_CLASS); |
|
} else { |
|
codeClassForName(mv, parameterTypes[i]); |
|
} |
|
mv.visitInsn(Opcodes.AASTORE); |
|
} |
|
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, |
|
JL_CLASS, |
|
"getMethod", |
|
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", |
|
false); |
|
|
|
mv.visitFieldInsn(PUTSTATIC, |
|
dotToSlash(className), |
|
methodFieldName, LJLR_METHOD); |
|
} |
|
|
|
/* |
|
* =============== Code Generation Utility Methods =============== |
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void codeClassForName(MethodVisitor mv, Class<?> cl) { |
|
mv.visitLdcInsn(cl.getName()); |
|
mv.visitMethodInsn(INVOKESTATIC, |
|
JL_CLASS, |
|
"forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void emitIconstInsn(MethodVisitor mv, final int cst) { |
|
if (cst >= -1 && cst <= 5) { |
|
mv.visitInsn(Opcodes.ICONST_0 + cst); |
|
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { |
|
mv.visitIntInsn(Opcodes.BIPUSH, cst); |
|
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { |
|
mv.visitIntInsn(Opcodes.SIPUSH, cst); |
|
} else { |
|
mv.visitLdcInsn(cst); |
|
} |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return method.toShortString(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class PrimitiveTypeInfo { |
|
|
|
private static Map<Class<?>, PrimitiveTypeInfo> table = new HashMap<>(); |
|
|
|
static { |
|
add(byte.class, Byte.class); |
|
add(char.class, Character.class); |
|
add(double.class, Double.class); |
|
add(float.class, Float.class); |
|
add(int.class, Integer.class); |
|
add(long.class, Long.class); |
|
add(short.class, Short.class); |
|
add(boolean.class, Boolean.class); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private String wrapperClassName; |
|
|
|
|
|
*/ |
|
private String wrapperValueOfDesc; |
|
|
|
|
|
*/ |
|
private String unwrapMethodName; |
|
|
|
|
|
*/ |
|
private String unwrapMethodDesc; |
|
|
|
private PrimitiveTypeInfo(Class<?> primitiveClass, Class<?> wrapperClass) { |
|
assert primitiveClass.isPrimitive(); |
|
|
|
|
|
|
|
*/ |
|
String baseTypeString = |
|
Array.newInstance(primitiveClass, 0) |
|
.getClass().getName().substring(1); |
|
wrapperClassName = dotToSlash(wrapperClass.getName()); |
|
wrapperValueOfDesc = |
|
"(" + baseTypeString + ")L" + wrapperClassName + ";"; |
|
unwrapMethodName = primitiveClass.getName() + "Value"; |
|
unwrapMethodDesc = "()" + baseTypeString; |
|
} |
|
|
|
private static void add(Class<?> primitiveClass, Class<?> wrapperClass) { |
|
table.put(primitiveClass, |
|
new PrimitiveTypeInfo(primitiveClass, wrapperClass)); |
|
} |
|
|
|
public static PrimitiveTypeInfo get(Class<?> cl) { |
|
return table.get(cl); |
|
} |
|
} |
|
} |