/* * 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: 18253 $ */ 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$", then it is * a 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$"); } 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, endOfAllCatches, 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); // FIXASC (groovychange) bad code GRE290 if (breakLabel == null) { return; } // end 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); // FIXASC (groovychange) bad code GRE291 if (continueLabel == null) { return; } // end 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); 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) { // FIXASC (groovychange) could be null for malformed code (GRE290) if (expression==null) { return; } // end 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. } } }