/*
* Copyright 2003-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.runtime.callsite;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.CachedMethod;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import groovy.lang.GroovyRuntimeException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class CallSiteGenerator {
private static final String GRE = BytecodeHelper.getClassInternalName(ClassHelper.make(GroovyRuntimeException.class));
private CallSiteGenerator () {}
private static MethodVisitor writeMethod(ClassWriter cw, String name, int argumentCount, final String superClass, CachedMethod cachedMethod, String receiverType, String parameterDescription, boolean useArray) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "call" + name, "(L" + receiverType + ";" + parameterDescription + ")Ljava/lang/Object;", null, null);
mv.visitCode();
final Label tryStart = new Label();
mv.visitLabel(tryStart);
// call for checking if method is still valid
for (int i = 0; i < argumentCount; ++i) mv.visitVarInsn(Opcodes.ALOAD, i);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, superClass, "checkCall", "(Ljava/lang/Object;" + parameterDescription + ")Z");
Label l0 = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, l0);
// valid method branch
Class callClass = cachedMethod.getDeclaringClass().getTheClass();
boolean useInterface = callClass.isInterface();
String type = BytecodeHelper.getClassInternalName(callClass.getName());
String descriptor = BytecodeHelper.getMethodDescriptor(cachedMethod.getReturnType(), cachedMethod.getNativeParameterTypes());
// prepare call
int invokeMethodCode = Opcodes.INVOKEVIRTUAL;
if (cachedMethod.isStatic()) {
invokeMethodCode = Opcodes.INVOKESTATIC;
} else {
mv.visitVarInsn(Opcodes.ALOAD, 1);
BytecodeHelper.doCast(mv, callClass);
if (useInterface) invokeMethodCode = Opcodes.INVOKEINTERFACE;
}
Method method = cachedMethod.setAccessible();
Class<?>[] parameters = method.getParameterTypes();
int size = parameters.length;
for (int i = 0; i < size; i++) {
if (useArray) {
// unpack argument from Object[]
mv.visitVarInsn(Opcodes.ALOAD, 2);
BytecodeHelper.pushConstant(mv, i);
mv.visitInsn(Opcodes.AALOAD);
} else {
mv.visitVarInsn(Opcodes.ALOAD, i+2);
}
// cast argument to parameter class, inclusive unboxing
// for methods with primitive types
BytecodeHelper.doCast(mv, parameters[i]);
}
// make call
mv.visitMethodInsn(invokeMethodCode, type, cachedMethod.getName(), descriptor);
// produce result
BytecodeHelper.box(mv, cachedMethod.getReturnType());
if (cachedMethod.getReturnType() == void.class) {
mv.visitInsn(Opcodes.ACONST_NULL);
}
// return
mv.visitInsn(Opcodes.ARETURN);
// fall back after method change
mv.visitLabel(l0);
for (int i = 0; i < argumentCount; ++i) mv.visitVarInsn(Opcodes.ALOAD, i);
if (!useArray) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/ArrayUtil", "createArray", "(" + parameterDescription + ")[Ljava/lang/Object;");
}
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "defaultCall" + name, "(Lorg/codehaus/groovy/runtime/callsite/CallSite;L" + receiverType + ";[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitInsn(Opcodes.ARETURN);
// exception unwrapping for stackless exceptions
final Label tryEnd = new Label();
mv.visitLabel(tryEnd);
final Label catchStart = new Label();
mv.visitLabel(catchStart);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/ScriptBytecodeAdapter", "unwrap", "(Lgroovy/lang/GroovyRuntimeException;)Ljava/lang/Throwable;");
mv.visitInsn(Opcodes.ATHROW);
mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, GRE);
mv.visitMaxs(0, 0);
mv.visitEnd();
return mv;
}
public static void genCallWithFixedParams(ClassWriter cw, String name, final String superClass, CachedMethod cachedMethod, String receiverType ) {
if (cachedMethod.getParamsCount() > 4) return;
StringBuilder pdescb = new StringBuilder();
final int pc = cachedMethod.getParamsCount();
for (int i = 0; i != pc; ++i) pdescb.append("Ljava/lang/Object;");
writeMethod(cw,name,pc+2,superClass,cachedMethod,receiverType,pdescb.toString(),false);
}
public static void genCallXxxWithArray(ClassWriter cw, final String name, final String superClass, CachedMethod cachedMethod, String receiverType) {
writeMethod(cw,name,3,superClass,cachedMethod,receiverType,"[Ljava/lang/Object;",true);
}
private static void genConstructor(ClassWriter cw, final String superClass, String internalName) {
MethodVisitor mv;
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Lorg/codehaus/groovy/runtime/callsite/CallSite;Lgroovy/lang/MetaClassImpl;Lgroovy/lang/MetaMethod;[Ljava/lang/Class;Ljava/lang/reflect/Constructor;)V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn(Opcodes.ALOAD, 4);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClass, "<init>", "(Lorg/codehaus/groovy/runtime/callsite/CallSite;Lgroovy/lang/MetaClassImpl;Lgroovy/lang/MetaMethod;[Ljava/lang/Class;)V");
mv.visitVarInsn(Opcodes.ALOAD, 5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, internalName, "__constructor__", "Ljava/lang/reflect/Constructor;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
public static byte[] genPogoMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
String internalName = name.replace('.', '/');
cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, internalName, null, "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", null);
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "__constructor__", "Ljava/lang/reflect/Constructor;", null, null);
genConstructor(cw, "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", internalName);
genCallXxxWithArray(cw, "Current", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "groovy/lang/GroovyObject");
genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "java/lang/Object");
genCallWithFixedParams(cw, "Current", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "groovy/lang/GroovyObject");
genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "java/lang/Object");
cw.visitEnd();
return cw.toByteArray();
}
public static byte[] genPojoMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
String internalName = name.replace('.', '/');
cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, internalName, null, "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", null);
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "__constructor__", "Ljava/lang/reflect/Constructor;", null, null);
genConstructor(cw, "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", internalName);
genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", cachedMethod, "java/lang/Object");
genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", cachedMethod, "java/lang/Object");
cw.visitEnd();
return cw.toByteArray();
}
public static byte[] genStaticMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
String internalName = name.replace('.', '/');
cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, internalName, null, "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", null);
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "__constructor__", "Ljava/lang/reflect/Constructor;", null, null);
genConstructor(cw, "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", internalName);
genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Object");
genCallXxxWithArray(cw, "Static", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Class");
genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Object");
genCallWithFixedParams(cw, "Static", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Class");
cw.visitEnd();
return cw.toByteArray();
}
public static Constructor compilePogoMethod(CachedMethod cachedMethod) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final CachedClass declClass = cachedMethod.getDeclaringClass();
final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
final byte[] bytes = genPogoMetaMethodSite(cachedMethod, cw, name);
return callSiteLoader.defineClassAndGetConstructor(name, bytes);
}
public static Constructor compilePojoMethod(CachedMethod cachedMethod) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final CachedClass declClass = cachedMethod.getDeclaringClass();
final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
final byte[] bytes = genPojoMetaMethodSite(cachedMethod, cw, name);
return callSiteLoader.defineClassAndGetConstructor(name, bytes);
}
public static Constructor compileStaticMethod(CachedMethod cachedMethod) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final CachedClass declClass = cachedMethod.getDeclaringClass();
final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
final byte[] bytes = genStaticMetaMethodSite(cachedMethod, cw, name);
return callSiteLoader.defineClassAndGetConstructor(name, bytes);
}
public static boolean isCompilable (CachedMethod method) {
return GroovySunClassLoader.sunVM != null || Modifier.isPublic(method.cachedClass.getModifiers()) && method.isPublic() && publicParams(method);
}
private static boolean publicParams(CachedMethod method) {
for (Class nativeParamType : method.getNativeParameterTypes()) {
if (!Modifier.isPublic(nativeParamType.getModifiers()))
return false;
}
return true;
}
}