1 /*
2 * Copyright 2003-2009 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.codehaus.groovy.runtime.callsite;
17
18 import org.codehaus.groovy.ast.ClassHelper;
19 import org.codehaus.groovy.classgen.BytecodeHelper;
20 import org.codehaus.groovy.reflection.CachedClass;
21 import org.codehaus.groovy.reflection.CachedMethod;
22 import org.objectweb.asm.ClassWriter;
23 import org.objectweb.asm.Label;
24 import org.objectweb.asm.MethodVisitor;
25 import org.objectweb.asm.Opcodes;
26
27 import groovy.lang.GroovyRuntimeException;
28
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Modifier;
32
33 public class CallSiteGenerator {
34
35 private static final String GRE = BytecodeHelper.getClassInternalName(ClassHelper.make(GroovyRuntimeException.class));
36
37 private CallSiteGenerator () {}
38
39 private static MethodVisitor writeMethod(ClassWriter cw, String name, int argumentCount, final String superClass, CachedMethod cachedMethod, String receiverType, String parameterDescription, boolean useArray) {
40 MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "call" + name, "(L" + receiverType + ";" + parameterDescription + ")Ljava/lang/Object;", null, null);
41 mv.visitCode();
42
43 final Label tryStart = new Label();
44 mv.visitLabel(tryStart);
45
46 // call for checking if method is still valid
47 for (int i = 0; i < argumentCount; ++i) mv.visitVarInsn(Opcodes.ALOAD, i);
48 mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, superClass, "checkCall", "(Ljava/lang/Object;" + parameterDescription + ")Z");
49 Label l0 = new Label();
50 mv.visitJumpInsn(Opcodes.IFEQ, l0);
51
52 // valid method branch
53 BytecodeHelper helper = new BytecodeHelper(mv);
54
55 Class callClass = cachedMethod.getDeclaringClass().getTheClass();
56 boolean useInterface = callClass.isInterface();
57
58 String type = BytecodeHelper.getClassInternalName(callClass.getName());
59 String descriptor = BytecodeHelper.getMethodDescriptor(cachedMethod.getReturnType(), cachedMethod.getNativeParameterTypes());
60
61 // prepare call
62 int invokeMethodCode = Opcodes.INVOKEVIRTUAL;
63 if (cachedMethod.isStatic()) {
64 invokeMethodCode = Opcodes.INVOKESTATIC;
65 } else {
66 mv.visitVarInsn(Opcodes.ALOAD, 1);
67 helper.doCast(callClass);
68 if (useInterface) invokeMethodCode = Opcodes.INVOKEINTERFACE;
69 }
70
71 Method method = cachedMethod.setAccessible();
72 Class<?>[] parameters = method.getParameterTypes();
73 int size = parameters.length;
74 for (int i = 0; i < size; i++) {
75 if (useArray) {
76 // unpack argument from Object[]
77 mv.visitVarInsn(Opcodes.ALOAD, 2);
78 helper.pushConstant(i);
79 mv.visitInsn(Opcodes.AALOAD);
80 } else {
81 mv.visitVarInsn(Opcodes.ALOAD, i+2);
82 }
83
84 // cast argument to parameter class, inclusive unboxing
85 // for methods with primitive types
86 Class parameterType = parameters[i];
87 if (parameterType.isPrimitive()) {
88 helper.unbox(parameterType);
89 } else {
90 helper.doCast(parameterType);
91 }
92 }
93
94 // make call
95 mv.visitMethodInsn(invokeMethodCode, type, cachedMethod.getName(), descriptor);
96
97 // produce result
98 helper.box(cachedMethod.getReturnType());
99 if (cachedMethod.getReturnType() == void.class) {
100 mv.visitInsn(Opcodes.ACONST_NULL);
101 }
102
103 // return
104 mv.visitInsn(Opcodes.ARETURN);
105
106 // fall back after method change
107 mv.visitLabel(l0);
108 for (int i = 0; i < argumentCount; ++i) mv.visitVarInsn(Opcodes.ALOAD, i);
109 if (!useArray) {
110 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/ArrayUtil", "createArray", "(" + parameterDescription + ")[Ljava/lang/Object;");
111 }
112 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;");
113 mv.visitInsn(Opcodes.ARETURN);
114
115 // exception unwrapping for stackless exceptions
116 final Label tryEnd = new Label();
117 mv.visitLabel(tryEnd);
118 final Label catchStart = new Label();
119 mv.visitLabel(catchStart);
120 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/ScriptBytecodeAdapter", "unwrap", "(Lgroovy/lang/GroovyRuntimeException;)Ljava/lang/Throwable;");
121 mv.visitInsn(Opcodes.ATHROW);
122 mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, GRE);
123
124 mv.visitMaxs(0, 0);
125 mv.visitEnd();
126 return mv;
127 }
128
129
130 public static void genCallWithFixedParams(ClassWriter cw, String name, final String superClass, CachedMethod cachedMethod, String receiverType ) {
131 if (cachedMethod.getParamsCount() > 4) return;
132
133 StringBuilder pdescb = new StringBuilder();
134 final int pc = cachedMethod.getParamsCount();
135 for (int i = 0; i != pc; ++i) pdescb.append("Ljava/lang/Object;");
136
137 writeMethod(cw,name,pc+2,superClass,cachedMethod,receiverType,pdescb.toString(),false);
138 }
139
140 public static void genCallXxxWithArray(ClassWriter cw, final String name, final String superClass, CachedMethod cachedMethod, String receiverType) {
141 writeMethod(cw,name,3,superClass,cachedMethod,receiverType,"[Ljava/lang/Object;",true);
142 }
143
144 private static void genConstructor(ClassWriter cw, final String superClass) {
145 MethodVisitor mv;
146 mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Lorg/codehaus/groovy/runtime/callsite/CallSite;Lgroovy/lang/MetaClassImpl;Lgroovy/lang/MetaMethod;[Ljava/lang/Class;)V", null, null);
147 mv.visitCode();
148 mv.visitVarInsn(Opcodes.ALOAD, 0);
149 mv.visitVarInsn(Opcodes.ALOAD, 1);
150 mv.visitVarInsn(Opcodes.ALOAD, 2);
151 mv.visitVarInsn(Opcodes.ALOAD, 3);
152 mv.visitVarInsn(Opcodes.ALOAD, 4);
153 mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClass, "<init>", "(Lorg/codehaus/groovy/runtime/callsite/CallSite;Lgroovy/lang/MetaClassImpl;Lgroovy/lang/MetaMethod;[Ljava/lang/Class;)V");
154 mv.visitInsn(Opcodes.RETURN);
155 mv.visitMaxs(0, 0);
156 mv.visitEnd();
157 }
158
159 public static byte[] genPogoMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
160 MethodVisitor mv;
161 cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name.replace('.','/'), null, "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", null);
162
163 genConstructor(cw, "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite");
164
165 genCallXxxWithArray(cw, "Current", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "groovy/lang/GroovyObject");
166 genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "java/lang/Object");
167
168 genCallWithFixedParams(cw, "Current", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "groovy/lang/GroovyObject");
169 genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/PogoMetaMethodSite", cachedMethod, "java/lang/Object");
170
171
172 cw.visitEnd();
173
174 return cw.toByteArray();
175 }
176
177 public static byte[] genPojoMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
178 MethodVisitor mv;
179 cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name.replace('.','/'), null, "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", null);
180
181 genConstructor(cw, "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite");
182
183 genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", cachedMethod, "java/lang/Object");
184 genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/PojoMetaMethodSite", cachedMethod, "java/lang/Object");
185
186 cw.visitEnd();
187
188 return cw.toByteArray();
189 }
190
191 public static byte[] genStaticMetaMethodSite(CachedMethod cachedMethod, ClassWriter cw, String name) {
192 cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name.replace('.','/'), null, "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", null);
193
194 genConstructor(cw, "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite");
195
196 genCallXxxWithArray(cw, "", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Object");
197 genCallXxxWithArray(cw, "Static", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Class");
198 genCallWithFixedParams(cw, "", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Object");
199 genCallWithFixedParams(cw, "Static", "org/codehaus/groovy/runtime/callsite/StaticMetaMethodSite", cachedMethod, "java/lang/Class");
200
201 cw.visitEnd();
202
203 return cw.toByteArray();
204 }
205
206 public static Constructor compilePogoMethod(CachedMethod cachedMethod) {
207 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
208
209 final CachedClass declClass = cachedMethod.getDeclaringClass();
210 final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
211 final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
212
213 final byte[] bytes = genPogoMetaMethodSite(cachedMethod, cw, name);
214
215 return callSiteLoader.defineClassAndGetConstructor(name, bytes);
216 }
217
218 public static Constructor compilePojoMethod(CachedMethod cachedMethod) {
219 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
220
221 final CachedClass declClass = cachedMethod.getDeclaringClass();
222 final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
223 final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
224
225 final byte[] bytes = genPojoMetaMethodSite(cachedMethod, cw, name);
226
227 return callSiteLoader.defineClassAndGetConstructor(name, bytes);
228 }
229
230 public static Constructor compileStaticMethod(CachedMethod cachedMethod) {
231 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
232
233 final CachedClass declClass = cachedMethod.getDeclaringClass();
234 final CallSiteClassLoader callSiteLoader = declClass.getCallSiteLoader();
235 final String name = callSiteLoader.createClassName(cachedMethod.setAccessible());
236
237 final byte[] bytes = genStaticMetaMethodSite(cachedMethod, cw, name);
238
239 return callSiteLoader.defineClassAndGetConstructor(name, bytes);
240 }
241
242 public static boolean isCompilable (CachedMethod method) {
243 return GroovySunClassLoader.sunVM != null || Modifier.isPublic(method.cachedClass.getModifiers()) && method.isPublic() && publicParams(method);
244 }
245
246 private static boolean publicParams(CachedMethod method) {
247 for (Class nativeParamType : method.getNativeParameterTypes()) {
248 if (!Modifier.isPublic(nativeParamType.getModifiers()))
249 return false;
250 }
251 return true;
252 }
253
254 }