| /* |
| * Copyright 2003-2007 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.classgen; |
| |
| import groovy.lang.GroovyRuntimeException; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.*; |
| import org.codehaus.groovy.ast.expr.*; |
| import org.codehaus.groovy.ast.stmt.*; |
| import org.codehaus.groovy.control.CompilerConfiguration; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.runtime.MetaClassHelper; |
| import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; |
| import org.codehaus.groovy.runtime.callsite.CallSite; |
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; |
| import org.codehaus.groovy.syntax.RuntimeParserException; |
| import org.codehaus.groovy.syntax.Types; |
| import org.objectweb.asm.AnnotationVisitor; |
| import org.objectweb.asm.*; |
| |
| import java.util.*; |
| |
| |
| /** |
| * Generates Java class versions of Groovy classes using ASM. |
| * |
| * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> |
| * @author <a href="mailto:b55r@sina.com">Bing Ran</a> |
| * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a> |
| * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a> |
| * @author Alex Tkachman |
| * @version $Revision$ |
| */ |
| public class AsmClassGenerator extends ClassGenerator { |
| |
| private final ClassVisitor cv; |
| private MethodVisitor mv; |
| private GeneratorContext context; |
| |
| private String sourceFile; |
| |
| // current class details |
| private ClassNode classNode; |
| private ClassNode outermostClass; |
| private String internalClassName; |
| private String internalBaseClassName; |
| |
| /* |
| * maps the variable names to the JVM indices |
| */ |
| private CompileStack compileStack; |
| |
| /* |
| * have we output a return statement yet |
| */ |
| private boolean outputReturn; |
| |
| /* |
| * Are we on the left or right of an expression? |
| * |
| * The default is false, that means the right side is default. |
| * The right side means that variables are read and not written. |
| * Any change of leftHandExpression to true, should be made carefully. |
| * If such a change is needed, then it should be set to false as soon as |
| * possible, but most important in the same method. Setting |
| * leftHandExpression to false is needed for writing variables. |
| */ |
| private boolean leftHandExpression = false; |
| |
| // method invocation |
| static final MethodCallerMultiAdapter invokeMethodOnCurrent = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnCurrent", true, false); |
| static final MethodCallerMultiAdapter invokeMethodOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnSuper", true, false); |
| static final MethodCallerMultiAdapter invokeMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethod", true, false); |
| static final MethodCallerMultiAdapter invokeStaticMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeStaticMethod", true, true); |
| static final MethodCallerMultiAdapter invokeNew = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeNew", true, true); |
| |
| // fields and properties |
| static final MethodCallerMultiAdapter setField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setField", false, false); |
| static final MethodCallerMultiAdapter getField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getField", false, false); |
| static final MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectField", false, false); |
| static final MethodCallerMultiAdapter getGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectField", false, false); |
| static final MethodCallerMultiAdapter setFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setFieldOnSuper", false, false); |
| static final MethodCallerMultiAdapter getFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getFieldOnSuper", false, false); |
| |
| static final MethodCallerMultiAdapter setProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setProperty", false, false); |
| static final MethodCallerMultiAdapter getProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getProperty", false, false); |
| static final MethodCallerMultiAdapter setGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectProperty", false, false); |
| static final MethodCallerMultiAdapter getGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectProperty", false, false); |
| static final MethodCallerMultiAdapter setPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setPropertyOnSuper", false, false); |
| static final MethodCallerMultiAdapter getPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getPropertyOnSuper", false, false); |
| |
| // iterator |
| static final MethodCaller iteratorNextMethod = MethodCaller.newInterface(Iterator.class, "next"); |
| static final MethodCaller iteratorHasNextMethod = MethodCaller.newInterface(Iterator.class, "hasNext"); |
| // assert |
| static final MethodCaller assertFailedMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "assertFailed"); |
| // isCase |
| static final MethodCaller isCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isCase"); |
| //compare |
| static final MethodCaller compareIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareIdentical"); |
| static final MethodCaller compareEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareEqual"); |
| static final MethodCaller compareNotEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotEqual"); |
| static final MethodCaller compareToMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareTo"); |
| static final MethodCaller compareLessThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThan"); |
| static final MethodCaller compareLessThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThanEqual"); |
| static final MethodCaller compareGreaterThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThan"); |
| static final MethodCaller compareGreaterThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThanEqual"); |
| //regexpr |
| static final MethodCaller findRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "findRegex"); |
| static final MethodCaller matchRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "matchRegex"); |
| static final MethodCaller regexPattern = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "regexPattern"); |
| // spread expressions |
| static final MethodCaller spreadMap = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "spreadMap"); |
| static final MethodCaller despreadList = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "despreadList"); |
| // Closure |
| static final MethodCaller getMethodPointer = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "getMethodPointer"); |
| static final MethodCaller invokeClosureMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "invokeClosure"); |
| // unary plus, unary minus, bitwise negation |
| static final MethodCaller unaryPlus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryPlus"); |
| static final MethodCaller unaryMinus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryMinus"); |
| static final MethodCaller bitwiseNegate = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "bitwiseNegate"); |
| |
| // type conversions |
| static final MethodCaller asTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "asType"); |
| static final MethodCaller castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType"); |
| static final MethodCaller createListMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createList"); |
| static final MethodCaller createTupleMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createTuple"); |
| static final MethodCaller createMapMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createMap"); |
| static final MethodCaller createRangeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createRange"); |
| |
| // wrapper creation methods |
| static final MethodCaller createPojoWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createPojoWrapper"); |
| static final MethodCaller createGroovyObjectWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createGroovyObjectWrapper"); |
| |
| // constructor calls with this() and super() |
| static final MethodCaller selectConstructorAndTransformArguments = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "selectConstructorAndTransformArguments"); |
| |
| // exception blocks list |
| private List exceptionBlocks = new ArrayList(); |
| private Map<String,ClassNode> referencedClasses = new HashMap<String,ClassNode>(); |
| private boolean passingClosureParams; |
| |
| private ConstructorNode constructorNode; |
| private MethodNode methodNode; |
| private BytecodeHelper helper = new BytecodeHelper(null); |
| |
| public static final boolean CREATE_DEBUG_INFO = true; |
| public static final boolean CREATE_LINE_NUMBER_INFO = true; |
| private static final boolean MARK_START = true; |
| |
| public static final boolean ASM_DEBUG = false; // add marker in the bytecode to show source-byecode relationship |
| private int lineNumber = -1; |
| private int columnNumber = -1; |
| private ASTNode currentASTNode = null; |
| |
| private DummyClassGenerator dummyGen = null; |
| private ClassWriter dummyClassWriter = null; |
| |
| private ClassNode interfaceClassLoadingClass; |
| |
| private boolean implicitThis = false; |
| |
| private Map genericParameterNames = null; |
| private ClassNode rightHandType; |
| private static final String CONSTRUCTOR = "<$constructor$>"; |
| private List callSites = new ArrayList(); |
| private int callSiteArrayVarIndex; |
| private HashMap closureClassMap; |
| private static final String DTT = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName()); |
| private boolean specialCallWithinConstructor = false; |
| |
| public AsmClassGenerator( |
| GeneratorContext context, ClassVisitor classVisitor, |
| ClassLoader classLoader, String sourceFile |
| ) { |
| super(classLoader); |
| this.context = context; |
| this.cv = classVisitor; |
| this.sourceFile = sourceFile; |
| |
| this.dummyClassWriter = new ClassWriter(true); |
| dummyGen = new DummyClassGenerator(context, dummyClassWriter, classLoader, sourceFile); |
| compileStack = new CompileStack(); |
| genericParameterNames = new HashMap(); |
| closureClassMap = new HashMap(); |
| } |
| |
| protected SourceUnit getSourceUnit() { |
| return null; |
| } |
| |
| |
| // GroovyClassVisitor interface |
| //------------------------------------------------------------------------- |
| public void visitClass(ClassNode classNode) { |
| try { |
| callSites.clear(); |
| if(classNode instanceof InterfaceHelperClassNode) { |
| InterfaceHelperClassNode ihcn = (InterfaceHelperClassNode) classNode; |
| callSites.addAll(ihcn.getCallSites()); |
| } |
| |
| referencedClasses.clear(); |
| this.classNode = classNode; |
| this.outermostClass = null; |
| this.internalClassName = BytecodeHelper.getClassInternalName(classNode); |
| |
| this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass()); |
| |
| cv.visit( |
| getBytecodeVersion(), |
| adjustedModifiers(classNode.getModifiers()), |
| internalClassName, |
| BytecodeHelper.getGenericsSignature(classNode), |
| internalBaseClassName, |
| BytecodeHelper.getClassInternalNames(classNode.getInterfaces()) |
| ); |
| cv.visitSource(sourceFile, null); |
| visitAnnotations(classNode, cv); |
| |
| if (classNode.isInterface()) { |
| ClassNode owner = classNode; |
| if (owner instanceof InnerClassNode) { |
| owner = owner.getOuterClass(); |
| } |
| String outerClassName = owner.getName(); |
| String name = outerClassName + "$" + context.getNextInnerClassIdx(); |
| interfaceClassLoadingClass = new InterfaceHelperClassNode(owner, name, 4128, ClassHelper.OBJECT_TYPE, callSites); |
| |
| super.visitClass(classNode); |
| createInterfaceSyntheticStaticFields(); |
| } else { |
| super.visitClass(classNode); |
| if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) { |
| createMopMethods(); |
| } |
| createSyntheticStaticFields(); |
| } |
| |
| for (Iterator iter = innerClasses.iterator(); iter.hasNext();) { |
| ClassNode innerClass = (ClassNode) iter.next(); |
| String innerClassName = innerClass.getName(); |
| String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassName); |
| { |
| int index = innerClassName.lastIndexOf('$'); |
| if (index >= 0) innerClassName = innerClassName.substring(index + 1); |
| } |
| String outerClassName = internalClassName; // default for inner classes |
| MethodNode enclosingMethod = innerClass.getEnclosingMethod(); |
| if (enclosingMethod != null) { |
| // local inner classes do not specify the outer class name |
| outerClassName = null; |
| innerClassName = null; |
| } |
| cv.visitInnerClass( |
| innerClassInternalName, |
| outerClassName, |
| innerClassName, |
| adjustedModifiers(innerClass.getModifiers())); |
| } |
| //TODO: an inner class should have an entry of itself |
| |
| generateCallSiteArray(); |
| cv.visitEnd(); |
| } |
| catch (GroovyRuntimeException e) { |
| e.setModule(classNode.getModule()); |
| throw e; |
| } |
| } |
| |
| /* |
| * Classes but not interfaces should have ACC_SUPER set |
| */ |
| private int adjustedModifiers(int modifiers) { |
| boolean needsSuper = (modifiers & ACC_INTERFACE) == 0; |
| return needsSuper ? modifiers | ACC_SUPER : modifiers; |
| } |
| |
| private void generateCallSiteArray() { |
| if (!classNode.isInterface()) { |
| cv.visitField(ACC_PRIVATE+ACC_STATIC+ACC_SYNTHETIC, "$callSiteArray", "Ljava/lang/ref/SoftReference;", null, null); |
| |
| generateCreateCallSiteArray(); |
| generateGetCallSiteArray(); |
| |
| // generateCallSiteMethods(); |
| // generateAdapterMethods (); |
| } |
| } |
| |
| |
| private void generateGetCallSiteArray() { |
| int visibility = (classNode instanceof InterfaceHelperClassNode) ? ACC_PUBLIC : ACC_PRIVATE; |
| MethodVisitor mv = cv.visitMethod(visibility + ACC_SYNTHETIC + ACC_STATIC,"$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;", null, null); |
| mv.visitCode(); |
| mv.visitFieldInsn(GETSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;"); |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFNULL, l0); |
| mv.visitFieldInsn(GETSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;"); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ref/SoftReference", "get", "()Ljava/lang/Object;"); |
| mv.visitTypeInsn(CHECKCAST, "org/codehaus/groovy/runtime/callsite/CallSiteArray"); |
| mv.visitInsn(DUP); |
| mv.visitVarInsn(ASTORE, 0); |
| Label l1 = new Label(); |
| mv.visitJumpInsn(IFNONNULL, l1); |
| mv.visitLabel(l0); |
| mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;"); |
| mv.visitVarInsn(ASTORE, 0); |
| mv.visitTypeInsn(NEW, "java/lang/ref/SoftReference"); |
| mv.visitInsn(DUP); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/ref/SoftReference", "<init>", "(Ljava/lang/Object;)V"); |
| mv.visitFieldInsn(PUTSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;"); |
| mv.visitLabel(l1); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "array", "[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| mv.visitInsn(ARETURN); |
| mv.visitMaxs(0,0); |
| mv.visitEnd(); |
| } |
| |
| private void generateCreateCallSiteArray() { |
| MethodVisitor mv = cv.visitMethod(ACC_PRIVATE+ACC_SYNTHETIC+ACC_STATIC,"$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", null, null); |
| mv.visitCode(); |
| mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/callsite/CallSiteArray"); |
| mv.visitInsn(DUP); |
| mv.visitFieldInsn(GETSTATIC, internalClassName, "$ownClass", "Ljava/lang/Class;"); |
| |
| final int size = callSites.size(); |
| mv.visitLdcInsn(size); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| mv.visitLdcInsn(i); |
| mv.visitLdcInsn(callSites.get(i)); |
| mv.visitInsn(AASTORE); |
| } |
| |
| mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "<init>", "(Ljava/lang/Class;[Ljava/lang/String;)V"); |
| mv.visitInsn(ARETURN); |
| mv.visitMaxs(0,0); |
| mv.visitEnd(); |
| } |
| |
| public void visitGenericType(GenericsType genericsType) { |
| ClassNode type = genericsType.getType(); |
| genericParameterNames.put(type.getName(), genericsType); |
| } |
| |
| private void createMopMethods() { |
| visitMopMethodList(classNode.getMethods(), true); |
| visitMopMethodList(classNode.getSuperClass().getAllDeclaredMethods(), false); |
| } |
| |
| private String[] buildExceptions(ClassNode[] exceptions) { |
| if (exceptions == null) return null; |
| String[] ret = new String[exceptions.length]; |
| for (int i = 0; i < exceptions.length; i++) { |
| ret[i] = BytecodeHelper.getClassInternalName(exceptions[i]); |
| } |
| return ret; |
| } |
| |
| /** |
| * filters a list of method for MOP methods. For all methods that are no |
| * MOP methods a MOP method is created if the method is not public and the |
| * call would be a call on "this" (isThis == true). If the call is not on |
| * "this", then the call is a call on "super" and all methods are used, |
| * unless they are already a MOP method |
| * |
| * @param methods unfiltered list of methods for MOP |
| * @param isThis if true, then we are creating a MOP method on "this", "super" else |
| * @see #generateMopCalls(LinkedList,boolean) |
| */ |
| private void visitMopMethodList(List methods, boolean isThis) { |
| HashMap mops = new HashMap(); |
| class Key { |
| int hash = 0; |
| String name; |
| Parameter[] params; |
| |
| Key(String name, Parameter[] params) { |
| this.name = name; |
| this.params = params; |
| hash = name.hashCode() << 2 + params.length; |
| } |
| |
| public int hashCode() { |
| return hash; |
| } |
| |
| public boolean equals(Object obj) { |
| Key other = (Key) obj; |
| return other.name.equals(name) && equalParameterTypes(other.params,params); |
| } |
| } |
| LinkedList mopCalls = new LinkedList(); |
| for (Iterator iter = methods.iterator(); iter.hasNext();) { |
| MethodNode mn = (MethodNode) iter.next(); |
| if ((mn.getModifiers() & ACC_ABSTRACT) != 0) continue; |
| if (mn.isStatic()) continue; |
| // no this$ methods for protected/public isThis=true |
| // super$ method for protected/public isThis=false |
| // --> results in XOR |
| if (isThis ^ (mn.getModifiers() & (ACC_PUBLIC | ACC_PROTECTED)) == 0) continue; |
| String methodName = mn.getName(); |
| if (isMopMethod(methodName)) { |
| mops.put(new Key(methodName, mn.getParameters()), mn); |
| continue; |
| } |
| if (methodName.startsWith("<")) continue; |
| String name = getMopMethodName(mn, isThis); |
| Key key = new Key(name, mn.getParameters()); |
| if (mops.containsKey(key)) continue; |
| mops.put(key, mn); |
| mopCalls.add(mn); |
| } |
| generateMopCalls(mopCalls, isThis); |
| mopCalls.clear(); |
| mops.clear(); |
| } |
| |
| private boolean equalParameterTypes(Parameter[] p1, Parameter[] p2) { |
| if (p1.length!=p2.length) return false; |
| for (int i=0; i<p1.length; i++) { |
| if (!p1[i].getType().equals(p2[i].getType())) return false; |
| } |
| return true; |
| } |
| |
| /** |
| * generates a Meta Object Protocol method, that is used to call a non public |
| * method, or to make a call to super. |
| * |
| * @param mopCalls list of methods a mop call method should be generated for |
| * @param useThis true if "this" should be used for the naming |
| */ |
| private void generateMopCalls(LinkedList mopCalls, boolean useThis) { |
| for (Iterator iter = mopCalls.iterator(); iter.hasNext();) { |
| MethodNode method = (MethodNode) iter.next(); |
| String name = getMopMethodName(method, useThis); |
| Parameter[] parameters = method.getParameters(); |
| String methodDescriptor = BytecodeHelper.getMethodDescriptor(method.getReturnType(), method.getParameters()); |
| mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name, methodDescriptor, null, null); |
| mv.visitVarInsn(ALOAD, 0); |
| int newRegister = 1; |
| BytecodeHelper helper = new BytecodeHelper(mv); |
| for (int i = 0; i < parameters.length; i++) { |
| ClassNode type = parameters[i].getType(); |
| helper.load(parameters[i].getType(), newRegister); |
| // increment to next register, double/long are using two places |
| newRegister++; |
| if (type == ClassHelper.double_TYPE || type == ClassHelper.long_TYPE) newRegister++; |
| } |
| mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(method.getDeclaringClass()), method.getName(), methodDescriptor); |
| helper.doReturn(method.getReturnType()); |
| mv.visitMaxs(0, 0); |
| mv.visitEnd(); |
| classNode.addMethod(name, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, method.getReturnType(), parameters, null, null); |
| } |
| } |
| |
| /** |
| * creates a MOP method name from a method |
| * |
| * @param method the method to be called by the mop method |
| * @param useThis if true, then it is a call on "this", "super" else |
| * @return the mop method name |
| */ |
| public static String getMopMethodName(MethodNode method, boolean useThis) { |
| ClassNode declaringNode = method.getDeclaringClass(); |
| int distance = 0; |
| for (; declaringNode != null; declaringNode = declaringNode.getSuperClass()) { |
| distance++; |
| } |
| return (useThis ? "this" : "super") + "$" + distance + "$" + method.getName(); |
| } |
| |
| /** |
| * method to determine if a method is a MOP method. This is done by the |
| * method name. If the name starts with "this$" or "super$" but does not |
| * contain "$dist$", then it is an MOP method |
| * |
| * @param methodName name of the method to test |
| * @return true if the method is a MOP method |
| */ |
| public static boolean isMopMethod(String methodName) { |
| return (methodName.startsWith("this$") || |
| methodName.startsWith("super$")) && !methodName.contains("$dist$"); |
| } |
| |
| protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { |
| lineNumber = -1; |
| columnNumber = -1; |
| |
| Parameter[] parameters = node.getParameters(); |
| String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), parameters); |
| |
| String signature = BytecodeHelper.getGenericsMethodSignature(node); |
| int modifiers = node.getModifiers(); |
| if (isVargs(node.getParameters())) modifiers |= Opcodes.ACC_VARARGS; |
| mv = cv.visitMethod(modifiers, node.getName(), methodType, signature, buildExceptions(node.getExceptions())); |
| mv = new MyMethodAdapter(); |
| visitAnnotations(node, mv); |
| for (int i = 0; i < parameters.length; i++) { |
| visitParameterAnnotations(parameters[i], i, mv); |
| } |
| helper = new BytecodeHelper(mv); |
| |
| if (classNode.isAnnotationDefinition()) { |
| visitAnnotationDefault(node, mv); |
| } else if (!node.isAbstract()) { |
| Statement code = node.getCode(); |
| |
| // fast path for getter/setters etc. |
| if (code instanceof BytecodeSequence && ((BytecodeSequence)code).getInstructions().size() == 1 && ((BytecodeSequence)code).getInstructions().get(0) instanceof BytecodeInstruction) { |
| ((BytecodeInstruction)((BytecodeSequence)code).getInstructions().get(0)).visit(mv); |
| } else{ |
| visitStdMethod(node, isConstructor, parameters, code); |
| } |
| |
| mv.visitMaxs(0, 0); |
| } |
| mv.visitEnd(); |
| } |
| |
| private void visitStdMethod(MethodNode node, boolean isConstructor, Parameter[] parameters, Statement code) { |
| if (isConstructor && (code == null || !((ConstructorNode) node).firstStatementIsSpecialConstructorCall())) { |
| // invokes the super class constructor |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(classNode.getSuperClass()), "<init>", "()V"); |
| } |
| |
| compileStack.init(node.getVariableScope(), parameters, mv, classNode); |
| |
| |
| if (isNotClinit()) { |
| mv.visitMethodInsn(INVOKESTATIC,internalClassName,"$getCallSiteArray","()[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| callSiteArrayVarIndex = compileStack.defineTemporaryVariable("$local$callSiteArray", ClassHelper.make(CallSite[].class), true); |
| } |
| |
| // handle body |
| super.visitConstructorOrMethod(node, isConstructor); |
| if (!outputReturn || node.isVoidMethod()) { |
| mv.visitInsn(RETURN); |
| } |
| compileStack.clear(); |
| |
| final Label finallyStart = new Label(); |
| mv.visitJumpInsn(GOTO, finallyStart); |
| |
| // let's do all the exception blocks |
| for (Iterator iter = exceptionBlocks.iterator(); iter.hasNext();) { |
| Runnable runnable = (Runnable) iter.next(); |
| runnable.run(); |
| } |
| exceptionBlocks.clear(); |
| } |
| |
| void visitAnnotationDefaultExpression(AnnotationVisitor av, ClassNode type, Expression exp) { |
| if (type.isArray()) { |
| ListExpression list = (ListExpression) exp; |
| AnnotationVisitor avl = av.visitArray(null); |
| ClassNode componentType = type.getComponentType(); |
| for (Iterator it = list.getExpressions().iterator(); it.hasNext();) { |
| Expression lExp = (Expression) it.next(); |
| visitAnnotationDefaultExpression(avl,componentType, lExp); |
| } |
| } else if (ClassHelper.isPrimitiveType(type) || type.equals(ClassHelper.STRING_TYPE)) { |
| ConstantExpression constExp = (ConstantExpression) exp; |
| av.visit(null, constExp.getValue()); |
| } else if (ClassHelper.CLASS_Type.equals(type)) { |
| ClassNode clazz = exp.getType(); |
| Type t = Type.getType(BytecodeHelper.getTypeDescription(clazz)); |
| av.visit(null, t); |
| } else if (type.isDerivedFrom(ClassHelper.Enum_Type)) { |
| PropertyExpression pExp = (PropertyExpression) exp; |
| ClassExpression cExp = (ClassExpression) pExp.getObjectExpression(); |
| String desc = BytecodeHelper.getTypeDescription(cExp.getType()); |
| String name = pExp.getPropertyAsString(); |
| av.visitEnum(null, desc, name); |
| } else if (type.implementsInterface(ClassHelper.Annotation_TYPE)) { |
| AnnotationConstantExpression avExp = (AnnotationConstantExpression) exp; |
| AnnotationNode value = (AnnotationNode) avExp.getValue(); |
| AnnotationVisitor avc = av.visitAnnotation(null, BytecodeHelper.getTypeDescription(avExp.getType())); |
| visitAnnotationAttributes(value,avc); |
| } else { |
| throw new GroovyBugError("unexpected annotation type " + type.getName()); |
| } |
| av.visitEnd(); |
| } |
| |
| private void visitAnnotationDefault(MethodNode node, MethodVisitor mv) { |
| if (!node.hasAnnotationDefault()) return; |
| Expression exp = ((ReturnStatement) node.getCode()).getExpression(); |
| AnnotationVisitor av = mv.visitAnnotationDefault(); |
| visitAnnotationDefaultExpression(av,node.getReturnType(),exp); |
| } |
| |
| |
| private boolean isNotClinit() { |
| return methodNode == null || !methodNode.getName().equals("<clinit>"); |
| } |
| |
| private boolean isVargs(Parameter[] p) { |
| if (p.length==0) return false; |
| ClassNode clazz = p[p.length-1].getType(); |
| return (clazz.isArray()); |
| } |
| |
| public void visitConstructor(ConstructorNode node) { |
| this.constructorNode = node; |
| this.methodNode = null; |
| outputReturn = false; |
| super.visitConstructor(node); |
| } |
| |
| public void visitMethod(MethodNode node) { |
| this.constructorNode = null; |
| this.methodNode = node; |
| outputReturn = false; |
| |
| super.visitMethod(node); |
| } |
| |
| public void visitField(FieldNode fieldNode) { |
| onLineNumber(fieldNode, "visitField: " + fieldNode.getName()); |
| ClassNode t = fieldNode.getType(); |
| String signature = helper.getGenericsBounds(t); |
| FieldVisitor fv = cv.visitField( |
| fieldNode.getModifiers(), |
| fieldNode.getName(), |
| BytecodeHelper.getTypeDescription(t), |
| signature, |
| null); |
| visitAnnotations(fieldNode, fv); |
| fv.visitEnd(); |
| } |
| |
| public void visitProperty(PropertyNode statement) { |
| // the verifier created the field and the setter/getter methods, so here is |
| // not really something to do |
| onLineNumber(statement, "visitProperty:" + statement.getField().getName()); |
| this.methodNode = null; |
| } |
| |
| // GroovyCodeVisitor interface |
| //------------------------------------------------------------------------- |
| |
| // Statements |
| //------------------------------------------------------------------------- |
| |
| protected void visitStatement(Statement statement) { |
| String name = statement.getStatementLabel(); |
| if (name != null) { |
| Label label = compileStack.createLocalLabel(name); |
| mv.visitLabel(label); |
| } |
| } |
| |
| public void visitBlockStatement(BlockStatement block) { |
| onLineNumber(block, "visitBlockStatement"); |
| visitStatement(block); |
| |
| compileStack.pushVariableScope(block.getVariableScope()); |
| super.visitBlockStatement(block); |
| compileStack.pop(); |
| } |
| |
| private void visitExpressionOrStatement(Object o) { |
| if (o == EmptyExpression.INSTANCE) return; |
| if (o instanceof Expression) { |
| Expression expr = (Expression) o; |
| visitAndAutoboxBoolean(expr); |
| if (isPopRequired(expr)) mv.visitInsn(POP); |
| } else { |
| ((Statement) o).visit(this); |
| } |
| } |
| |
| private void visitForLoopWithClosureList(ForStatement loop) { |
| compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabel()); |
| |
| ClosureListExpression clExpr = (ClosureListExpression) loop.getCollectionExpression(); |
| compileStack.pushVariableScope(clExpr.getVariableScope()); |
| |
| List expressions = clExpr.getExpressions(); |
| int size = expressions.size(); |
| |
| // middle element is condition, lower half is init, higher half is increment |
| int condIndex = (size - 1) / 2; |
| |
| // visit init |
| for (int i = 0; i < condIndex; i++) { |
| visitExpressionOrStatement(expressions.get(i)); |
| } |
| |
| Label continueLabel = compileStack.getContinueLabel(); |
| Label breakLabel = compileStack.getBreakLabel(); |
| |
| Label cond = new Label(); |
| mv.visitLabel(cond); |
| // visit condition leave boolean on stack |
| { |
| Expression condExpr = (Expression) expressions.get(condIndex); |
| if (condExpr == EmptyExpression.INSTANCE) { |
| mv.visitIntInsn(BIPUSH, 1); |
| } else if (isComparisonExpression(condExpr)) { |
| condExpr.visit(this); |
| } else { |
| visitAndAutoboxBoolean(condExpr); |
| helper.unbox(ClassHelper.boolean_TYPE); |
| } |
| } |
| // jump if we don't want to continue |
| // note: ifeq tests for ==0, a boolean is 0 if it is false |
| mv.visitJumpInsn(IFEQ, breakLabel); |
| |
| // Generate the loop body |
| loop.getLoopBlock().visit(this); |
| |
| // visit increment |
| mv.visitLabel(continueLabel); |
| for (int i = condIndex + 1; i < size; i++) { |
| visitExpressionOrStatement(expressions.get(i)); |
| } |
| |
| // jump to test the condition again |
| mv.visitJumpInsn(GOTO, cond); |
| |
| // loop end |
| mv.visitLabel(breakLabel); |
| |
| compileStack.pop(); |
| compileStack.pop(); |
| |
| } |
| |
| public void visitForLoop(ForStatement loop) { |
| |
| onLineNumber(loop, "visitForLoop"); |
| visitStatement(loop); |
| |
| |
| Parameter loopVar = loop.getVariable(); |
| if (loopVar == ForStatement.FOR_LOOP_DUMMY) { |
| visitForLoopWithClosureList(loop); |
| return; |
| } |
| |
| compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabel()); |
| |
| // Declare the loop counter. |
| Variable variable = compileStack.defineVariable(loop.getVariable(), false); |
| |
| // |
| // Then get the iterator and generate the loop control |
| MethodCallExpression iterator = new MethodCallExpression(loop.getCollectionExpression(), "iterator", new ArgumentListExpression()); |
| iterator.visit(this); |
| |
| final int iteratorIdx = compileStack.defineTemporaryVariable("iterator", ClassHelper.make(java.util.Iterator.class), true); |
| |
| Label continueLabel = compileStack.getContinueLabel(); |
| Label breakLabel = compileStack.getBreakLabel(); |
| |
| mv.visitLabel(continueLabel); |
| mv.visitVarInsn(ALOAD, iteratorIdx); |
| iteratorHasNextMethod.call(mv); |
| // note: ifeq tests for ==0, a boolean is 0 if it is false |
| mv.visitJumpInsn(IFEQ, breakLabel); |
| |
| mv.visitVarInsn(ALOAD, iteratorIdx); |
| iteratorNextMethod.call(mv); |
| helper.storeVar(variable); |
| |
| // Generate the loop body |
| loop.getLoopBlock().visit(this); |
| |
| mv.visitJumpInsn(GOTO, continueLabel); |
| mv.visitLabel(breakLabel); |
| |
| compileStack.pop(); |
| } |
| |
| public void visitWhileLoop(WhileStatement loop) { |
| onLineNumber(loop, "visitWhileLoop"); |
| visitStatement(loop); |
| |
| compileStack.pushLoop(loop.getStatementLabel()); |
| Label continueLabel = compileStack.getContinueLabel(); |
| Label breakLabel = compileStack.getBreakLabel(); |
| |
| mv.visitLabel(continueLabel); |
| Expression bool = loop.getBooleanExpression(); |
| boolean boolHandled = false; |
| if (bool instanceof ConstantExpression) { |
| ConstantExpression constant = (ConstantExpression) bool; |
| if (constant.getValue()==Boolean.TRUE) { |
| boolHandled = true; |
| // do nothing |
| } else if (constant.getValue()==Boolean.FALSE) { |
| boolHandled = true; |
| mv.visitJumpInsn(GOTO, breakLabel); |
| } |
| } |
| |
| if(!boolHandled) { |
| bool.visit(this); |
| mv.visitJumpInsn(IFEQ, breakLabel); |
| } |
| |
| loop.getLoopBlock().visit(this); |
| |
| mv.visitJumpInsn(GOTO, continueLabel); |
| mv.visitLabel(breakLabel); |
| |
| compileStack.pop(); |
| } |
| |
| public void visitDoWhileLoop(DoWhileStatement loop) { |
| onLineNumber(loop, "visitDoWhileLoop"); |
| visitStatement(loop); |
| |
| compileStack.pushLoop(loop.getStatementLabel()); |
| Label breakLabel = compileStack.getBreakLabel(); |
| Label continueLabel = compileStack.getContinueLabel(); |
| mv.visitLabel(continueLabel); |
| |
| loop.getLoopBlock().visit(this); |
| |
| loop.getBooleanExpression().visit(this); |
| mv.visitJumpInsn(IFEQ, continueLabel); |
| mv.visitLabel(breakLabel); |
| |
| compileStack.pop(); |
| } |
| |
| public void visitIfElse(IfStatement ifElse) { |
| onLineNumber(ifElse, "visitIfElse"); |
| visitStatement(ifElse); |
| ifElse.getBooleanExpression().visit(this); |
| |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| // if-else is here handled as a special version |
| // of a booelan expression |
| compileStack.pushBooleanExpression(); |
| ifElse.getIfBlock().visit(this); |
| compileStack.pop(); |
| |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| mv.visitLabel(l0); |
| |
| compileStack.pushBooleanExpression(); |
| ifElse.getElseBlock().visit(this); |
| compileStack.pop(); |
| |
| mv.visitLabel(l1); |
| } |
| |
| public void visitTernaryExpression(TernaryExpression expression) { |
| onLineNumber(expression, "visitTernaryExpression"); |
| |
| BooleanExpression boolPart = expression.getBooleanExpression(); |
| Expression truePart = expression.getTrueExpression(); |
| Expression falsePart = expression.getFalseExpression(); |
| |
| if (expression instanceof ElvisOperatorExpression) { |
| visitAndAutoboxBoolean(expression.getTrueExpression()); |
| boolPart = new BooleanExpression( |
| new BytecodeExpression() { |
| public void visit(MethodVisitor mv) { |
| mv.visitInsn(DUP); |
| } |
| } |
| ); |
| truePart = BytecodeExpression.NOP; |
| final Expression oldFalse = falsePart; |
| falsePart = new BytecodeExpression() { |
| public void visit(MethodVisitor mv) { |
| mv.visitInsn(POP); |
| visitAndAutoboxBoolean(oldFalse); |
| } |
| }; |
| } |
| |
| |
| boolPart.visit(this); |
| |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| compileStack.pushBooleanExpression(); |
| visitAndAutoboxBoolean(truePart); |
| compileStack.pop(); |
| |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| mv.visitLabel(l0); |
| compileStack.pushBooleanExpression(); |
| visitAndAutoboxBoolean(falsePart); |
| compileStack.pop(); |
| |
| mv.visitLabel(l1); |
| } |
| |
| public void visitAssertStatement(AssertStatement statement) { |
| onLineNumber(statement, "visitAssertStatement"); |
| visitStatement(statement); |
| |
| BooleanExpression booleanExpression = statement.getBooleanExpression(); |
| booleanExpression.visit(this); |
| |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| // do nothing |
| |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| mv.visitLabel(l0); |
| |
| // push expression string onto stack |
| String expressionText = booleanExpression.getText(); |
| List list = new ArrayList(); |
| addVariableNames(booleanExpression, list); |
| if (list.isEmpty()) { |
| mv.visitLdcInsn(expressionText); |
| } else { |
| boolean first = true; |
| |
| // let's create a new expression |
| mv.visitTypeInsn(NEW, "java/lang/StringBuffer"); |
| mv.visitInsn(DUP); |
| mv.visitLdcInsn(expressionText + ". Values: "); |
| |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuffer", "<init>", "(Ljava/lang/String;)V"); |
| |
| int tempIndex = compileStack.defineTemporaryVariable("assert", true); |
| |
| for (Iterator iter = list.iterator(); iter.hasNext();) { |
| String name = (String) iter.next(); |
| String text = name + " = "; |
| if (first) { |
| first = false; |
| } else { |
| text = ", " + text; |
| } |
| |
| mv.visitVarInsn(ALOAD, tempIndex); |
| mv.visitLdcInsn(text); |
| mv.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "java/lang/StringBuffer", |
| "append", |
| "(Ljava/lang/Object;)Ljava/lang/StringBuffer;"); |
| mv.visitInsn(POP); |
| |
| mv.visitVarInsn(ALOAD, tempIndex); |
| new VariableExpression(name).visit(this); |
| mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "toString", "(Ljava/lang/Object;)Ljava/lang/String;"); |
| mv.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "java/lang/StringBuffer", |
| "append", |
| "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); |
| mv.visitInsn(POP); |
| |
| } |
| mv.visitVarInsn(ALOAD, tempIndex); |
| compileStack.removeVar(tempIndex); |
| } |
| // now the optional exception expression |
| visitAndAutoboxBoolean(statement.getMessageExpression()); |
| |
| assertFailedMethod.call(mv); |
| mv.visitLabel(l1); |
| } |
| |
| private void addVariableNames(Expression expression, List list) { |
| if (expression instanceof BooleanExpression) { |
| BooleanExpression boolExp = (BooleanExpression) expression; |
| addVariableNames(boolExp.getExpression(), list); |
| } else if (expression instanceof BinaryExpression) { |
| BinaryExpression binExp = (BinaryExpression) expression; |
| addVariableNames(binExp.getLeftExpression(), list); |
| addVariableNames(binExp.getRightExpression(), list); |
| } else if (expression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) expression; |
| list.add(varExp.getName()); |
| } |
| } |
| |
| public void visitTryCatchFinally(TryCatchStatement statement) { |
| onLineNumber(statement, "visitTryCatchFinally"); |
| visitStatement(statement); |
| |
| CatchStatement catchStatement = statement.getCatchStatement(0); |
| Statement tryStatement = statement.getTryStatement(); |
| final Statement finallyStatement = statement.getFinallyStatement(); |
| |
| int anyExceptionIndex = compileStack.defineTemporaryVariable("exception", false); |
| if (!finallyStatement.isEmpty()) { |
| compileStack.pushFinallyBlock( |
| new Runnable() { |
| public void run() { |
| compileStack.pushFinallyBlockVisit(this); |
| finallyStatement.visit(AsmClassGenerator.this); |
| compileStack.popFinallyBlockVisit(this); |
| } |
| } |
| ); |
| } |
| |
| // start try block, label needed for exception table |
| final Label tryStart = new Label(); |
| mv.visitLabel(tryStart); |
| tryStatement.visit(this); |
| // goto finally part |
| final Label finallyStart = new Label(); |
| mv.visitJumpInsn(GOTO, finallyStart); |
| // marker needed for Exception table |
| final Label greEnd = new Label(); |
| mv.visitLabel(greEnd); |
| |
| final Label tryEnd = new Label(); |
| mv.visitLabel(tryEnd); |
| |
| for (Iterator it = statement.getCatchStatements().iterator(); it.hasNext();) { |
| catchStatement = (CatchStatement) it.next(); |
| ClassNode exceptionType = catchStatement.getExceptionType(); |
| // start catch block, label needed for exception table |
| final Label catchStart = new Label(); |
| mv.visitLabel(catchStart); |
| // create exception variable and store the exception |
| compileStack.pushState(); |
| compileStack.defineVariable(catchStatement.getVariable(), true); |
| // handle catch body |
| catchStatement.visit(this); |
| compileStack.pop(); |
| // goto finally start |
| mv.visitJumpInsn(GOTO, finallyStart); |
| // add exception to table |
| final String exceptionTypeInternalName = BytecodeHelper.getClassInternalName(exceptionType); |
| exceptionBlocks.add(new Runnable() { |
| public void run() { |
| mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, exceptionTypeInternalName); |
| } |
| }); |
| } |
| |
| // marker needed for the exception table |
| final Label endOfAllCatches = new Label(); |
| mv.visitLabel(endOfAllCatches); |
| |
| // remove the finally, don't let it visit itself |
| if (!finallyStatement.isEmpty()) compileStack.popFinallyBlock(); |
| |
| // start finally |
| mv.visitLabel(finallyStart); |
| finallyStatement.visit(this); |
| // goto end of finally |
| Label afterFinally = new Label(); |
| mv.visitJumpInsn(GOTO, afterFinally); |
| |
| // start a block catching any Exception |
| final Label catchAny = new Label(); |
| mv.visitLabel(catchAny); |
| //store exception |
| mv.visitVarInsn(ASTORE, anyExceptionIndex); |
| finallyStatement.visit(this); |
| // load the exception and rethrow it |
| mv.visitVarInsn(ALOAD, anyExceptionIndex); |
| mv.visitInsn(ATHROW); |
| |
| // end of all catches and finally parts |
| mv.visitLabel(afterFinally); |
| |
| // add catch any block to exception table |
| exceptionBlocks.add(new Runnable() { |
| public void run() { |
| mv.visitTryCatchBlock(tryStart, tryEnd, catchAny, null); |
| } |
| }); |
| } |
| |
| public void visitSwitch(SwitchStatement statement) { |
| onLineNumber(statement, "visitSwitch"); |
| visitStatement(statement); |
| |
| statement.getExpression().visit(this); |
| |
| // switch does not have a continue label. use its parent's for continue |
| Label breakLabel = compileStack.pushSwitch(); |
| |
| int switchVariableIndex = compileStack.defineTemporaryVariable("switch", true); |
| |
| List caseStatements = statement.getCaseStatements(); |
| int caseCount = caseStatements.size(); |
| Label[] labels = new Label[caseCount + 1]; |
| for (int i = 0; i < caseCount; i++) { |
| labels[i] = new Label(); |
| } |
| |
| int i = 0; |
| for (Iterator iter = caseStatements.iterator(); iter.hasNext(); i++) { |
| CaseStatement caseStatement = (CaseStatement) iter.next(); |
| visitCaseStatement(caseStatement, switchVariableIndex, labels[i], labels[i + 1]); |
| } |
| |
| statement.getDefaultStatement().visit(this); |
| |
| mv.visitLabel(breakLabel); |
| |
| compileStack.pop(); |
| } |
| |
| public void visitCaseStatement(CaseStatement statement) { |
| } |
| |
| public void visitCaseStatement( |
| CaseStatement statement, |
| int switchVariableIndex, |
| Label thisLabel, |
| Label nextLabel) { |
| |
| onLineNumber(statement, "visitCaseStatement"); |
| |
| mv.visitVarInsn(ALOAD, switchVariableIndex); |
| statement.getExpression().visit(this); |
| |
| isCaseMethod.call(mv); |
| |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| mv.visitLabel(thisLabel); |
| |
| statement.getCode().visit(this); |
| |
| // now if we don't finish with a break we need to jump past |
| // the next comparison |
| if (nextLabel != null) { |
| mv.visitJumpInsn(GOTO, nextLabel); |
| } |
| |
| mv.visitLabel(l0); |
| } |
| |
| public void visitBreakStatement(BreakStatement statement) { |
| onLineNumber(statement, "visitBreakStatement"); |
| visitStatement(statement); |
| |
| String name = statement.getLabel(); |
| Label breakLabel = compileStack.getNamedBreakLabel(name); |
| compileStack.applyFinallyBlocks(breakLabel, true); |
| |
| mv.visitJumpInsn(GOTO, breakLabel); |
| } |
| |
| public void visitContinueStatement(ContinueStatement statement) { |
| onLineNumber(statement, "visitContinueStatement"); |
| visitStatement(statement); |
| |
| String name = statement.getLabel(); |
| Label continueLabel = compileStack.getContinueLabel(); |
| if (name != null) continueLabel = compileStack.getNamedContinueLabel(name); |
| compileStack.applyFinallyBlocks(continueLabel, false); |
| mv.visitJumpInsn(GOTO, continueLabel); |
| } |
| |
| public void visitSynchronizedStatement(SynchronizedStatement statement) { |
| onLineNumber(statement, "visitSynchronizedStatement"); |
| visitStatement(statement); |
| |
| statement.getExpression().visit(this); |
| final int index = compileStack.defineTemporaryVariable("synchronized", ClassHelper.Integer_TYPE, true); |
| |
| final Label synchronizedStart = new Label(); |
| final Label synchronizedEnd = new Label(); |
| final Label catchAll = new Label(); |
| |
| mv.visitVarInsn(ALOAD, index); |
| mv.visitInsn(MONITORENTER); |
| mv.visitLabel(synchronizedStart); |
| |
| Runnable finallyPart = new Runnable() { |
| public void run() { |
| mv.visitVarInsn(ALOAD, index); |
| mv.visitInsn(MONITOREXIT); |
| } |
| }; |
| compileStack.pushFinallyBlock(finallyPart); |
| statement.getCode().visit(this); |
| |
| finallyPart.run(); |
| mv.visitJumpInsn(GOTO, synchronizedEnd); |
| mv.visitLabel(catchAll); |
| finallyPart.run(); |
| mv.visitInsn(ATHROW); |
| mv.visitLabel(synchronizedEnd); |
| |
| compileStack.popFinallyBlock(); |
| exceptionBlocks.add(new Runnable() { |
| public void run() { |
| mv.visitTryCatchBlock(synchronizedStart, catchAll, catchAll, null); |
| } |
| }); |
| } |
| |
| public void visitThrowStatement(ThrowStatement statement) { |
| onLineNumber(statement, "visitThrowStatement"); |
| visitStatement(statement); |
| |
| statement.getExpression().visit(this); |
| |
| // we should infer the type of the exception from the expression |
| mv.visitTypeInsn(CHECKCAST, "java/lang/Throwable"); |
| |
| mv.visitInsn(ATHROW); |
| } |
| |
| public void visitReturnStatement(ReturnStatement statement) { |
| onLineNumber(statement, "visitReturnStatement"); |
| visitStatement(statement); |
| |
| ClassNode returnType; |
| if (methodNode != null) { |
| returnType = methodNode.getReturnType(); |
| } else if (constructorNode != null) { |
| returnType = constructorNode.getReturnType(); |
| } else { |
| throw new GroovyBugError("I spotted a return that is neither in a method nor in a constructor... I can not handle that"); |
| } |
| |
| if (returnType == ClassHelper.VOID_TYPE) { |
| if (!(statement.isReturningNullOrVoid())) { |
| throwException("Cannot use return statement with an expression on a method that returns void"); |
| } |
| compileStack.applyFinallyBlocks(); |
| mv.visitInsn(RETURN); |
| outputReturn = true; |
| return; |
| } |
| |
| Expression expression = statement.getExpression(); |
| evaluateExpression(expression); |
| if (returnType == ClassHelper.OBJECT_TYPE && expression.getType() != null && expression.getType() == ClassHelper.VOID_TYPE) { |
| mv.visitInsn(ACONST_NULL); // cheat the caller |
| } else { |
| // return is based on class type |
| // we may need to cast |
| doConvertAndCast(returnType, expression, false, true, false); |
| } |
| if (compileStack.hasFinallyBlocks()) { |
| // value is always saved in boxed form, so no need to have a special load routine here |
| int returnValueIdx = compileStack.defineTemporaryVariable("returnValue", ClassHelper.OBJECT_TYPE, true); |
| compileStack.applyFinallyBlocks(); |
| helper.load(ClassHelper.OBJECT_TYPE, returnValueIdx); |
| } |
| // value is always saved in boxed form, so we need to unbox it here |
| helper.unbox(returnType); |
| helper.doReturn(returnType); |
| outputReturn = true; |
| } |
| |
| /** |
| * Casts to the given type unless it can be determined that the cast is unnecessary |
| */ |
| protected void doConvertAndCast(ClassNode type, Expression expression, boolean ignoreAutoboxing, boolean forceCast, boolean coerce) { |
| ClassNode expType = getExpressionType(expression); |
| // temp resolution: convert all primitive casting to corresponding Object type |
| if (!ignoreAutoboxing && ClassHelper.isPrimitiveType(type)) { |
| type = ClassHelper.getWrapper(type); |
| } |
| |
| if (forceCast || (type != null && !expType.isDerivedFrom(type) && !expType.implementsInterface(type))) { |
| doConvertAndCast(type, coerce); |
| } |
| } |
| |
| /** |
| * @param expression |
| */ |
| protected void evaluateExpression(Expression expression) { |
| visitAndAutoboxBoolean(expression); |
| |
| if (isPopRequired(expression)) { |
| return; // we already have the return value |
| } |
| // otherwise create return value if appropriate |
| Expression assignExpr = createReturnLHSExpression(expression); |
| if (assignExpr != null) { |
| leftHandExpression = false; |
| assignExpr.visit(this); |
| } |
| } |
| |
| public void visitExpressionStatement(ExpressionStatement statement) { |
| onLineNumber(statement, "visitExpressionStatement: " + statement.getExpression().getClass().getName()); |
| visitStatement(statement); |
| |
| Expression expression = statement.getExpression(); |
| |
| visitAndAutoboxBoolean(expression); |
| |
| if (isPopRequired(expression)) { |
| mv.visitInsn(POP); |
| } |
| } |
| |
| // Expressions |
| //------------------------------------------------------------------------- |
| |
| public void visitDeclarationExpression(DeclarationExpression expression) { |
| onLineNumber(expression, "visitDeclarationExpression: \"" + expression.getText() + "\""); |
| evaluateEqual(expression,true); |
| } |
| |
| public void visitBinaryExpression(BinaryExpression expression) { |
| onLineNumber(expression, "visitBinaryExpression: \"" + expression.getOperation().getText() + "\" "); |
| switch (expression.getOperation().getType()) { |
| case Types.EQUAL: // = assignment |
| evaluateEqual(expression,false); |
| break; |
| |
| case Types.COMPARE_IDENTICAL: // === |
| evaluateBinaryExpression(compareIdenticalMethod, expression); |
| break; |
| |
| case Types.COMPARE_EQUAL: // == |
| evaluateBinaryExpression(compareEqualMethod, expression); |
| break; |
| |
| case Types.COMPARE_NOT_EQUAL: |
| evaluateBinaryExpression(compareNotEqualMethod, expression); |
| break; |
| |
| case Types.COMPARE_TO: |
| evaluateCompareTo(expression); |
| break; |
| |
| case Types.COMPARE_GREATER_THAN: |
| evaluateBinaryExpression(compareGreaterThanMethod, expression); |
| break; |
| |
| case Types.COMPARE_GREATER_THAN_EQUAL: |
| evaluateBinaryExpression(compareGreaterThanEqualMethod, expression); |
| break; |
| |
| case Types.COMPARE_LESS_THAN: |
| evaluateBinaryExpression(compareLessThanMethod, expression); |
| break; |
| |
| case Types.COMPARE_LESS_THAN_EQUAL: |
| evaluateBinaryExpression(compareLessThanEqualMethod, expression); |
| break; |
| |
| case Types.LOGICAL_AND: |
| evaluateLogicalAndExpression(expression); |
| break; |
| |
| case Types.LOGICAL_OR: |
| evaluateLogicalOrExpression(expression); |
| break; |
| |
| case Types.BITWISE_AND: |
| evaluateBinaryExpression("and", expression); |
| break; |
| |
| case Types.BITWISE_AND_EQUAL: |
| evaluateBinaryExpressionWithAssignment("and", expression); |
| break; |
| |
| case Types.BITWISE_OR: |
| evaluateBinaryExpression("or", expression); |
| break; |
| |
| case Types.BITWISE_OR_EQUAL: |
| evaluateBinaryExpressionWithAssignment("or", expression); |
| break; |
| |
| case Types.BITWISE_XOR: |
| evaluateBinaryExpression("xor", expression); |
| break; |
| |
| case Types.BITWISE_XOR_EQUAL: |
| evaluateBinaryExpressionWithAssignment("xor", expression); |
| break; |
| |
| case Types.PLUS: |
| evaluateBinaryExpression("plus", expression); |
| break; |
| |
| case Types.PLUS_EQUAL: |
| evaluateBinaryExpressionWithAssignment("plus", expression); |
| break; |
| |
| case Types.MINUS: |
| evaluateBinaryExpression("minus", expression); |
| break; |
| |
| case Types.MINUS_EQUAL: |
| evaluateBinaryExpressionWithAssignment("minus", expression); |
| break; |
| |
| case Types.MULTIPLY: |
| evaluateBinaryExpression("multiply", expression); |
| break; |
| |
| case Types.MULTIPLY_EQUAL: |
| evaluateBinaryExpressionWithAssignment("multiply", expression); |
| break; |
| |
| case Types.DIVIDE: |
| evaluateBinaryExpression("div", expression); |
| break; |
| |
| case Types.DIVIDE_EQUAL: |
| //SPG don't use divide since BigInteger implements directly |
| //and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result |
| evaluateBinaryExpressionWithAssignment("div", expression); |
| break; |
| |
| case Types.INTDIV: |
| evaluateBinaryExpression("intdiv", expression); |
| break; |
| |
| case Types.INTDIV_EQUAL: |
| evaluateBinaryExpressionWithAssignment("intdiv", expression); |
| break; |
| |
| case Types.MOD: |
| evaluateBinaryExpression("mod", expression); |
| break; |
| |
| case Types.MOD_EQUAL: |
| evaluateBinaryExpressionWithAssignment("mod", expression); |
| break; |
| |
| case Types.POWER: |
| evaluateBinaryExpression("power", expression); |
| break; |
| |
| case Types.POWER_EQUAL: |
| evaluateBinaryExpressionWithAssignment("power", expression); |
| break; |
| |
| case Types.LEFT_SHIFT: |
| evaluateBinaryExpression("leftShift", expression); |
| break; |
| |
| case Types.LEFT_SHIFT_EQUAL: |
| evaluateBinaryExpressionWithAssignment("leftShift", expression); |
| break; |
| |
| case Types.RIGHT_SHIFT: |
| evaluateBinaryExpression("rightShift", expression); |
| break; |
| |
| case Types.RIGHT_SHIFT_EQUAL: |
| evaluateBinaryExpressionWithAssignment("rightShift", expression); |
| break; |
| |
| case Types.RIGHT_SHIFT_UNSIGNED: |
| evaluateBinaryExpression("rightShiftUnsigned", expression); |
| break; |
| |
| case Types.RIGHT_SHIFT_UNSIGNED_EQUAL: |
| evaluateBinaryExpressionWithAssignment("rightShiftUnsigned", expression); |
| break; |
| |
| case Types.KEYWORD_INSTANCEOF: |
| evaluateInstanceof(expression); |
| break; |
| |
| case Types.FIND_REGEX: |
| evaluateBinaryExpression(findRegexMethod, expression); |
| break; |
| |
| case Types.MATCH_REGEX: |
| evaluateBinaryExpression(matchRegexMethod, expression); |
| break; |
| |
| case Types.LEFT_SQUARE_BRACKET: |
| if (leftHandExpression) { |
| throwException("Should not be called here. Possible reason: postfix operation on array."); |
| // This is handled right now in the evaluateEqual() |
| // should support this here later |
| //evaluateBinaryExpression("putAt", expression); |
| } else { |
| evaluateBinaryExpression("getAt", expression); |
| } |
| break; |
| |
| case Types.KEYWORD_IN: |
| evaluateBinaryExpression(isCaseMethod, expression); |
| break; |
| |
| default: |
| throwException("Operation: " + expression.getOperation() + " not supported"); |
| } |
| } |
| |
| private void load(Expression exp) { |
| |
| boolean wasLeft = leftHandExpression; |
| leftHandExpression = false; |
| // if (CREATE_DEBUG_INFO) |
| // helper.mark("-- loading expression: " + exp.getClass().getName() + |
| // " at [" + exp.getLineNumber() + ":" + exp.getColumnNumber() + "]"); |
| //exp.visit(this); |
| visitAndAutoboxBoolean(exp); |
| // if (CREATE_DEBUG_INFO) |
| // helper.mark(" -- end of loading --"); |
| |
| leftHandExpression = wasLeft; |
| } |
| |
| public void visitPostfixExpression(PostfixExpression expression) { |
| switch (expression.getOperation().getType()) { |
| case Types.PLUS_PLUS: |
| evaluatePostfixMethod("next", expression.getExpression()); |
| break; |
| case Types.MINUS_MINUS: |
| evaluatePostfixMethod("previous", expression.getExpression()); |
| break; |
| } |
| } |
| |
| private void throwException(String s) { |
| throw new RuntimeParserException(s, currentASTNode); |
| } |
| |
| public void visitPrefixExpression(PrefixExpression expression) { |
| switch (expression.getOperation().getType()) { |
| case Types.PLUS_PLUS: |
| evaluatePrefixMethod("next", expression.getExpression()); |
| break; |
| case Types.MINUS_MINUS: |
| evaluatePrefixMethod("previous", expression.getExpression()); |
| break; |
| } |
| } |
| |
| public void visitClosureExpression(ClosureExpression expression) { |
| ClassNode innerClass = (ClassNode) closureClassMap.get(expression); |
| if (innerClass==null) { |
| innerClass = createClosureClass(expression); |
| closureClassMap.put(expression,innerClass); |
| addInnerClass(innerClass); |
| innerClass.addInterface(ClassHelper.GENERATED_CLOSURE_Type); |
| } |
| |
| String innerClassinternalName = BytecodeHelper.getClassInternalName(innerClass); |
| passingClosureParams = true; |
| List constructors = innerClass.getDeclaredConstructors(); |
| ConstructorNode node = (ConstructorNode) constructors.get(0); |
| |
| Parameter[] localVariableParams = node.getParameters(); |
| |
| mv.visitTypeInsn(NEW, innerClassinternalName); |
| mv.visitInsn(DUP); |
| if ((isStaticMethod() || specialCallWithinConstructor) && !classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) { |
| visitClassExpression(new ClassExpression(classNode)); |
| visitClassExpression(new ClassExpression(getOutermostClass())); |
| } else { |
| mv.visitVarInsn(ALOAD, 0); |
| loadThis(); |
| } |
| |
| // now let's load the various parameters we're passing |
| // we start at index 1 because the first variable we pass |
| // is the owner instance and at this point it is already |
| // on the stack |
| for (int i = 2; i < localVariableParams.length; i++) { |
| Parameter param = localVariableParams[i]; |
| String name = param.getName(); |
| |
| // compileStack.containsVariable(name) means to ask if the variable is already declared |
| // compileStack.getScope().isReferencedClassVariable(name) means to ask if the variable is a field |
| // If it is no field and is not yet declared, then it is either a closure shared variable or |
| // an already declared variable. |
| if (!compileStack.containsVariable(name) && compileStack.getScope().isReferencedClassVariable(name)) { |
| visitFieldExpression(new FieldExpression(classNode.getDeclaredField(name))); |
| } else { |
| Variable v = compileStack.getVariable(name, classNode.getSuperClass() != ClassHelper.CLOSURE_TYPE); |
| if (v == null) { |
| // variable is not on stack because we are |
| // inside a nested Closure and this variable |
| // was not used before |
| // then load it from the Closure field |
| FieldNode field = classNode.getDeclaredField(name); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, internalClassName, name, BytecodeHelper.getTypeDescription(field.getType())); |
| // and define it |
| // Note: |
| // we can simply define it here and don't have to |
| // be afraid about name problems because a second |
| // variable with that name is not allowed inside the closure |
| param.setClosureSharedVariable(false); |
| v = compileStack.defineVariable(param, true); |
| param.setClosureSharedVariable(true); |
| v.setHolder(true); |
| } |
| mv.visitVarInsn(ALOAD, v.getIndex()); |
| } |
| } |
| passingClosureParams = false; |
| |
| // we may need to pass in some other constructors |
| //cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V"); |
| mv.visitMethodInsn( |
| INVOKESPECIAL, |
| innerClassinternalName, |
| "<init>", |
| BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams)); |
| } |
| |
| /** |
| * Loads either this object or if we're inside a closure then load the top level owner |
| */ |
| protected void loadThisOrOwner() { |
| if (isInnerClass()) { |
| visitFieldExpression(new FieldExpression(classNode.getDeclaredField("owner"))); |
| } else { |
| loadThis(); |
| } |
| } |
| |
| public void visitRegexExpression(RegexExpression expression) { |
| expression.getRegex().visit(this); |
| regexPattern.call(mv); |
| } |
| |
| /** |
| * Generate byte code for constants |
| * |
| * @see <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152">Class field types</a> |
| */ |
| public void visitConstantExpression(ConstantExpression expression) { |
| final String constantName = expression.getConstantName(); |
| if ((methodNode != null && methodNode.getName().equals("<clinit>")) || constantName == null) { |
| Object value = expression.getValue(); |
| helper.loadConstant(value); |
| } |
| else { |
| mv.visitFieldInsn(GETSTATIC, internalClassName,constantName, BytecodeHelper.getTypeDescription(expression.getType())); |
| } |
| } |
| |
| public void visitSpreadExpression(SpreadExpression expression) { |
| throw new GroovyBugError("SpreadExpression should not be visited here"); |
| } |
| |
| public void visitSpreadMapExpression(SpreadMapExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| spreadMap.call(mv); |
| } |
| |
| public void visitMethodPointerExpression(MethodPointerExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| loadDynamicName(expression.getMethodName()); |
| getMethodPointer.call(mv); |
| } |
| |
| private void loadDynamicName(Expression name) { |
| if (name instanceof ConstantExpression) { |
| ConstantExpression ce = (ConstantExpression) name; |
| Object value = ce.getValue(); |
| if (value instanceof String) { |
| helper.loadConstant(value); |
| return; |
| } |
| } |
| new CastExpression(ClassHelper.STRING_TYPE, name).visit(this); |
| } |
| |
| public void visitUnaryMinusExpression(UnaryMinusExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| unaryMinus.call(mv); |
| } |
| |
| public void visitUnaryPlusExpression(UnaryPlusExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| unaryPlus.call(mv); |
| } |
| |
| public void visitBitwiseNegationExpression(BitwiseNegationExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| bitwiseNegate.call(mv); |
| } |
| |
| public void visitCastExpression(CastExpression castExpression) { |
| ClassNode type = castExpression.getType(); |
| visitAndAutoboxBoolean(castExpression.getExpression()); |
| final ClassNode rht = rightHandType; |
| rightHandType = castExpression.getExpression().getType(); |
| doConvertAndCast(type, castExpression.getExpression(), castExpression.isIgnoringAutoboxing(), false, castExpression.isCoerce()); |
| rightHandType = rht; |
| } |
| |
| public void visitNotExpression(NotExpression expression) { |
| Expression subExpression = expression.getExpression(); |
| subExpression.visit(this); |
| // if we do !object, then the cast to boolean will |
| // do the conversion of Object to boolean. so a simple |
| // call to unbox is enough here. |
| if ( |
| !isComparisonExpression(subExpression) && |
| !(subExpression instanceof BooleanExpression)) { |
| helper.unbox(boolean.class); |
| } |
| helper.negateBoolean(); |
| } |
| |
| /** |
| * return a primitive boolean value of the BooleanExpresion. |
| * |
| * @param expression |
| */ |
| public void visitBooleanExpression(BooleanExpression expression) { |
| compileStack.pushBooleanExpression(); |
| expression.getExpression().visit(this); |
| |
| if (!isComparisonExpression(expression.getExpression())) { |
| // comment out for optimization when boolean values are not autoboxed for eg. function calls. |
| // Class typeClass = expression.getExpression().getTypeClass(); |
| // if (typeClass != null && typeClass != boolean.class) { |
| helper.unbox(boolean.class); // to return a primitive boolean |
| // } |
| } |
| compileStack.pop(); |
| } |
| |
| private void makeInvokeMethodCall(MethodCallExpression call, boolean useSuper, MethodCallerMultiAdapter adapter) { |
| // receiver |
| // we operate on GroovyObject if possible |
| Expression objectExpression = call.getObjectExpression(); |
| if (!isStaticMethod() && !isStaticContext() && isThisExpression(call.getObjectExpression())) { |
| objectExpression = new CastExpression(classNode, objectExpression); |
| } |
| // message name |
| Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod()); |
| if (useSuper) { |
| makeCall(new ClassExpression(getOutermostClass().getSuperClass()), |
| objectExpression, messageName, |
| call.getArguments(), adapter, |
| call.isSafe(), call.isSpreadSafe(), |
| false |
| ); |
| } else { |
| makeCall(objectExpression, messageName, |
| call.getArguments(), adapter, |
| call.isSafe(), call.isSpreadSafe(), |
| call.isImplicitThis() |
| ); |
| } |
| } |
| |
| private void makeCall( |
| Expression receiver, Expression message, Expression arguments, |
| MethodCallerMultiAdapter adapter, |
| boolean safe, boolean spreadSafe, boolean implicitThis |
| ) { |
| ClassNode cn = classNode; |
| if (isInClosure() && !implicitThis) { |
| cn = getOutermostClass(); |
| } |
| makeCall(new ClassExpression(cn), receiver, message, arguments, |
| adapter, safe, spreadSafe, implicitThis); |
| } |
| |
| private void makeCall( |
| ClassExpression sender, |
| Expression receiver, Expression message, Expression arguments, |
| MethodCallerMultiAdapter adapter, |
| boolean safe, boolean spreadSafe, boolean implicitThis |
| ) { |
| if ((adapter == invokeMethod || adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod)&& !spreadSafe) { |
| String methodName = getMethodName(message); |
| |
| if (methodName != null) { |
| makeCallSite(receiver, methodName, arguments, safe, implicitThis, adapter == invokeMethodOnCurrent, adapter == invokeStaticMethod); |
| return; |
| } |
| } |
| |
| // ensure VariableArguments are read, not stored |
| boolean lhs = leftHandExpression; |
| leftHandExpression = false; |
| |
| // sender |
| sender.visit(this); |
| // receiver |
| boolean oldVal = this.implicitThis; |
| this.implicitThis = implicitThis; |
| visitAndAutoboxBoolean(receiver); |
| this.implicitThis = oldVal; |
| // message |
| if (message != null) message.visit(this); |
| |
| // arguments |
| boolean containsSpreadExpression = containsSpreadExpression(arguments); |
| int numberOfArguments = containsSpreadExpression ? -1 : argumentSize(arguments); |
| if (numberOfArguments > MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) { |
| ArgumentListExpression ae; |
| if (arguments instanceof ArgumentListExpression) { |
| ae = (ArgumentListExpression) arguments; |
| } else if (arguments instanceof TupleExpression) { |
| TupleExpression te = (TupleExpression) arguments; |
| ae = new ArgumentListExpression(te.getExpressions()); |
| } else { |
| ae = new ArgumentListExpression(); |
| ae.addExpression(arguments); |
| } |
| if (containsSpreadExpression) { |
| despreadList(ae.getExpressions(), true); |
| } else { |
| ae.visit(this); |
| } |
| } else if (numberOfArguments > 0) { |
| TupleExpression te = (TupleExpression) arguments; |
| for (int i = 0; i < numberOfArguments; i++) { |
| Expression argument = te.getExpression(i); |
| visitAndAutoboxBoolean(argument); |
| if (argument instanceof CastExpression) loadWrapper(argument); |
| } |
| } |
| |
| adapter.call(mv, numberOfArguments, safe, spreadSafe); |
| |
| leftHandExpression = lhs; |
| } |
| |
| private void makeGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) { |
| if (isNotClinit()) { |
| mv.visitVarInsn(ALOAD, callSiteArrayVarIndex); |
| } |
| else { |
| mv.visitMethodInsn(INVOKESTATIC,getClassName(),"$getCallSiteArray","()[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| } |
| final int index = allocateIndex(methodName); |
| mv.visitLdcInsn(index); |
| mv.visitInsn(AALOAD); |
| |
| // site |
| boolean lhs = leftHandExpression; |
| leftHandExpression = false; |
| boolean oldVal = this.implicitThis; |
| this.implicitThis = implicitThis; |
| visitAndAutoboxBoolean(receiver); |
| this.implicitThis = oldVal; |
| if (!safe) |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callGetProperty","(Ljava/lang/Object;)Ljava/lang/Object;"); |
| else { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callGetPropertySafe","(Ljava/lang/Object;)Ljava/lang/Object;"); |
| } |
| leftHandExpression = lhs; |
| } |
| |
| private void makeGroovyObjectGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) { |
| if (isNotClinit()) { |
| mv.visitVarInsn(ALOAD, callSiteArrayVarIndex); |
| } |
| else { |
| mv.visitMethodInsn(INVOKESTATIC,getClassName(),"$getCallSiteArray","()[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| } |
| final int index = allocateIndex(methodName); |
| mv.visitLdcInsn(index); |
| mv.visitInsn(AALOAD); |
| |
| // site |
| boolean lhs = leftHandExpression; |
| leftHandExpression = false; |
| boolean oldVal = this.implicitThis; |
| this.implicitThis = implicitThis; |
| visitAndAutoboxBoolean(receiver); |
| this.implicitThis = oldVal; |
| if (!safe) |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callGroovyObjectGetProperty","(Ljava/lang/Object;)Ljava/lang/Object;"); |
| else { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callGroovyObjectGetPropertySafe","(Ljava/lang/Object;)Ljava/lang/Object;"); |
| } |
| leftHandExpression = lhs; |
| } |
| |
| private String getMethodName(Expression message) { |
| String methodName = null; |
| if (message instanceof CastExpression) { |
| CastExpression msg = (CastExpression) message; |
| if (msg.getType() == ClassHelper.STRING_TYPE) { |
| final Expression methodExpr = msg.getExpression(); |
| if (methodExpr instanceof ConstantExpression) |
| methodName = methodExpr.getText(); |
| } |
| } |
| |
| if (methodName == null && message instanceof ConstantExpression) { |
| ConstantExpression constantExpression = (ConstantExpression) message; |
| methodName = constantExpression.getText(); |
| } |
| return methodName; |
| } |
| |
| private void makeCallSite(Expression receiver, String message, Expression arguments, boolean safe, boolean implicitThis, boolean callCurrent, boolean callStatic) { |
| if (isNotClinit()) { |
| mv.visitVarInsn(ALOAD, callSiteArrayVarIndex); |
| } |
| else { |
| mv.visitMethodInsn(INVOKESTATIC,getClassName(),"$getCallSiteArray","()[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| } |
| final int index = allocateIndex(message); |
| mv.visitLdcInsn(index); |
| mv.visitInsn(AALOAD); |
| |
| boolean constructor = message.equals(CONSTRUCTOR); |
| |
| // ensure VariableArguments are read, not stored |
| boolean lhs = leftHandExpression; |
| leftHandExpression = false; |
| |
| // receiver |
| boolean oldVal = this.implicitThis; |
| this.implicitThis = implicitThis; |
| visitAndAutoboxBoolean(receiver); |
| this.implicitThis = oldVal; |
| |
| // arguments |
| boolean containsSpreadExpression = containsSpreadExpression(arguments); |
| int numberOfArguments = containsSpreadExpression ? -1 : argumentSize(arguments); |
| if (numberOfArguments > MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) { |
| ArgumentListExpression ae; |
| if (arguments instanceof ArgumentListExpression) { |
| ae = (ArgumentListExpression) arguments; |
| } else if (arguments instanceof TupleExpression) { |
| TupleExpression te = (TupleExpression) arguments; |
| ae = new ArgumentListExpression(te.getExpressions()); |
| } else { |
| ae = new ArgumentListExpression(); |
| ae.addExpression(arguments); |
| } |
| if (containsSpreadExpression) { |
| numberOfArguments = -1; |
| despreadList(ae.getExpressions(), true); |
| } else { |
| numberOfArguments = ae.getExpressions().size(); |
| for (int i = 0; i < numberOfArguments; i++) { |
| Expression argument = ae.getExpression(i); |
| visitAndAutoboxBoolean(argument); |
| if (argument instanceof CastExpression) loadWrapper(argument); |
| } |
| } |
| } |
| |
| if (numberOfArguments == -1) { |
| // despreaded array already on stack |
| } |
| else { |
| if (numberOfArguments > 4) { |
| final String createArraySignature = getCreateArraySignature(numberOfArguments); |
| mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/ArrayUtil", "createArray", createArraySignature); |
| } |
| } |
| |
| final String desc = getDescForParamNum(numberOfArguments); |
| if (callStatic) { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callStatic", "(Ljava/lang/Class;" + desc); |
| } |
| else |
| if (constructor) { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callConstructor", "(Ljava/lang/Object;" + desc); |
| } |
| else { |
| if (callCurrent) { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callCurrent", "(Lgroovy/lang/GroovyObject;" + desc); |
| } |
| else { |
| if (safe) { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "callSafe", "(Ljava/lang/Object;" + desc); |
| } |
| else { |
| mv.visitMethodInsn(INVOKEINTERFACE,"org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;" + desc); |
| } |
| } |
| } |
| leftHandExpression = lhs; |
| } |
| |
| private static String getDescForParamNum(int numberOfArguments) { |
| switch (numberOfArguments) { |
| case 0: |
| return ")Ljava/lang/Object;"; |
| case 1: |
| return "Ljava/lang/Object;)Ljava/lang/Object;"; |
| case 2: |
| return "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; |
| case 3: |
| return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; |
| case 4: |
| return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; |
| default: |
| return "[Ljava/lang/Object;)Ljava/lang/Object;"; |
| } |
| } |
| |
| private static String [] sig = new String [255]; |
| private static String getCreateArraySignature(int numberOfArguments) { |
| if (sig[numberOfArguments] == null) { |
| StringBuilder sb = new StringBuilder("("); |
| for (int i = 0; i != numberOfArguments; ++i) { |
| sb.append("Ljava/lang/Object;"); |
| } |
| sb.append(")[Ljava/lang/Object;"); |
| sig[numberOfArguments] = sb.toString(); |
| } |
| return sig[numberOfArguments]; |
| } |
| |
| private static final HashSet<String> names = new HashSet<String>(); |
| private static final HashSet<String> basic = new HashSet<String>(); |
| |
| static { |
| Collections.addAll(names, "plus", "minus", "multiply", "div", "compareTo", "or", "and", "xor", "intdiv", "mod", "leftShift", "rightShift", "rightShiftUnsigned"); |
| Collections.addAll(basic, "plus", "minus", "multiply", "div"); |
| } |
| |
| private void makeBinopCallSite(BinaryExpression bin, String message) { |
| final Expression left = bin.getLeftExpression(); |
| final Expression right = bin.getRightExpression(); |
| if (!names.contains(message)) { |
| makeBinopCallSite(left, message, right); |
| } |
| else { |
| improveExprType(bin); |
| |
| ClassNode type1 = getLHSType(left); |
| ClassNode type2 = getLHSType(right); |
| if (ClassHelper.isNumberType(type1) && ClassHelper.isNumberType(type2)) { |
| ClassNode prim1 = ClassHelper.getUnwrapper(type1); |
| ClassNode prim2 = ClassHelper.getUnwrapper(type2); |
| |
| if (message.equals("div") && prim1 == ClassHelper.int_TYPE && prim2 == ClassHelper.int_TYPE) { |
| makeBinopCallSite(left, message, right); |
| return; |
| } |
| |
| ClassNode retType; |
| if (prim1 == ClassHelper.double_TYPE || prim2 == ClassHelper.double_TYPE) { |
| retType = ClassHelper.double_TYPE; |
| } |
| else |
| if (prim1 == ClassHelper.float_TYPE || prim2 == ClassHelper.float_TYPE) { |
| retType = ClassHelper.double_TYPE; |
| } |
| else |
| if (prim1 == ClassHelper.long_TYPE || prim2 == ClassHelper.long_TYPE) { |
| retType = ClassHelper.long_TYPE; |
| } |
| else |
| retType = ClassHelper.int_TYPE; |
| |
| if (retType == ClassHelper.double_TYPE && !basic.contains(message)) { |
| makeBinopCallSite(left, message, right); |
| return; |
| } |
| |
| if (left instanceof ConstantExpression) { |
| mv.visitLdcInsn(((ConstantExpression)left).getValue()); |
| } |
| else { |
| visitAndAutoboxBoolean(left); |
| helper.unbox(prim1); |
| } |
| |
| if (right instanceof ConstantExpression) { |
| mv.visitLdcInsn(((ConstantExpression)right).getValue()); |
| } |
| else { |
| visitAndAutoboxBoolean(right); |
| helper.unbox(prim2); |
| } |
| |
| mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/typehandling/NumberMathModificationInfo", message, "(" + BytecodeHelper.getTypeDescription(prim1) + BytecodeHelper.getTypeDescription(prim2) + ")" + BytecodeHelper.getTypeDescription(retType)); |
| helper.box(retType); |
| } |
| else { |
| makeBinopCallSite(left, message, right); |
| } |
| } |
| } |
| |
| private void improveExprType(Expression expr) { |
| if (expr instanceof BinaryExpression) { |
| if (ClassHelper.isNumberType(expr.getType())) |
| return; |
| |
| final BinaryExpression bin = (BinaryExpression) expr; |
| String message = ""; |
| switch (bin.getOperation().getType()) { |
| case Types.BITWISE_AND: |
| message = "and"; |
| break; |
| |
| case Types.BITWISE_OR: |
| message = "or"; |
| break; |
| |
| case Types.BITWISE_XOR: |
| message = "xor"; |
| break; |
| |
| case Types.PLUS: |
| message = "plus"; |
| break; |
| |
| case Types.MINUS: |
| message = "minus"; |
| break; |
| |
| case Types.MULTIPLY: |
| message = "multiply"; |
| break; |
| |
| case Types.DIVIDE: |
| message = "div"; |
| break; |
| |
| case Types.INTDIV: |
| message = "intdiv"; |
| break; |
| |
| case Types.MOD: |
| message = "mod"; |
| break; |
| |
| case Types.LEFT_SHIFT: |
| message = "leftShift"; |
| break; |
| |
| case Types.RIGHT_SHIFT: |
| message = "rightShift"; |
| break; |
| |
| case Types.RIGHT_SHIFT_UNSIGNED: |
| message = "rightShiftUnsigned"; |
| break; |
| } |
| |
| if (!names.contains(message)) |
| return; |
| |
| improveExprType(bin.getLeftExpression()); |
| improveExprType(bin.getRightExpression()); |
| |
| ClassNode type1 = getLHSType(bin.getLeftExpression()); |
| ClassNode type2 = getLHSType(bin.getRightExpression()); |
| |
| if (ClassHelper.isNumberType(type1) && ClassHelper.isNumberType(type2)) { |
| ClassNode prim1 = ClassHelper.getUnwrapper(type1); |
| ClassNode prim2 = ClassHelper.getUnwrapper(type2); |
| |
| if (message.equals("div") && prim1 == ClassHelper.int_TYPE && prim2 == ClassHelper.int_TYPE) { |
| return; |
| } |
| |
| ClassNode retType; |
| if (prim1 == ClassHelper.double_TYPE || prim2 == ClassHelper.double_TYPE) { |
| retType = ClassHelper.double_TYPE; |
| } |
| else |
| if (prim1 == ClassHelper.float_TYPE || prim2 == ClassHelper.float_TYPE) { |
| retType = ClassHelper.double_TYPE; |
| } |
| else |
| if (prim1 == ClassHelper.long_TYPE || prim2 == ClassHelper.long_TYPE) { |
| retType = ClassHelper.long_TYPE; |
| } |
| else |
| retType = ClassHelper.int_TYPE; |
| |
| if (retType == ClassHelper.double_TYPE && !basic.contains(message)) { |
| return; |
| } |
| |
| bin.setType(retType); |
| } |
| } |
| } |
| |
| private void makeBinopCallSite(Expression receiver, String message, Expression arguments) { |
| |
| prepareCallSite(message); |
| |
| // site |
| |
| // ensure VariableArguments are read, not stored |
| boolean lhs = leftHandExpression; |
| leftHandExpression = false; |
| boolean oldVal = this.implicitThis; |
| this.implicitThis = false; |
| visitAndAutoboxBoolean(receiver); |
| this.implicitThis = oldVal; |
| visitAndAutoboxBoolean(arguments); |
| mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| leftHandExpression = lhs; |
| } |
| |
| private void prepareCallSite(String message) { |
| if (isNotClinit()) { |
| mv.visitVarInsn(ALOAD, callSiteArrayVarIndex); |
| } |
| else { |
| mv.visitMethodInsn(INVOKESTATIC,getClassName(),"$getCallSiteArray","()[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); |
| } |
| final int index = allocateIndex(message); |
| mv.visitLdcInsn(index); |
| mv.visitInsn(AALOAD); |
| } |
| |
| private String getClassName() { |
| String className; |
| if (!classNode.isInterface() || interfaceClassLoadingClass == null) { |
| className = internalClassName; |
| } else { |
| className = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass); |
| } |
| return className; |
| } |
| |
| private int allocateIndex(String name) { |
| callSites.add(name); |
| return callSites.size()-1; |
| } |
| |
| private void despreadList(List expressions, boolean wrap) { |
| |
| ArrayList spreadIndexes = new ArrayList(); |
| ArrayList spreadExpressions = new ArrayList(); |
| ArrayList normalArguments = new ArrayList(); |
| for (int i = 0; i < expressions.size(); i++) { |
| Object expr = expressions.get(i); |
| if (!(expr instanceof SpreadExpression)) { |
| normalArguments.add(expr); |
| } else { |
| spreadIndexes.add(new ConstantExpression(Integer.valueOf(i - spreadExpressions.size()))); |
| spreadExpressions.add(((SpreadExpression) expr).getExpression()); |
| } |
| } |
| |
| //load normal arguments as array |
| visitTupleExpression(new ArgumentListExpression(normalArguments), wrap); |
| //load spread expressions as array |
| (new TupleExpression(spreadExpressions)).visit(this); |
| //load insertion index |
| (new ArrayExpression(ClassHelper.int_TYPE, spreadIndexes, null)).visit(this); |
| despreadList.call(mv); |
| } |
| |
| public void visitMethodCallExpression(MethodCallExpression call) { |
| onLineNumber(call, "visitMethodCallExpression: \"" + call.getMethod() + "\":"); |
| |
| if (isClosureCall(call)) { |
| // let's invoke the closure method |
| invokeClosure(call.getArguments(), call.getMethodAsString()); |
| } else { |
| boolean isSuperMethodCall = usesSuper(call); |
| MethodCallerMultiAdapter adapter = invokeMethod; |
| if (isThisExpression(call.getObjectExpression())) adapter = invokeMethodOnCurrent; |
| if (isSuperMethodCall) adapter = invokeMethodOnSuper; |
| if (isStaticInvocation(call)) adapter = invokeStaticMethod; |
| makeInvokeMethodCall(call, isSuperMethodCall, adapter); |
| } |
| } |
| |
| private boolean isClosureCall(MethodCallExpression call) { |
| // are we a local variable? |
| // it should not be an explicitly "this" qualified method call |
| // and the current class should have a possible method |
| String methodName = call.getMethodAsString(); |
| if (methodName==null) return false; |
| if (!call.isImplicitThis()) return false; |
| if (!isThisExpression(call.getObjectExpression())) return false; |
| FieldNode field = classNode.getDeclaredField(methodName); |
| if (field == null) return false; |
| if (isStaticInvocation(call) && !field.isStatic()) return false; |
| Expression arguments = call.getArguments(); |
| return ! classNode.hasPossibleMethod(methodName, arguments); |
| } |
| |
| private void invokeClosure(Expression arguments, String methodName) { |
| visitVariableExpression(new VariableExpression(methodName)); |
| if (arguments instanceof TupleExpression) { |
| arguments.visit(this); |
| } else { |
| new TupleExpression(arguments).visit(this); |
| } |
| invokeClosureMethod.call(mv); |
| } |
| |
| private boolean isStaticInvocation(MethodCallExpression call) { |
| if (!isThisExpression(call.getObjectExpression())) return false; |
| if (isStaticMethod()) return true; |
| return isStaticContext() && !call.isImplicitThis(); |
| } |
| |
| protected boolean emptyArguments(Expression arguments) { |
| return argumentSize(arguments) == 0; |
| } |
| |
| protected static boolean containsSpreadExpression(Expression arguments) { |
| List args = null; |
| if (arguments instanceof TupleExpression) { |
| TupleExpression tupleExpression = (TupleExpression) arguments; |
| args = tupleExpression.getExpressions(); |
| } else if (arguments instanceof ListExpression) { |
| ListExpression le = (ListExpression) arguments; |
| args = le.getExpressions(); |
| } else { |
| return arguments instanceof SpreadExpression; |
| } |
| for (Iterator iter = args.iterator(); iter.hasNext();) { |
| if (iter.next() instanceof SpreadExpression) return true; |
| } |
| return false; |
| } |
| |
| protected static int argumentSize(Expression arguments) { |
| if (arguments instanceof TupleExpression) { |
| TupleExpression tupleExpression = (TupleExpression) arguments; |
| int size = tupleExpression.getExpressions().size(); |
| return size; |
| } |
| return 1; |
| } |
| |
| public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { |
| onLineNumber(call, "visitStaticMethodCallExpression: \"" + call.getMethod() + "\":"); |
| |
| makeCall( |
| new ClassExpression(call.getOwnerType()), |
| new ConstantExpression(call.getMethod()), |
| call.getArguments(), |
| invokeStaticMethod, |
| false, false, false); |
| } |
| |
| private void addGeneratedClosureConstructorCall(ConstructorCallExpression call) { |
| mv.visitVarInsn(ALOAD, 0); |
| ClassNode callNode = classNode.getSuperClass(); |
| TupleExpression arguments = (TupleExpression) call.getArguments(); |
| if (arguments.getExpressions().size()!=2) throw new GroovyBugError("expected 2 arguments for closure constructor super call, but got"+arguments.getExpressions().size()); |
| arguments.getExpression(0).visit(this); |
| arguments.getExpression(1).visit(this); |
| Parameter p = new Parameter(ClassHelper.OBJECT_TYPE,"_p"); |
| String descriptor = helper.getMethodDescriptor(ClassHelper.VOID_TYPE, new Parameter[]{p,p}); |
| mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor); |
| } |
| |
| private void visitSpecialConstructorCall(ConstructorCallExpression call) { |
| if (classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) { |
| addGeneratedClosureConstructorCall(call); |
| return; |
| } |
| |
| ClassNode callNode = classNode; |
| if (call.isSuperCall()) callNode = callNode.getSuperClass(); |
| List constructors = sortConstructors(call, callNode); |
| call.getArguments().visit(this); |
| // keep Object[] on stack |
| mv.visitInsn(DUP); |
| // to select the constructor we need also the number of |
| // available constructors and the class we want to make |
| // the call on |
| helper.pushConstant(constructors.size()); |
| visitClassExpression(new ClassExpression(callNode)); |
| // removes one Object[] leaves the int containing the |
| // call flags and the constructor number |
| selectConstructorAndTransformArguments.call(mv); |
| // Object[],int -> int,Object[],int |
| // we need to examine the flags and maybe change the |
| // Object[] later, so this reordering will do the job |
| mv.visitInsn(DUP_X1); |
| // test if rewrap flag is set |
| mv.visitInsn(ICONST_1); |
| mv.visitInsn(IAND); |
| Label afterIf = new Label(); |
| mv.visitJumpInsn(IFEQ, afterIf); |
| // true part, so rewrap using the first argument |
| mv.visitInsn(ICONST_0); |
| mv.visitInsn(AALOAD); |
| mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;"); |
| mv.visitLabel(afterIf); |
| // here the stack is int,Object[], but we need the |
| // the int for our table, so swap it |
| mv.visitInsn(SWAP); |
| //load "this" |
| if (constructorNode!=null) { |
| mv.visitVarInsn(ALOAD, 0); |
| } else { |
| mv.visitTypeInsn(NEW, BytecodeHelper.getClassInternalName(callNode)); |
| } |
| mv.visitInsn(SWAP); |
| //prepare switch with >>8 |
| mv.visitIntInsn(BIPUSH, 8); |
| mv.visitInsn(ISHR); |
| Label[] targets = new Label[constructors.size()]; |
| int[] indices = new int[constructors.size()]; |
| for (int i = 0; i < targets.length; i++) { |
| targets[i] = new Label(); |
| indices[i] = i; |
| } |
| // create switch targets |
| Label defaultLabel = new Label(); |
| Label afterSwitch = new Label(); |
| mv.visitLookupSwitchInsn(defaultLabel, indices, targets); |
| for (int i = 0; i < targets.length; i++) { |
| mv.visitLabel(targets[i]); |
| // to keep the stack height, we need to leave |
| // one Object[] on the stack as last element. At the |
| // same time, we need the Object[] on top of the stack |
| // to extract the parameters. |
| if (constructorNode!=null) { |
| // in this case we need one "this", so a SWAP will exchange |
| // "this" and Object[], a DUP_X1 will then copy the Object[] |
| /// to the last place in the stack: |
| // Object[],this -SWAP-> this,Object[] |
| // this,Object[] -DUP_X1-> Object[],this,Object[] |
| mv.visitInsn(SWAP); |
| mv.visitInsn(DUP_X1); |
| } else { |
| // in this case we need two "this" in between and the Object[] |
| // at the bottom of the stack as well as on top for our invokeSpecial |
| // So we do DUP_X1, DUP2_X1, POP |
| // Object[],this -DUP_X1-> this,Object[],this |
| // this,Object[],this -DUP2_X1-> Object[],this,this,Object[],this |
| // Object[],this,this,Object[],this -POP-> Object[],this,this,Object[] |
| mv.visitInsn(DUP_X1); |
| mv.visitInsn(DUP2_X1); |
| mv.visitInsn(POP); |
| } |
| |
| ConstructorNode cn = (ConstructorNode) constructors.get(i); |
| String descriptor = helper.getMethodDescriptor(ClassHelper.VOID_TYPE, cn.getParameters()); |
| // unwrap the Object[] and make transformations if needed |
| // that means, to duplicate the Object[], make a cast with possible |
| // unboxing and then swap it with the Object[] for each parameter |
| Parameter[] parameters = cn.getParameters(); |
| for (int p = 0; p < parameters.length; p++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(p); |
| mv.visitInsn(AALOAD); |
| ClassNode type = parameters[p].getType(); |
| if (ClassHelper.isPrimitiveType(type)) { |
| helper.unbox(type); |
| } else { |
| helper.doCast(type); |
| } |
| helper.swapWithObject(type); |
| } |
| // at the end we remove the Object[] |
| mv.visitInsn(POP); |
| // make the constructor call |
| mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor); |
| mv.visitJumpInsn(GOTO, afterSwitch); |
| } |
| mv.visitLabel(defaultLabel); |
| // this part should never be reached! |
| mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); |
| mv.visitInsn(DUP); |
| mv.visitLdcInsn("illegal constructor number"); |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V"); |
| mv.visitInsn(ATHROW); |
| mv.visitLabel(afterSwitch); |
| |
| // to keep the stack height we kept one object on the stack |
| // for the switch, now we remove that object |
| if (constructorNode==null) { |
| // but in case we are not in a constructor we have an additional |
| // object on the stack, the result of our constructor call |
| // which we want to keep, so we swap the arguments to remove |
| // the right one |
| mv.visitInsn(SWAP); |
| } |
| mv.visitInsn(POP); |
| } |
| |
| private List sortConstructors(ConstructorCallExpression call, ClassNode callNode) { |
| // sort in a new list to prevent side effects |
| List constructors = new ArrayList(callNode.getDeclaredConstructors()); |
| Comparator comp = new Comparator() { |
| public int compare(Object arg0, Object arg1) { |
| ConstructorNode c0 = (ConstructorNode) arg0; |
| ConstructorNode c1 = (ConstructorNode) arg1; |
| String descriptor0 = helper.getMethodDescriptor(ClassHelper.VOID_TYPE, c0.getParameters()); |
| String descriptor1 = helper.getMethodDescriptor(ClassHelper.VOID_TYPE, c1.getParameters()); |
| return descriptor0.compareTo(descriptor1); |
| } |
| }; |
| Collections.sort(constructors, comp); |
| return constructors; |
| } |
| |
| public void visitConstructorCallExpression(ConstructorCallExpression call) { |
| onLineNumber(call, "visitConstructorCallExpression: \"" + call.getType().getName() + "\":"); |
| |
| if (call.isSpecialCall()) { |
| specialCallWithinConstructor = true; |
| visitSpecialConstructorCall(call); |
| // reset the variable |
| specialCallWithinConstructor = false; |
| return; |
| } |
| |
| Expression arguments = call.getArguments(); |
| if (arguments instanceof TupleExpression) { |
| TupleExpression tupleExpression = (TupleExpression) arguments; |
| int size = tupleExpression.getExpressions().size(); |
| if (size == 0) { |
| arguments = MethodCallExpression.NO_ARGUMENTS; |
| } |
| } |
| |
| Expression receiverClass = new ClassExpression(call.getType()); |
| makeCallSite( |
| receiverClass, CONSTRUCTOR, |
| arguments,false, false, false, |
| false); |
| } |
| |
| private static String makeFieldClassName(ClassNode type) { |
| String internalName = BytecodeHelper.getClassInternalName(type); |
| StringBuffer ret = new StringBuffer(internalName.length()); |
| for (int i = 0; i < internalName.length(); i++) { |
| char c = internalName.charAt(i); |
| if (c == '/') { |
| ret.append('$'); |
| } else if (c == ';') { |
| //append nothing -> delete ';' |
| } else { |
| ret.append(c); |
| } |
| } |
| return ret.toString(); |
| } |
| |
| private static String getStaticFieldName(ClassNode type) { |
| ClassNode componentType = type; |
| String prefix = ""; |
| for (; componentType.isArray(); componentType = componentType.getComponentType()) { |
| prefix += "$"; |
| } |
| if (prefix.length() != 0) prefix = "array" + prefix; |
| String name = prefix + "$class$" + makeFieldClassName(componentType); |
| return name; |
| } |
| |
| private void visitAttributeOrProperty(PropertyExpression expression, MethodCallerMultiAdapter adapter) { |
| Expression objectExpression = expression.getObjectExpression(); |
| if (isThisOrSuper(objectExpression)) { |
| // let's use the field expression if it's available |
| String name = expression.getPropertyAsString(); |
| if (name != null) { |
| FieldNode field = null; |
| if (isSuperExpression(objectExpression)) { |
| field = classNode.getSuperClass().getDeclaredField(name); |
| } else { |
| if(isNotExplicitThisInClosure(expression.isImplicitThis())) { |
| field = classNode.getDeclaredField(name); |
| } |
| } |
| if (field != null) { |
| visitFieldExpression(new FieldExpression(field)); |
| return; |
| } |
| } |
| if (isSuperExpression(objectExpression)) { |
| String prefix; |
| if (leftHandExpression) { |
| prefix = "set"; |
| } else { |
| prefix = "get"; |
| } |
| String propName = prefix + MetaClassHelper.capitalize(name); |
| visitMethodCallExpression(new MethodCallExpression(objectExpression, propName, MethodCallExpression.NO_ARGUMENTS)); |
| return; |
| } |
| } |
| |
| final String methodName = expression.getPropertyAsString(); |
| if (adapter == getProperty && !expression.isSpreadSafe() && methodName != null) { |
| makeGetPropertySite(objectExpression, methodName, expression.isSafe(), expression.isImplicitThis()); |
| } |
| else { |
| // arguments already on stack if any |
| if (adapter == getGroovyObjectProperty && !expression.isSpreadSafe() && methodName != null) { |
| makeGroovyObjectGetPropertySite(objectExpression, methodName, expression.isSafe(), expression.isImplicitThis()); |
| } |
| else { |
| makeCall( |
| objectExpression, // receiver |
| new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName |
| MethodCallExpression.NO_ARGUMENTS, |
| adapter, |
| expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis() |
| ); |
| } |
| } |
| } |
| |
| private boolean isStaticContext() { |
| if (compileStack!=null && compileStack.getScope()!=null) { |
| return compileStack.getScope().isInStaticContext(); |
| } |
| if (!isInClosure()) return false; |
| if (constructorNode != null) return false; |
| return classNode.isStaticClass() || methodNode.isStatic(); |
| } |
| |
| public void visitPropertyExpression(PropertyExpression expression) { |
| Expression objectExpression = expression.getObjectExpression(); |
| MethodCallerMultiAdapter adapter; |
| if (leftHandExpression) { |
| adapter = setProperty; |
| if (isGroovyObject(objectExpression)) adapter = setGroovyObjectProperty; |
| if (isStaticContext() && isThisOrSuper(objectExpression)) adapter = setProperty; |
| } else { |
| adapter = getProperty; |
| if (isGroovyObject(objectExpression)) adapter = getGroovyObjectProperty; |
| if (isStaticContext() && isThisOrSuper(objectExpression)) adapter = getProperty; |
| } |
| visitAttributeOrProperty(expression, adapter); |
| } |
| |
| public void visitAttributeExpression(AttributeExpression expression) { |
| Expression objectExpression = expression.getObjectExpression(); |
| MethodCallerMultiAdapter adapter; |
| if (leftHandExpression) { |
| adapter = setField; |
| if (isGroovyObject(objectExpression)) adapter = setGroovyObjectField; |
| if (usesSuper(expression)) adapter = setFieldOnSuper; |
| } else { |
| adapter = getField; |
| if (isGroovyObject(objectExpression)) adapter = getGroovyObjectField; |
| if (usesSuper(expression)) adapter = getFieldOnSuper; |
| } |
| visitAttributeOrProperty(expression, adapter); |
| } |
| |
| protected boolean isGroovyObject(Expression objectExpression) { |
| return isThisExpression(objectExpression) || objectExpression.getType().isDerivedFromGroovyObject() && !(objectExpression instanceof ClassExpression); |
| } |
| |
| public void visitFieldExpression(FieldExpression expression) { |
| FieldNode field = expression.getField(); |
| |
| if (field.isStatic()) { |
| if (leftHandExpression) { |
| storeStaticField(expression); |
| } else { |
| loadStaticField(expression); |
| } |
| } else { |
| if (leftHandExpression) { |
| storeThisInstanceField(expression); |
| } else { |
| loadInstanceField(expression); |
| } |
| } |
| } |
| |
| /** |
| * @param fldExp |
| */ |
| public void loadStaticField(FieldExpression fldExp) { |
| FieldNode field = fldExp.getField(); |
| boolean holder = field.isHolder() && !isInClosureConstructor(); |
| ClassNode type = field.getType(); |
| |
| String ownerName = (field.getOwner().equals(classNode)) |
| ? internalClassName |
| : BytecodeHelper.getClassInternalName(field.getOwner()); |
| if (holder) { |
| mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;"); |
| } else { |
| mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| if (ClassHelper.isPrimitiveType(type)) { |
| helper.box(type); |
| } else { |
| } |
| } |
| } |
| |
| /** |
| * RHS instance field. should move most of the code in the BytecodeHelper |
| * |
| * @param fldExp |
| */ |
| public void loadInstanceField(FieldExpression fldExp) { |
| FieldNode field = fldExp.getField(); |
| boolean holder = field.isHolder() && !isInClosureConstructor(); |
| ClassNode type = field.getType(); |
| String ownerName = (field.getOwner().equals(classNode)) |
| ? internalClassName |
| : helper.getClassInternalName(field.getOwner()); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| |
| if (holder) { |
| mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;"); |
| } else { |
| if (ClassHelper.isPrimitiveType(type)) { |
| helper.box(type); |
| } else { |
| } |
| } |
| } |
| |
| public void storeThisInstanceField(FieldExpression expression) { |
| FieldNode field = expression.getField(); |
| |
| boolean holder = field.isHolder() && !isInClosureConstructor(); |
| ClassNode type = field.getType(); |
| |
| String ownerName = (field.getOwner().equals(classNode)) ? |
| internalClassName : BytecodeHelper.getClassInternalName(field.getOwner()); |
| if (holder) { |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| mv.visitInsn(SWAP); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V"); |
| } else { |
| if (isInClosureConstructor()) { |
| helper.doCast(type); |
| } else if (!ClassHelper.isPrimitiveType(type)) { |
| doConvertAndCast(type); |
| } |
| mv.visitVarInsn(ALOAD, 0); |
| //helper.swapObjectWith(type); |
| mv.visitInsn(SWAP); |
| helper.unbox(type); |
| helper.putField(field, ownerName); |
| } |
| } |
| |
| |
| public void storeStaticField(FieldExpression expression) { |
| FieldNode field = expression.getField(); |
| |
| boolean holder = field.isHolder() && !isInClosureConstructor(); |
| |
| ClassNode type = field.getType(); |
| |
| String ownerName = (field.getOwner().equals(classNode)) |
| ? internalClassName |
| : helper.getClassInternalName(field.getOwner()); |
| if (holder) { |
| mv.visitFieldInsn(GETSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| mv.visitInsn(SWAP); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V"); |
| } else { |
| helper.doCast(type); |
| mv.visitFieldInsn(PUTSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type)); |
| } |
| } |
| |
| protected void visitOuterFieldExpression(FieldExpression expression, ClassNode outerClassNode, int steps, boolean first) { |
| FieldNode field = expression.getField(); |
| boolean isStatic = field.isStatic(); |
| |
| int tempIdx = compileStack.defineTemporaryVariable(field, leftHandExpression && first); |
| |
| if (steps > 1 || !isStatic) { |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn( |
| GETFIELD, |
| internalClassName, |
| "owner", |
| BytecodeHelper.getTypeDescription(outerClassNode)); |
| } |
| |
| if (steps == 1) { |
| int opcode = (leftHandExpression) ? ((isStatic) ? PUTSTATIC : PUTFIELD) : ((isStatic) ? GETSTATIC : GETFIELD); |
| String ownerName = BytecodeHelper.getClassInternalName(outerClassNode); |
| |
| if (leftHandExpression) { |
| mv.visitVarInsn(ALOAD, tempIdx); |
| boolean holder = field.isHolder() && !isInClosureConstructor(); |
| if (!holder) { |
| doConvertAndCast(field.getType()); |
| } |
| } |
| mv.visitFieldInsn(opcode, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(field.getType())); |
| if (!leftHandExpression) { |
| if (ClassHelper.isPrimitiveType(field.getType())) { |
| helper.box(field.getType()); |
| } |
| } |
| } else { |
| visitOuterFieldExpression(expression, outerClassNode.getOuterClass(), steps - 1, false); |
| } |
| } |
| |
| |
| /** |
| * Visits a bare (unqualified) variable expression. |
| */ |
| |
| public void |
| visitVariableExpression(VariableExpression expression) { |
| |
| String variableName = expression.getName(); |
| |
| //----------------------------------------------------------------------- |
| // SPECIAL CASES |
| |
| // |
| // "this" for static methods is the Class instance |
| |
| ClassNode classNode = this.classNode; |
| if (isInClosure()) classNode = getOutermostClass(); |
| |
| if (variableName.equals("this")) { |
| if (isStaticMethod() || (!implicitThis && isStaticContext())) { |
| visitClassExpression(new ClassExpression(classNode)); |
| } else { |
| loadThis(); |
| } |
| return; |
| } |
| |
| // |
| // "super" also requires special handling |
| |
| if (variableName.equals("super")) { |
| if (isStaticMethod()) { |
| visitClassExpression(new ClassExpression(classNode.getSuperClass())); |
| } else { |
| loadThis(); |
| } |
| return; // <<< FLOW CONTROL <<<<<<<<< |
| } |
| |
| Variable variable = compileStack.getVariable(variableName, false); |
| |
| VariableScope scope = compileStack.getScope(); |
| if (variable == null) { |
| processClassVariable(variableName); |
| } else { |
| processStackVariable(variable); |
| } |
| } |
| |
| private void loadThis() { |
| mv.visitVarInsn(ALOAD, 0); |
| if (!implicitThis && isInClosure()) { |
| mv.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "groovy/lang/Closure", |
| "getThisObject", |
| "()Ljava/lang/Object;" |
| ); |
| } |
| } |
| |
| protected void processStackVariable(Variable variable) { |
| if (leftHandExpression) { |
| helper.storeVar(variable); |
| } else { |
| helper.loadVar(variable); |
| } |
| if (ASM_DEBUG) { |
| helper.mark("var: " + variable.getName()); |
| } |
| } |
| |
| protected void processClassVariable(String name) { |
| if (passingClosureParams && isInScriptBody()) { |
| // let's create a ScriptReference to pass into the closure |
| mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/ScriptReference"); |
| mv.visitInsn(DUP); |
| |
| loadThisOrOwner(); |
| mv.visitLdcInsn(name); |
| |
| mv.visitMethodInsn( |
| INVOKESPECIAL, |
| "org/codehaus/groovy/runtime/ScriptReference", |
| "<init>", |
| "(Lgroovy/lang/Script;Ljava/lang/String;)V"); |
| } else { |
| PropertyExpression pexp = new PropertyExpression(VariableExpression.THIS_EXPRESSION, name); |
| pexp.setImplicitThis(true); |
| visitPropertyExpression(pexp); |
| } |
| } |
| |
| |
| protected void processFieldAccess(String name, FieldNode field, int steps) { |
| FieldExpression expression = new FieldExpression(field); |
| |
| if (steps == 0) { |
| visitFieldExpression(expression); |
| } else { |
| visitOuterFieldExpression(expression, classNode.getOuterClass(), steps, true); |
| } |
| } |
| |
| |
| /** |
| * @return true if we are in a script body, where all variables declared are no longer |
| * local variables but are properties |
| */ |
| protected boolean isInScriptBody() { |
| if (classNode.isScriptBody()) { |
| return true; |
| } else { |
| return classNode.isScript() && methodNode != null && methodNode.getName().equals("run"); |
| } |
| } |
| |
| /** |
| * @return true if this expression will have left a value on the stack |
| * that must be popped |
| */ |
| protected boolean isPopRequired(Expression expression) { |
| if (expression instanceof MethodCallExpression) { |
| return expression.getType() != ClassHelper.VOID_TYPE; |
| } |
| if (expression instanceof DeclarationExpression) { |
| DeclarationExpression de = (DeclarationExpression) expression; |
| return de.getLeftExpression() instanceof TupleExpression; |
| } |
| if (expression instanceof BinaryExpression) { |
| BinaryExpression binExp = (BinaryExpression) expression; |
| switch (binExp.getOperation().getType()) { // br todo should leave a copy of the value on the stack for all the assignment. |
| // case Types.EQUAL : // br a copy of the right value is left on the stack (see evaluateEqual()) so a pop is required for a standalone assignment |
| // case Types.PLUS_EQUAL : // this and the following are related to evaluateBinaryExpressionWithAssignment() |
| // case Types.MINUS_EQUAL : |
| // case Types.MULTIPLY_EQUAL : |
| // case Types.DIVIDE_EQUAL : |
| // case Types.INTDIV_EQUAL : |
| // case Types.MOD_EQUAL : |
| // return false; |
| } |
| } |
| if (expression instanceof ConstructorCallExpression) { |
| ConstructorCallExpression cce = (ConstructorCallExpression) expression; |
| return !cce.isSpecialCall(); |
| } |
| return true; |
| } |
| |
| protected void createInterfaceSyntheticStaticFields() { |
| if (referencedClasses.isEmpty()) return; |
| |
| addInnerClass(interfaceClassLoadingClass); |
| |
| for (String staticFieldName : referencedClasses.keySet()) { // generate a field node |
| interfaceClassLoadingClass.addField(staticFieldName, ACC_STATIC + ACC_SYNTHETIC, ClassHelper.CLASS_Type, new ClassExpression(referencedClasses.get(staticFieldName))); |
| } |
| } |
| |
| protected void createSyntheticStaticFields() { |
| for (String staticFieldName : referencedClasses.keySet()) { |
| // generate a field node |
| FieldNode fn = classNode.getDeclaredField(staticFieldName); |
| if (fn != null) { |
| boolean type = fn.getType() == ClassHelper.CLASS_Type; |
| boolean modifiers = fn.getModifiers() == ACC_STATIC + ACC_SYNTHETIC; |
| if (!type || !modifiers) { |
| String text = ""; |
| if (!type) text = " with wrong type: " + fn.getType() + " (java.lang.Class needed)"; |
| if (!modifiers) |
| text = " with wrong modifiers: " + fn.getModifiers() + " (" + (ACC_STATIC + ACC_SYNTHETIC) + " needed)"; |
| throwException( |
| "tried to set a static syntethic field " + staticFieldName + " in " + classNode.getName() + |
| " for class resolving, but found alreeady a node of that" + |
| " name " + text); |
| } |
| } else { |
| cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, staticFieldName, "Ljava/lang/Class;", null, null); |
| } |
| |
| mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$get$" + staticFieldName,"()Ljava/lang/Class;",null, null); |
| mv.visitCode(); |
| mv.visitFieldInsn(GETSTATIC,internalClassName,staticFieldName,"Ljava/lang/Class;"); |
| mv.visitInsn(DUP); |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFNONNULL,l0); |
| mv.visitInsn(POP); |
| mv.visitLdcInsn(BytecodeHelper.getClassLoadingTypeDescription(referencedClasses.get(staticFieldName))); |
| mv.visitMethodInsn(INVOKESTATIC,internalClassName,"class$","(Ljava/lang/String;)Ljava/lang/Class;"); |
| mv.visitInsn(DUP); |
| mv.visitFieldInsn(PUTSTATIC,internalClassName,staticFieldName,"Ljava/lang/Class;"); |
| mv.visitLabel(l0); |
| mv.visitInsn(ARETURN); |
| mv.visitMaxs(0,0); |
| mv.visitEnd(); |
| } |
| |
| mv = |
| cv.visitMethod( |
| ACC_STATIC + ACC_SYNTHETIC, |
| "class$", |
| "(Ljava/lang/String;)Ljava/lang/Class;", |
| null, |
| null); |
| Label l0 = new Label(); |
| mv.visitLabel(l0); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); |
| Label l1 = new Label(); |
| mv.visitLabel(l1); |
| mv.visitInsn(ARETURN); |
| Label l2 = new Label(); |
| mv.visitLabel(l2); |
| mv.visitVarInsn(ASTORE, 1); |
| mv.visitTypeInsn(NEW, "java/lang/NoClassDefFoundError"); |
| mv.visitInsn(DUP); |
| mv.visitVarInsn(ALOAD, 1); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassNotFoundException", "getMessage", "()Ljava/lang/String;"); |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V"); |
| mv.visitInsn(ATHROW); |
| mv.visitTryCatchBlock(l0, l2, l2, "java/lang/ClassNotFoundException"); // br using l2 as the 2nd param seems create the right table entry |
| mv.visitMaxs(3, 2); |
| } |
| |
| /** |
| * load class object on stack |
| */ |
| public void visitClassExpression(ClassExpression expression) { |
| ClassNode type = expression.getType(); |
| |
| if (ClassHelper.isPrimitiveType(type)) { |
| ClassNode objectType = ClassHelper.getWrapper(type); |
| mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(objectType), "TYPE", "Ljava/lang/Class;"); |
| } else { |
| String staticFieldName = getStaticFieldName(type); |
| referencedClasses.put(staticFieldName,type); |
| |
| String internalClassName = this.internalClassName; |
| if (classNode.isInterface()) { |
| internalClassName = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass); |
| mv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;"); |
| } else { |
| mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$get$" + staticFieldName, "()Ljava/lang/Class;"); |
| } |
| } |
| } |
| |
| public void visitRangeExpression(RangeExpression expression) { |
| expression.getFrom().visit(this); |
| expression.getTo().visit(this); |
| |
| helper.pushConstant(expression.isInclusive()); |
| |
| createRangeMethod.call(mv); |
| } |
| |
| public void visitMapEntryExpression(MapEntryExpression expression) { |
| throw new GroovyBugError("MapEntryExpression should not be visited here"); |
| } |
| |
| public void visitMapExpression(MapExpression expression) { |
| List entries = expression.getMapEntryExpressions(); |
| int size = entries.size(); |
| helper.pushConstant(size * 2); |
| |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); |
| |
| int i = 0; |
| for (Iterator iter = entries.iterator(); iter.hasNext();) { |
| Object object = iter.next(); |
| MapEntryExpression entry = (MapEntryExpression) object; |
| |
| mv.visitInsn(DUP); |
| helper.pushConstant(i++); |
| visitAndAutoboxBoolean(entry.getKeyExpression()); |
| mv.visitInsn(AASTORE); |
| |
| mv.visitInsn(DUP); |
| helper.pushConstant(i++); |
| visitAndAutoboxBoolean(entry.getValueExpression()); |
| mv.visitInsn(AASTORE); |
| } |
| createMapMethod.call(mv); |
| } |
| |
| public void visitArgumentlistExpression(ArgumentListExpression ale) { |
| if (containsSpreadExpression(ale)) { |
| despreadList(ale.getExpressions(), true); |
| } else { |
| visitTupleExpression(ale, true); |
| } |
| } |
| |
| public void visitTupleExpression(TupleExpression expression) { |
| visitTupleExpression(expression, false); |
| } |
| |
| private void visitTupleExpression(TupleExpression expression, boolean useWrapper) { |
| int size = expression.getExpressions().size(); |
| |
| helper.pushConstant(size); |
| |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); |
| |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(i); |
| Expression argument = expression.getExpression(i); |
| visitAndAutoboxBoolean(argument); |
| if (useWrapper && argument instanceof CastExpression) loadWrapper(argument); |
| |
| mv.visitInsn(AASTORE); |
| } |
| } |
| |
| private void loadWrapper(Expression argument) { |
| ClassNode goalClass = argument.getType(); |
| visitClassExpression(new ClassExpression(goalClass)); |
| if (goalClass.isDerivedFromGroovyObject()) { |
| createGroovyObjectWrapperMethod.call(mv); |
| } else { |
| createPojoWrapperMethod.call(mv); |
| } |
| } |
| |
| public void visitArrayExpression(ArrayExpression expression) { |
| ClassNode elementType = expression.getElementType(); |
| String arrayTypeName = BytecodeHelper.getClassInternalName(elementType); |
| List sizeExpression = expression.getSizeExpression(); |
| |
| int size = 0; |
| int dimensions = 0; |
| if (sizeExpression != null) { |
| for (Iterator iter = sizeExpression.iterator(); iter.hasNext();) { |
| Expression element = (Expression) iter.next(); |
| if (element == ConstantExpression.EMTPY_EXPRESSION) break; |
| dimensions++; |
| // let's convert to an int |
| visitAndAutoboxBoolean(element); |
| helper.unbox(int.class); |
| } |
| } else { |
| size = expression.getExpressions().size(); |
| helper.pushConstant(size); |
| } |
| |
| int storeIns = AASTORE; |
| if (sizeExpression != null) { |
| arrayTypeName = BytecodeHelper.getTypeDescription(expression.getType()); |
| mv.visitMultiANewArrayInsn(arrayTypeName, dimensions); |
| } else if (ClassHelper.isPrimitiveType(elementType)) { |
| int primType = 0; |
| if (elementType == ClassHelper.boolean_TYPE) { |
| primType = T_BOOLEAN; |
| storeIns = BASTORE; |
| } else if (elementType == ClassHelper.char_TYPE) { |
| primType = T_CHAR; |
| storeIns = CASTORE; |
| } else if (elementType == ClassHelper.float_TYPE) { |
| primType = T_FLOAT; |
| storeIns = FASTORE; |
| } else if (elementType == ClassHelper.double_TYPE) { |
| primType = T_DOUBLE; |
| storeIns = DASTORE; |
| } else if (elementType == ClassHelper.byte_TYPE) { |
| primType = T_BYTE; |
| storeIns = BASTORE; |
| } else if (elementType == ClassHelper.short_TYPE) { |
| primType = T_SHORT; |
| storeIns = SASTORE; |
| } else if (elementType == ClassHelper.int_TYPE) { |
| primType = T_INT; |
| storeIns = IASTORE; |
| } else if (elementType == ClassHelper.long_TYPE) { |
| primType = T_LONG; |
| storeIns = LASTORE; |
| } |
| mv.visitIntInsn(NEWARRAY, primType); |
| } else { |
| mv.visitTypeInsn(ANEWARRAY, arrayTypeName); |
| } |
| |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(i); |
| Expression elementExpression = expression.getExpression(i); |
| if (elementExpression == null) { |
| ConstantExpression.NULL.visit(this); |
| } else { |
| if (!elementType.equals(elementExpression.getType())) { |
| visitCastExpression(new CastExpression(elementType, elementExpression, true)); |
| } else { |
| visitAndAutoboxBoolean(elementExpression); |
| } |
| } |
| mv.visitInsn(storeIns); |
| } |
| |
| if (sizeExpression == null && ClassHelper.isPrimitiveType(elementType)) { |
| int par = compileStack.defineTemporaryVariable("par", true); |
| mv.visitVarInsn(ALOAD, par); |
| } |
| } |
| |
| public void visitClosureListExpression(ClosureListExpression expression) { |
| compileStack.pushVariableScope(expression.getVariableScope()); |
| |
| List expressions = expression.getExpressions(); |
| final int size = expressions.size(); |
| // init declarations |
| LinkedList declarations = new LinkedList(); |
| for (int i = 0; i < size; i++) { |
| Object expr = expressions.get(i); |
| if (expr instanceof DeclarationExpression) { |
| declarations.add(expr); |
| DeclarationExpression de = (DeclarationExpression) expr; |
| BinaryExpression be = new BinaryExpression( |
| de.getLeftExpression(), |
| de.getOperation(), |
| de.getRightExpression()); |
| expressions.set(i, be); |
| de.setRightExpression(ConstantExpression.NULL); |
| visitDeclarationExpression(de); |
| } |
| } |
| |
| LinkedList instructions = new LinkedList(); |
| BytecodeSequence seq = new BytecodeSequence(instructions); |
| BlockStatement bs = new BlockStatement(); |
| bs.addStatement(seq); |
| Parameter closureIndex = new Parameter(ClassHelper.int_TYPE, "__closureIndex"); |
| ClosureExpression ce = new ClosureExpression(new Parameter[]{closureIndex}, bs); |
| ce.setVariableScope(expression.getVariableScope()); |
| |
| // to keep stack hight put a null on stack |
| instructions.add(ConstantExpression.NULL); |
| |
| // init table |
| final Label dflt = new Label(); |
| final Label tableEnd = new Label(); |
| final Label[] labels = new Label[size]; |
| instructions.add(new BytecodeInstruction() { |
| public void visit(MethodVisitor mv) { |
| mv.visitVarInsn(ILOAD, 1); |
| mv.visitTableSwitchInsn(0, size - 1, dflt, labels); |
| } |
| }); |
| |
| // visit cases |
| for (int i = 0; i < size; i++) { |
| final Label label = new Label(); |
| Object expr = expressions.get(i); |
| final boolean isStatement = expr instanceof Statement; |
| labels[i] = label; |
| instructions.add(new BytecodeInstruction() { |
| public void visit(MethodVisitor mv) { |
| mv.visitLabel(label); |
| // expressions will leave a value on stack, statements not |
| // so expressions need to pop the alibi null |
| if (!isStatement) mv.visitInsn(POP); |
| } |
| }); |
| instructions.add(expr); |
| instructions.add(new BytecodeInstruction() { |
| public void visit(MethodVisitor mv) { |
| mv.visitJumpInsn(GOTO, tableEnd); |
| } |
| }); |
| } |
| |
| // default case |
| { |
| instructions.add(new BytecodeInstruction() { |
| public void visit(MethodVisitor mv) { |
| mv.visitLabel(dflt); |
| } |
| }); |
| ConstantExpression text = new ConstantExpression("invalid index for closure"); |
| ConstructorCallExpression cce = new ConstructorCallExpression(ClassHelper.make(IllegalArgumentException.class), text); |
| ThrowStatement ts = new ThrowStatement(cce); |
| instructions.add(ts); |
| } |
| |
| // return |
| instructions.add(new BytecodeInstruction() { |
| public void visit(MethodVisitor mv) { |
| mv.visitLabel(tableEnd); |
| mv.visitInsn(ARETURN); |
| } |
| }); |
| |
| // load main Closure |
| visitClosureExpression(ce); |
| |
| // we need later an array to store the curried |
| // closures, so we create it here and ave it |
| // in a temporary variable |
| helper.pushConstant(size); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); |
| int listArrayVar = compileStack.defineTemporaryVariable("_listOfClosures", true); |
| |
| // add curried versions |
| for (int i = 0; i < size; i++) { |
| // stack: closure |
| |
| // we need to create a curried closure version |
| // so we store the type on stack |
| mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/CurriedClosure"); |
| // stack: closure, type |
| // for a constructor call we need the type two times |
| |
| // and the closure after them |
| mv.visitInsn(DUP2); |
| mv.visitInsn(SWAP); |
| // stack: closure,type,type,closure |
| |
| // so we can create the curried closure |
| helper.pushConstant(i); |
| mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/CurriedClosure", "<init>", "(Lgroovy/lang/Closure;I)V"); |
| // stack: closure,curriedClosure |
| |
| // we need to save the result |
| mv.visitVarInsn(ALOAD, listArrayVar); |
| mv.visitInsn(SWAP); |
| helper.pushConstant(i); |
| mv.visitInsn(SWAP); |
| mv.visitInsn(AASTORE); |
| // stack: closure |
| } |
| |
| // we don't need the closure any longer, so remove it |
| mv.visitInsn(POP); |
| // we load the array and create a list from it |
| mv.visitVarInsn(ALOAD, listArrayVar); |
| createListMethod.call(mv); |
| |
| // remove the temporary variable to keep the |
| // stack clean |
| compileStack.removeVar(listArrayVar); |
| compileStack.pop(); |
| } |
| |
| public void visitBytecodeSequence(BytecodeSequence bytecodeSequence) { |
| List instructions = bytecodeSequence.getInstructions(); |
| for (Iterator iterator = instructions.iterator(); iterator.hasNext();) { |
| Object part = iterator.next(); |
| if (part == EmptyExpression.INSTANCE) { |
| mv.visitInsn(ACONST_NULL); |
| } else if (part instanceof Expression) { |
| visitAndAutoboxBoolean((Expression) part); |
| } else if (part instanceof Statement) { |
| Statement stm = (Statement) part; |
| stm.visit(this); |
| mv.visitInsn(ACONST_NULL); |
| } else { |
| BytecodeInstruction runner = (BytecodeInstruction) part; |
| runner.visit(mv); |
| } |
| } |
| } |
| |
| public void visitListExpression(ListExpression expression) { |
| onLineNumber(expression,"ListExpression" ); |
| int size = expression.getExpressions().size(); |
| boolean containsSpreadExpression = containsSpreadExpression(expression); |
| if (!containsSpreadExpression) { |
| helper.pushConstant(size); |
| |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); |
| |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(i); |
| visitAndAutoboxBoolean(expression.getExpression(i)); |
| mv.visitInsn(AASTORE); |
| } |
| } else { |
| despreadList(expression.getExpressions(), false); |
| } |
| createListMethod.call(mv); |
| } |
| |
| public void visitGStringExpression(GStringExpression expression) { |
| |
| mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/GStringImpl"); |
| mv.visitInsn(DUP); |
| |
| int size = expression.getValues().size(); |
| helper.pushConstant(size); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); |
| |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(i); |
| visitAndAutoboxBoolean(expression.getValue(i)); |
| mv.visitInsn(AASTORE); |
| } |
| |
| List strings = expression.getStrings(); |
| size = strings.size(); |
| helper.pushConstant(size); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); |
| |
| for (int i = 0; i < size; i++) { |
| mv.visitInsn(DUP); |
| helper.pushConstant(i); |
| mv.visitLdcInsn(((ConstantExpression) strings.get(i)).getValue()); |
| mv.visitInsn(AASTORE); |
| } |
| |
| mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/GStringImpl", "<init>", "([Ljava/lang/Object;[Ljava/lang/String;)V"); |
| } |
| |
| /** |
| * Note: ignore it. Annotation generation needs the current visitor. |
| */ |
| public void visitAnnotations(AnnotatedNode node) { |
| } |
| |
| private void visitAnnotations(AnnotatedNode targetNode, Object visitor) { |
| List annotations = targetNode.getAnnotations(); |
| if (annotations.isEmpty()) return; |
| |
| Iterator it = annotations.iterator(); |
| while (it.hasNext()) { |
| AnnotationNode an = (AnnotationNode) it.next(); |
| // skip built-in properties |
| if (an.isBuiltIn()) continue; |
| if (an.hasSourceRetention()) continue; |
| |
| AnnotationVisitor av = getAnnotationVisitor(targetNode, an, visitor); |
| visitAnnotationAttributes(an, av); |
| av.visitEnd(); |
| } |
| } |
| |
| // TODO remove dup between this and visitAnnotations |
| private void visitParameterAnnotations(Parameter parameter, int paramNumber, MethodVisitor mv) { |
| List annotations = parameter.getAnnotations(); |
| if (annotations.isEmpty()) return; |
| |
| Iterator it = annotations.iterator(); |
| while (it.hasNext()) { |
| AnnotationNode an = (AnnotationNode) it.next(); |
| // skip built-in properties |
| if (an.isBuiltIn()) continue; |
| if (an.hasSourceRetention()) continue; |
| |
| final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode()); |
| AnnotationVisitor av = mv.visitParameterAnnotation(paramNumber, annotationDescriptor, an.hasRuntimeRetention()); |
| visitAnnotationAttributes(an, av); |
| av.visitEnd(); |
| } |
| } |
| |
| private AnnotationVisitor getAnnotationVisitor(AnnotatedNode targetNode, AnnotationNode an, Object visitor) { |
| final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode()); |
| if (targetNode instanceof MethodNode) { |
| return ((MethodVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); |
| } else if (targetNode instanceof FieldNode) { |
| return ((FieldVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); |
| } else if (targetNode instanceof ClassNode) { |
| return ((ClassVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); |
| } |
| |
| throwException("Cannot create an AnnotationVisitor. Please report Groovy bug"); |
| |
| return null; |
| } |
| |
| /** |
| * Generate the annotation attributes. |
| */ |
| private void visitAnnotationAttributes(AnnotationNode an, AnnotationVisitor av) { |
| Map constantAttrs = new HashMap(); |
| Map enumAttrs = new HashMap(); |
| Map atAttrs = new HashMap(); |
| Map arrayAttrs = new HashMap(); |
| |
| Iterator mIt = an.getMembers().keySet().iterator(); |
| while (mIt.hasNext()) { |
| String name = (String) mIt.next(); |
| Expression expr = an.getMember(name); |
| if (expr instanceof AnnotationConstantExpression) { |
| atAttrs.put(name, ((AnnotationConstantExpression) expr).getValue()); |
| } else if (expr instanceof ConstantExpression) { |
| constantAttrs.put(name, ((ConstantExpression) expr).getValue()); |
| } else if (expr instanceof ClassExpression) { |
| constantAttrs.put(name, |
| Type.getType(BytecodeHelper.getTypeDescription(expr.getType()))); |
| } else if (expr instanceof PropertyExpression) { |
| enumAttrs.put(name, expr); |
| } else if (expr instanceof ListExpression) { |
| arrayAttrs.put(name, expr); |
| } |
| } |
| |
| for (Iterator it = constantAttrs.entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| av.visit((String) entry.getKey(), entry.getValue()); |
| } |
| for (Iterator it = enumAttrs.entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| PropertyExpression propExp = (PropertyExpression) entry.getValue(); |
| av.visitEnum((String) entry.getKey(), |
| BytecodeHelper.getTypeDescription(propExp.getObjectExpression().getType()), |
| String.valueOf(((ConstantExpression) propExp.getProperty()).getValue())); |
| } |
| for (Iterator it = atAttrs.entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| AnnotationNode atNode = (AnnotationNode) entry.getValue(); |
| AnnotationVisitor av2 = av.visitAnnotation((String) entry.getKey(), |
| BytecodeHelper.getTypeDescription(atNode.getClassNode())); |
| visitAnnotationAttributes(atNode, av2); |
| av2.visitEnd(); |
| } |
| |
| visitArrayAttributes(an, arrayAttrs, av); |
| } |
| |
| private void visitArrayAttributes(AnnotationNode an, Map arrayAttr, AnnotationVisitor av) { |
| if (arrayAttr.isEmpty()) return; |
| |
| for (Iterator it = arrayAttr.entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| String attrName = (String) entry.getKey(); |
| ListExpression listExpr = (ListExpression) entry.getValue(); |
| AnnotationVisitor av2 = av.visitArray(attrName); |
| List values = listExpr.getExpressions(); |
| if (!values.isEmpty()) { |
| Expression expr = (Expression) values.get(0); |
| int arrayElementType = -1; |
| if (expr instanceof AnnotationConstantExpression) { |
| arrayElementType = 1; |
| } else if (expr instanceof ConstantExpression) { |
| arrayElementType = 2; |
| } else if (expr instanceof ClassExpression) { |
| arrayElementType = 3; |
| } else if (expr instanceof PropertyExpression) { |
| arrayElementType = 4; |
| } |
| for (Iterator exprIt = listExpr.getExpressions().iterator(); exprIt.hasNext();) { |
| switch (arrayElementType) { |
| case 1: |
| AnnotationNode atAttr = |
| (AnnotationNode) ((AnnotationConstantExpression) exprIt.next()).getValue(); |
| AnnotationVisitor av3 = av2.visitAnnotation(null, |
| BytecodeHelper.getTypeDescription(atAttr.getClassNode())); |
| visitAnnotationAttributes(atAttr, av3); |
| av3.visitEnd(); |
| break; |
| case 2: |
| av2.visit(null, ((ConstantExpression) exprIt.next()).getValue()); |
| break; |
| case 3: |
| av2.visit(null, Type.getType( |
| BytecodeHelper.getTypeDescription(((Expression) exprIt.next()).getType()))); |
| break; |
| case 4: |
| PropertyExpression propExpr = (PropertyExpression) exprIt.next(); |
| av2.visitEnum(null, |
| BytecodeHelper.getTypeDescription(propExpr.getObjectExpression().getType()), |
| String.valueOf(((ConstantExpression) propExpr.getProperty()).getValue())); |
| break; |
| } |
| } |
| |
| } |
| av2.visitEnd(); |
| } |
| } |
| |
| // Implementation methods |
| //------------------------------------------------------------------------- |
| protected boolean addInnerClass(ClassNode innerClass) { |
| innerClass.setModule(classNode.getModule()); |
| return innerClasses.add(innerClass); |
| } |
| |
| protected ClassNode createClosureClass(ClosureExpression expression) { |
| ClassNode outerClass = getOutermostClass(); |
| String name = outerClass.getName() + "$" |
| + context.getNextClosureInnerName(outerClass, classNode, methodNode); // add a more informative name |
| boolean staticMethodOrInStaticClass = isStaticMethod() || classNode.isStaticClass(); |
| |
| Parameter[] parameters = expression.getParameters(); |
| if (parameters == null) { |
| parameters = Parameter.EMPTY_ARRAY; |
| } else if (parameters.length == 0) { |
| // let's create a default 'it' parameter |
| Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL); |
| parameters = new Parameter[]{it}; |
| org.codehaus.groovy.ast.Variable ref = expression.getVariableScope().getDeclaredVariable("it"); |
| if (ref!=null) it.setClosureSharedVariable(ref.isClosureSharedVariable()); |
| } |
| |
| Parameter[] localVariableParams = getClosureSharedVariables(expression); |
| removeInitialValues(localVariableParams); |
| |
| InnerClassNode answer = new InnerClassNode(outerClass, name, 0, ClassHelper.CLOSURE_TYPE); // closures are local inners and not public |
| answer.setEnclosingMethod(this.methodNode); |
| answer.setSynthetic(true); |
| answer.setUsingGenerics(outerClass.isUsingGenerics()); |
| |
| if (staticMethodOrInStaticClass) { |
| answer.setStaticClass(true); |
| } |
| if (isInScriptBody()) { |
| answer.setScriptBody(true); |
| } |
| MethodNode method = |
| answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, parameters, ClassNode.EMPTY_ARRAY, expression.getCode()); |
| method.setSourcePosition(expression); |
| |
| VariableScope varScope = expression.getVariableScope(); |
| if (varScope == null) { |
| throw new RuntimeException( |
| "Must have a VariableScope by now! for expression: " + expression + " class: " + name); |
| } else { |
| method.setVariableScope(varScope.copy()); |
| } |
| if (parameters.length > 1 |
| || (parameters.length == 1 |
| && parameters[0].getType() != null |
| && parameters[0].getType() != ClassHelper.OBJECT_TYPE)) { |
| |
| // let's add a typesafe call method |
| MethodNode call = answer.addMethod( |
| "call", |
| ACC_PUBLIC, |
| ClassHelper.OBJECT_TYPE, |
| parameters, |
| ClassNode.EMPTY_ARRAY, |
| new ReturnStatement( |
| new MethodCallExpression( |
| VariableExpression.THIS_EXPRESSION, |
| "doCall", |
| new ArgumentListExpression(parameters)))); |
| call.setSourcePosition(expression); |
| } |
| |
| // let's make the constructor |
| BlockStatement block = new BlockStatement(); |
| // this block does not get a source position, because we don't |
| // want this synthetic constructor to show up in corbertura reports |
| VariableExpression outer = new VariableExpression("_outerInstance"); |
| outer.setSourcePosition(expression); |
| block.getVariableScope().putReferencedLocalVariable(outer); |
| VariableExpression thisObject = new VariableExpression("_thisObject"); |
| thisObject.setSourcePosition(expression); |
| block.getVariableScope().putReferencedLocalVariable(thisObject); |
| TupleExpression conArgs = new TupleExpression(outer, thisObject); |
| block.addStatement( |
| new ExpressionStatement( |
| new ConstructorCallExpression( |
| ClassNode.SUPER, |
| conArgs))); |
| |
| // let's assign all the parameter fields from the outer context |
| for (int i = 0; i < localVariableParams.length; i++) { |
| Parameter param = localVariableParams[i]; |
| String paramName = param.getName(); |
| Expression initialValue = null; |
| ClassNode type = param.getType(); |
| FieldNode paramField = null; |
| if (true) { |
| initialValue = new VariableExpression(paramName); |
| ClassNode realType = type; |
| type = ClassHelper.makeReference(); |
| param.setType(ClassHelper.makeReference()); |
| paramField = answer.addField(paramName, ACC_PRIVATE, type, initialValue); |
| paramField.setHolder(true); |
| String methodName = Verifier.capitalize(paramName); |
| |
| // let's add a getter & setter |
| Expression fieldExp = new FieldExpression(paramField); |
| answer.addMethod( |
| "get" + methodName, |
| ACC_PUBLIC, |
| realType, |
| Parameter.EMPTY_ARRAY, |
| ClassNode.EMPTY_ARRAY, |
| new ReturnStatement(fieldExp)); |
| |
| /* |
| answer.addMethod( |
| "set" + methodName, |
| ACC_PUBLIC, |
| "void", |
| new Parameter[] { new Parameter(realType, "__value") }, |
| new ExpressionStatement( |
| new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("__value")))); |
| */ |
| } |
| } |
| |
| Parameter[] params = new Parameter[2 + localVariableParams.length]; |
| params[0] = new Parameter(ClassHelper.OBJECT_TYPE, "_outerInstance"); |
| params[1] = new Parameter(ClassHelper.OBJECT_TYPE, "_thisObject"); |
| System.arraycopy(localVariableParams, 0, params, 2, localVariableParams.length); |
| |
| ASTNode sn = answer.addConstructor(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, block); |
| sn.setSourcePosition(expression); |
| return answer; |
| } |
| |
| /** |
| * this method is called for local variables shared between scopes. |
| * These variables must not have init values because these would |
| * then in later steps be used to create multiple versions of the |
| * same method, in this case the constructor. A closure should not |
| * have more than one constructor! |
| */ |
| private void removeInitialValues(Parameter[] params) { |
| for (int i = 0; i < params.length; i++) { |
| if (params[i].hasInitialExpression()) { |
| params[i] = new Parameter(params[i].getType(), params[i].getName()); |
| } |
| } |
| } |
| |
| protected Parameter[] getClosureSharedVariables(ClosureExpression ce) { |
| VariableScope scope = ce.getVariableScope(); |
| Parameter[] ret = new Parameter[scope.getReferencedLocalVariablesCount()]; |
| int index = 0; |
| for (Iterator iter = scope.getReferencedLocalVariablesIterator(); iter.hasNext();) { |
| org.codehaus.groovy.ast.Variable element = (org.codehaus.groovy.ast.Variable) iter.next(); |
| Parameter p = new Parameter(element.getType(), element.getName()); |
| ret[index] = p; |
| index++; |
| } |
| return ret; |
| } |
| |
| protected ClassNode getOutermostClass() { |
| if (outermostClass == null) { |
| outermostClass = classNode; |
| while (outermostClass instanceof InnerClassNode) { |
| outermostClass = outermostClass.getOuterClass(); |
| } |
| } |
| return outermostClass; |
| } |
| |
| protected void doConvertAndCast(ClassNode type) { |
| doConvertAndCast(type, false); |
| } |
| |
| protected void doConvertAndCast(ClassNode type, boolean coerce) { |
| if (type == ClassHelper.OBJECT_TYPE) return; |
| if (rightHandType == null || !rightHandType.isDerivedFrom(type) || !rightHandType.implementsInterface(type)) { |
| if (isValidTypeForCast(type)) { |
| visitClassExpression(new ClassExpression(type)); |
| if (coerce) { |
| asTypeMethod.call(mv); |
| } else { |
| castToTypeMethod.call(mv); |
| } |
| } |
| } |
| helper.doCast(type); |
| } |
| |
| protected void evaluateLogicalOrExpression(BinaryExpression expression) { |
| visitBooleanExpression(new BooleanExpression(expression.getLeftExpression())); |
| Label l0 = new Label(); |
| Label l2 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| mv.visitLabel(l2); |
| |
| visitConstantExpression(ConstantExpression.TRUE); |
| |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| mv.visitLabel(l0); |
| |
| visitBooleanExpression(new BooleanExpression(expression.getRightExpression())); |
| |
| mv.visitJumpInsn(IFNE, l2); |
| |
| visitConstantExpression(ConstantExpression.FALSE); |
| mv.visitLabel(l1); |
| } |
| |
| // todo: optimization: change to return primitive boolean. need to adjust the BinaryExpression and isComparisonExpression for |
| // consistancy. |
| protected void evaluateLogicalAndExpression(BinaryExpression expression) { |
| visitBooleanExpression(new BooleanExpression(expression.getLeftExpression())); |
| Label l0 = new Label(); |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| visitBooleanExpression(new BooleanExpression(expression.getRightExpression())); |
| |
| mv.visitJumpInsn(IFEQ, l0); |
| |
| visitConstantExpression(ConstantExpression.TRUE); |
| |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| mv.visitLabel(l0); |
| |
| visitConstantExpression(ConstantExpression.FALSE); |
| |
| mv.visitLabel(l1); |
| } |
| |
| protected void evaluateBinaryExpression(String method, BinaryExpression expression) { |
| // makeBinopCallSite(expression, method); |
| makeBinopCallSite(expression.getLeftExpression(), method, expression.getRightExpression()); |
| } |
| |
| protected void evaluateCompareTo(BinaryExpression expression) { |
| Expression leftExpression = expression.getLeftExpression(); |
| leftExpression.visit(this); |
| if (isComparisonExpression(leftExpression)) { |
| helper.boxBoolean(); |
| } |
| |
| // if the right hand side is a boolean expression, we need to autobox |
| Expression rightExpression = expression.getRightExpression(); |
| rightExpression.visit(this); |
| if (isComparisonExpression(rightExpression)) { |
| helper.boxBoolean(); |
| } |
| compareToMethod.call(mv); |
| } |
| |
| protected void evaluateBinaryExpressionWithAssignment(String method, BinaryExpression expression) { |
| Expression leftExpression = expression.getLeftExpression(); |
| if (leftExpression instanceof BinaryExpression) { |
| BinaryExpression leftBinExpr = (BinaryExpression) leftExpression; |
| if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) { |
| // let's replace this assignment to a subscript operator with a |
| // method call |
| // e.g. x[5] += 10 |
| // -> (x, [], 5), =, x[5] + 10 |
| // -> methodCall(x, "putAt", [5, methodCall(x[5], "plus", 10)]) |
| |
| prepareCallSite("putAt"); |
| // cs_put |
| prepareCallSite(method); |
| prepareCallSite("getAt"); |
| visitAndAutoboxBoolean(leftBinExpr.getLeftExpression()); |
| visitAndAutoboxBoolean(leftBinExpr.getRightExpression()); |
| // cs_put cs_method cs_get obj index |
| mv.visitInsn(DUP2_X2); |
| // cs_put obj index cs_method cs_get obj index |
| mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| // cs_put obj index cs_method obj[index] |
| visitAndAutoboxBoolean(expression.getRightExpression()); |
| // cs_put obj index cs_method obj[index] right |
| mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| // cs_put obj index (obj[index] + right) |
| final int resultVar = compileStack.defineTemporaryVariable("$result", true); |
| mv.visitVarInsn(ALOAD, resultVar ); |
| mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call","(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| mv.visitInsn(POP); //drop return value |
| |
| mv.visitVarInsn(ALOAD, resultVar ); |
| compileStack.removeVar(resultVar); |
| return; |
| } |
| } |
| |
| evaluateBinaryExpression(method, expression); |
| |
| // br to leave a copy of rvalue on the stack. see also isPopRequired() |
| mv.visitInsn(DUP); |
| doConvertAndCast(ClassHelper.getWrapper(leftExpression.getType())); |
| |
| leftHandExpression = true; |
| evaluateExpression(leftExpression); |
| leftHandExpression = false; |
| } |
| |
| private void evaluateBinaryExpression(MethodCaller compareMethod, BinaryExpression expression) { |
| Expression leftExp = expression.getLeftExpression(); |
| Expression rightExp = expression.getRightExpression(); |
| load(leftExp); |
| load(rightExp); |
| compareMethod.call(mv); |
| } |
| |
| protected void evaluateEqual(BinaryExpression expression, boolean defineVariable) { |
| Expression leftExpression = expression.getLeftExpression(); |
| if (leftExpression instanceof BinaryExpression) { |
| BinaryExpression leftBinExpr = (BinaryExpression) leftExpression; |
| if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) { |
| // let's replace this assignment to a subscript operator with a |
| // method call |
| // e.g. x[5] = 10 |
| // -> (x, [], 5), =, 10 |
| // -> methodCall(x, "putAt", [5, 10]) |
| |
| prepareCallSite("putAt"); |
| visitAndAutoboxBoolean(leftBinExpr.getLeftExpression()); |
| visitAndAutoboxBoolean(leftBinExpr.getRightExpression()); |
| visitAndAutoboxBoolean(expression.getRightExpression()); |
| |
| final int resultVar = compileStack.defineTemporaryVariable("$result", true); |
| mv.visitVarInsn(ALOAD, resultVar ); |
| mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call","(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| mv.visitInsn(POP); //drop return value |
| |
| mv.visitVarInsn(ALOAD, resultVar ); |
| compileStack.removeVar(resultVar); |
| return; |
| } |
| } |
| |
| // let's evaluate the RHS then hopefully the LHS will be a field |
| Expression rightExpression = expression.getRightExpression(); |
| if (!(leftExpression instanceof TupleExpression)) { |
| ClassNode type = null; |
| if(expression instanceof DeclarationExpression) { |
| type = leftExpression.getType(); |
| } else { |
| type = getLHSType(leftExpression); |
| } |
| assignmentCastAndVisit(type,rightExpression); |
| } else { |
| // multiple assignment here! |
| visitAndAutoboxBoolean(rightExpression); |
| } |
| |
| rightHandType = rightExpression.getType(); |
| leftHandExpression = true; |
| if (leftExpression instanceof TupleExpression) { |
| TupleExpression tuple = (TupleExpression) leftExpression; |
| int i = 0; |
| Expression lhsExpr = new BytecodeExpression() { |
| public void visit (MethodVisitor mv) { |
| // copy for method call |
| mv.visitInsn(SWAP); |
| mv.visitInsn(DUP_X1); |
| } |
| }; |
| for (Iterator iterator = tuple.getExpressions().iterator(); iterator.hasNext();) { |
| VariableExpression var = (VariableExpression) iterator.next(); |
| MethodCallExpression call = new MethodCallExpression( |
| lhsExpr, "getAt", |
| new ArgumentListExpression(new ConstantExpression(Integer.valueOf(i)))); |
| ClassNode type = getLHSType(var); |
| assignmentCastAndVisit(type,call); |
| i++; |
| if (defineVariable) { |
| compileStack.defineVariable(var, true); |
| } else { |
| visitVariableExpression(var); |
| } |
| } |
| } else if (defineVariable) { |
| VariableExpression var = (VariableExpression) leftExpression; |
| compileStack.defineVariable(var, true); |
| } else { |
| mv.visitInsn(DUP); // to leave a copy of the rightexpression value on the stack after the assignment. |
| leftExpression.visit(this); |
| } |
| rightHandType = null; |
| leftHandExpression = false; |
| } |
| |
| private void assignmentCastAndVisit(ClassNode type, Expression rightExpression) { |
| // let's not cast for primitive types as we handle these in field setting etc |
| if (ClassHelper.isPrimitiveType(type)) { |
| visitAndAutoboxBoolean(rightExpression); |
| } else if (!rightExpression.getType().isDerivedFrom(type)) { |
| visitCastExpression(new CastExpression(type, rightExpression)); |
| } else { |
| visitAndAutoboxBoolean(rightExpression); |
| } |
| } |
| |
| /** |
| * Deduces the type name required for some casting |
| * |
| * @return the type of the given (LHS) expression or null if it is java.lang.Object or it cannot be deduced |
| */ |
| protected ClassNode getLHSType(Expression leftExpression) { |
| if (leftExpression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) leftExpression; |
| ClassNode type = varExp.getType(); |
| if (isValidTypeForCast(type)) { |
| return type; |
| } |
| String variableName = varExp.getName(); |
| Variable variable = compileStack.getVariable(variableName, false); |
| if (variable != null) { |
| if (variable.isHolder()) { |
| return type; |
| } |
| if (variable.isProperty()) return variable.getType(); |
| type = variable.getType(); |
| if (isValidTypeForCast(type)) { |
| return type; |
| } |
| } else { |
| FieldNode field = classNode.getDeclaredField(variableName); |
| if (field == null) { |
| field = classNode.getOuterField(variableName); |
| } |
| if (field != null) { |
| type = field.getType(); |
| if (!field.isHolder() && isValidTypeForCast(type)) { |
| return type; |
| } |
| } |
| } |
| } else if (leftExpression instanceof FieldExpression) { |
| FieldExpression fieldExp = (FieldExpression) leftExpression; |
| ClassNode type = fieldExp.getType(); |
| if (isValidTypeForCast(type)) { |
| return type; |
| } |
| } |
| return leftExpression.getType(); |
| } |
| |
| protected boolean isValidTypeForCast(ClassNode type) { |
| return type != ClassHelper.DYNAMIC_TYPE && |
| type != ClassHelper.REFERENCE_TYPE; |
| } |
| |
| public void visitBytecodeExpression(BytecodeExpression cle) { |
| cle.visit(mv); |
| } |
| |
| protected void visitAndAutoboxBoolean(Expression expression) { |
| expression.visit(this); |
| |
| if (isComparisonExpression(expression)) { |
| helper.boxBoolean(); // convert boolean to Boolean |
| } |
| } |
| |
| private void execMethodAndStoreForSubscriptOperator(String method, Expression expression) { |
| // execute method |
| makeCallSite( |
| expression, |
| method, |
| MethodCallExpression.NO_ARGUMENTS, |
| false, false, false, false); |
| |
| // we need special code for arrays to store the result |
| if (expression instanceof BinaryExpression) { |
| BinaryExpression be = (BinaryExpression) expression; |
| if (be.getOperation().getType()==Types.LEFT_SQUARE_BRACKET) { |
| mv.visitInsn(DUP); |
| final int resultIdx = compileStack.defineTemporaryVariable("postfix_" + method, true); |
| BytecodeExpression result = new BytecodeExpression() { |
| public void visit(MethodVisitor mv) { |
| mv.visitVarInsn(ALOAD, resultIdx); |
| } |
| }; |
| TupleExpression args = new ArgumentListExpression(); |
| args.addExpression(be.getRightExpression()); |
| args.addExpression(result); |
| makeCallSite( |
| be.getLeftExpression(), "putAt", |
| args, |
| false, false, false, false); |
| mv.visitInsn(POP); |
| compileStack.removeVar(resultIdx); |
| } |
| } |
| |
| if (expression instanceof VariableExpression || |
| expression instanceof FieldExpression || |
| expression instanceof PropertyExpression) |
| { |
| mv.visitInsn(DUP); |
| leftHandExpression = true; |
| expression.visit(this); |
| leftHandExpression = false; |
| } |
| } |
| |
| protected void evaluatePrefixMethod(String method, Expression expression) { |
| // execute Method |
| execMethodAndStoreForSubscriptOperator(method,expression); |
| |
| // new value is already on stack, so nothing to do here |
| } |
| |
| protected void evaluatePostfixMethod(String method, Expression expression) { |
| // load |
| expression.visit(this); |
| |
| // save value for later |
| int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, true); |
| |
| // execute Method |
| execMethodAndStoreForSubscriptOperator(method,expression); |
| // remove the result of the method call |
| mv.visitInsn(POP); |
| |
| //reload saved value |
| mv.visitVarInsn(ALOAD, tempIdx); |
| compileStack.removeVar(tempIdx); |
| } |
| |
| protected void evaluateInstanceof(BinaryExpression expression) { |
| visitAndAutoboxBoolean(expression.getLeftExpression()); |
| Expression rightExp = expression.getRightExpression(); |
| ClassNode classType = ClassHelper.DYNAMIC_TYPE; |
| if (rightExp instanceof ClassExpression) { |
| ClassExpression classExp = (ClassExpression) rightExp; |
| classType = classExp.getType(); |
| } else { |
| throw new RuntimeException( |
| "Right hand side of the instanceof keyword must be a class name, not: " + rightExp); |
| } |
| String classInternalName = BytecodeHelper.getClassInternalName(classType); |
| mv.visitTypeInsn(INSTANCEOF, classInternalName); |
| } |
| |
| /** |
| * @return true if the given argument expression requires the stack, in |
| * which case the arguments are evaluated first, stored in the |
| * variable stack and then reloaded to make a method call |
| */ |
| protected boolean argumentsUseStack(Expression arguments) { |
| return arguments instanceof TupleExpression || arguments instanceof ClosureExpression; |
| } |
| |
| private static boolean isThisExpression(Expression expression) { |
| if (expression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) expression; |
| return varExp.getName().equals("this"); |
| } |
| return false; |
| } |
| |
| private static boolean isSuperExpression(Expression expression) { |
| if (expression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) expression; |
| return varExp.getName().equals("super"); |
| } |
| return false; |
| } |
| |
| private static boolean isThisOrSuper(Expression expression) { |
| return isThisExpression(expression) || isSuperExpression(expression); |
| } |
| |
| |
| /** |
| * For assignment expressions, return a safe expression for the LHS we can use |
| * to return the value |
| */ |
| protected Expression createReturnLHSExpression(Expression expression) { |
| if (expression instanceof BinaryExpression) { |
| BinaryExpression binExpr = (BinaryExpression) expression; |
| if (binExpr.getOperation().isA(Types.ASSIGNMENT_OPERATOR)) { |
| return createReusableExpression(binExpr.getLeftExpression()); |
| } |
| } |
| return null; |
| } |
| |
| protected Expression createReusableExpression(Expression expression) { |
| ExpressionTransformer transformer = new ExpressionTransformer() { |
| public Expression transform(Expression expression) { |
| if (expression instanceof PostfixExpression) { |
| PostfixExpression postfixExp = (PostfixExpression) expression; |
| return postfixExp.getExpression(); |
| } else if (expression instanceof PrefixExpression) { |
| PrefixExpression prefixExp = (PrefixExpression) expression; |
| return prefixExp.getExpression(); |
| } |
| return expression; |
| } |
| }; |
| |
| // could just be a postfix / prefix expression or nested inside some other expression |
| return transformer.transform(expression.transformExpression(transformer)); |
| } |
| |
| protected boolean isComparisonExpression(Expression expression) { |
| if (expression instanceof BinaryExpression) { |
| BinaryExpression binExpr = (BinaryExpression) expression; |
| switch (binExpr.getOperation().getType()) { |
| case Types.COMPARE_EQUAL: |
| case Types.MATCH_REGEX: |
| case Types.COMPARE_GREATER_THAN: |
| case Types.COMPARE_GREATER_THAN_EQUAL: |
| case Types.COMPARE_LESS_THAN: |
| case Types.COMPARE_LESS_THAN_EQUAL: |
| case Types.COMPARE_IDENTICAL: |
| case Types.COMPARE_NOT_EQUAL: |
| case Types.KEYWORD_INSTANCEOF: |
| case Types.KEYWORD_IN: |
| return true; |
| } |
| } else if (expression instanceof BooleanExpression) { |
| return true; |
| } |
| return false; |
| } |
| |
| protected void onLineNumber(ASTNode statement, String message) { |
| if (statement==null) return; |
| int line = statement.getLineNumber(); |
| int col = statement.getColumnNumber(); |
| this.currentASTNode = statement; |
| |
| if (line < 0) return; |
| if (!ASM_DEBUG && line==lineNumber) return; |
| |
| lineNumber = line; |
| columnNumber = col; |
| |
| if (mv != null) { |
| Label l = new Label(); |
| mv.visitLabel(l); |
| mv.visitLineNumber(line, l); |
| if (ASM_DEBUG) { |
| helper.mark(message + "[" + statement.getLineNumber() + ":" + statement.getColumnNumber() + "]"); |
| } |
| } |
| } |
| |
| private boolean isInnerClass() { |
| return classNode instanceof InnerClassNode; |
| } |
| |
| /** |
| * @return true if the given name is a local variable or a field |
| */ |
| protected boolean isFieldOrVariable(String name) { |
| return compileStack.containsVariable(name) || classNode.getDeclaredField(name) != null; |
| } |
| |
| /** |
| * @return if the type of the expression can be determined at compile time |
| * then this method returns the type - otherwise null |
| */ |
| protected ClassNode getExpressionType(Expression expression) { |
| if (isComparisonExpression(expression)) { |
| return ClassHelper.boolean_TYPE; |
| } |
| if (expression instanceof VariableExpression) { |
| VariableExpression varExpr = (VariableExpression)expression; |
| |
| if (varExpr.isThisExpression()) { |
| return classNode; |
| } else if (varExpr.isSuperExpression()) { |
| return classNode.getSuperClass(); |
| } |
| |
| Variable variable = compileStack.getVariable(varExpr.getName(), false); |
| if (variable != null && !variable.isHolder()) { |
| ClassNode type = variable.getType(); |
| if (!variable.isDynamicTyped()) return type; |
| } |
| if (variable == null) { |
| org.codehaus.groovy.ast.Variable var = (org.codehaus.groovy.ast.Variable) compileStack.getScope().getReferencedClassVariable(varExpr.getName()); |
| if (var != null && !var.isDynamicTyped()) return var.getType(); |
| } |
| } |
| return expression.getType(); |
| } |
| |
| protected boolean isInClosureConstructor() { |
| return constructorNode != null |
| && classNode.getOuterClass() != null |
| && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; |
| } |
| |
| protected boolean isInClosure() { |
| return classNode.getOuterClass() != null |
| && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; |
| } |
| |
| protected boolean isNotExplicitThisInClosure(boolean implicitThis) { |
| return implicitThis || !isInClosure(); |
| } |
| |
| protected boolean isStaticMethod() { |
| return methodNode != null && methodNode.isStatic(); |
| } |
| |
| protected CompileUnit getCompileUnit() { |
| CompileUnit answer = classNode.getCompileUnit(); |
| if (answer == null) { |
| answer = context.getCompileUnit(); |
| } |
| return answer; |
| } |
| |
| public static boolean usesSuper(MethodCallExpression call) { |
| Expression expression = call.getObjectExpression(); |
| if (expression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) expression; |
| String variable = varExp.getName(); |
| return variable.equals("super"); |
| } |
| return false; |
| } |
| |
| public static boolean usesSuper(PropertyExpression pe) { |
| Expression expression = pe.getObjectExpression(); |
| if (expression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) expression; |
| String variable = varExp.getName(); |
| return variable.equals("super"); |
| } |
| return false; |
| } |
| |
| protected int getBytecodeVersion() { |
| if ( !classNode.isUsingGenerics() && |
| !classNode.isAnnotated() && |
| !classNode.isAnnotationDefinition() ) |
| { |
| return Opcodes.V1_3; |
| } |
| |
| final String target = getCompileUnit().getConfig().getTargetBytecode(); |
| return CompilerConfiguration.POST_JDK5.equals(target) ? Opcodes.V1_5 : Opcodes.V1_3; |
| } |
| |
| private class MyMethodAdapter extends MethodAdapter { |
| private String boxingDesc = null; |
| |
| public MyMethodAdapter() { |
| super(AsmClassGenerator.this.mv); |
| } |
| |
| private void dropBoxing () { |
| if (boxingDesc != null) { |
| super.visitMethodInsn(INVOKESTATIC, DTT, "box", boxingDesc); |
| boxingDesc = null; |
| } |
| } |
| |
| public void visitInsn(int opcode) { |
| dropBoxing (); |
| super.visitInsn(opcode); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitIntInsn(int opcode, int operand) { |
| dropBoxing (); |
| super.visitIntInsn(opcode, operand); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitVarInsn(int opcode, int var) { |
| dropBoxing (); |
| super.visitVarInsn(opcode, var); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitTypeInsn(int opcode, String desc) { |
| dropBoxing (); |
| super.visitTypeInsn(opcode, desc); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| dropBoxing (); |
| super.visitFieldInsn(opcode, owner, name, desc); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitMethodInsn(int opcode, String owner, String name, String desc) { |
| if (boxing(opcode,owner,name)) { |
| boxingDesc = desc; |
| dropBoxing(); |
| } |
| else { |
| if (unboxing(opcode, owner, name)) { |
| if (boxingDesc != null) |
| boxingDesc = null; |
| else |
| super.visitMethodInsn(opcode, owner, name, desc); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| else { |
| dropBoxing(); |
| super.visitMethodInsn(opcode, owner, name, desc); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| } |
| } |
| |
| private boolean boxing(int opcode, String owner, String name) { |
| return opcode == INVOKESTATIC && owner.equals(DTT) && name.equals("box"); |
| } |
| |
| private boolean unboxing(int opcode, String owner, String name) { |
| return opcode == INVOKESTATIC && owner.equals(DTT) && name.endsWith("Unbox"); |
| } |
| |
| public void visitJumpInsn(int opcode, Label label) { |
| dropBoxing (); |
| super.visitJumpInsn(opcode, label); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitLabel(Label label) { |
| dropBoxing (); |
| super.visitLabel(label); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitLdcInsn(Object cst) { |
| dropBoxing (); |
| super.visitLdcInsn(cst); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitIincInsn(int var, int increment) { |
| dropBoxing (); |
| super.visitIincInsn(var, increment); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) { |
| dropBoxing (); |
| super.visitTableSwitchInsn(min, max, dflt, labels); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) { |
| dropBoxing (); |
| super.visitLookupSwitchInsn(dflt, keys, labels); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| dropBoxing (); |
| super.visitMultiANewArrayInsn(desc, dims); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| |
| public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { |
| dropBoxing (); |
| super.visitTryCatchBlock(start, end, handler, type); //To change body of overridden methods use File | Settings | File Templates. |
| } |
| } |
| } |